in progress: configuring s3 to allow public get

This commit is contained in:
2023-11-07 15:19:26 -06:00
parent 5fc7a9f983
commit 8c8ca802aa
11 changed files with 118 additions and 82 deletions

View File

@@ -1,6 +1,37 @@
import InProgress from "@/components/InProgress";
import NotFound from "@/components/NotFound";
import MusicController from "@/server/controllers/music.controller";
import { MusicStreamingEntry } from "@/server/db/schema";
import { S3Service } from "@/server/s3";
import { Suspense } from "react";
export default async function ListenByCollectionID() {
export default async function ListenByCollectionID({ params }: { params: { collectionid?: string }}) {
const id = params.collectionid ? Number(params.collectionid) : undefined;
if (!id) return <NotFound />
return <InProgress />
const controller = new MusicController();
const result = await controller.getByID(id);
if (!result) return <NotFound />
console.log(result);
const entries = await S3Service.getURLs(result.pathToEntry);
return (
<div>
<Suspense fallback={<p>Loading...</p>}>
<header>
<h1>{result.name}</h1>
<p>{result.shortDescription}</p>
</header>
<p>{result.longDescription}</p>
<section>
{ entries
? entries.map((entry: string, idx: number) => <p key={idx}>{entry}</p>)
: <p>No entries found</p>
}
</section>
</Suspense>
</div>
)
}

8
components/NotFound.tsx Normal file
View File

@@ -0,0 +1,8 @@
export default function NotFound() {
return (
<div className="flex flex-col items-center justify-center w-screen h-screen">
<h1 className="text-4xl font-bold">404</h1>
<h2 className="text-2xl font-semibold">Page not found</h2>
</div>
)
}

View File

@@ -12,11 +12,12 @@
},
"dependencies": {
"@aws-sdk/client-s3": "^3.367.0",
"@smithy/node-http-handler": "^2.1.8",
"@t3-oss/env-nextjs": "^0.7.0",
"@vercel/style-guide": "^5.0.1",
"autoprefixer": "10.4.14",
"ioredis": "^5.3.2",
"next": "^13.4.12",
"next": "^14.0.1",
"pg": "^8.11.3",
"react": "18.2.0",
"react-dom": "18.2.0",

View File

@@ -1,5 +1,5 @@
import { S3Client } from '@aws-sdk/client-s3';
import Redis from 'ioredis';
// import Redis from 'ioredis';
import pg from 'pg';
import { createDBClient } from '../db';
import { Maybe, must } from '@/util/helpers';
@@ -16,40 +16,47 @@ type ControllerOptions<T> = {
export default abstract class BaseController<T> {
#db: pg.Client
#bucket: S3Client
#cache: Redis
// #bucket: S3Client
// #cache: Redis
tableName: string
parser?: FullParserType<T>
constructor(options: ControllerOptions<T>) {
this.#db = must(createDBClient);
this.#bucket = must(createS3Client);
this.#cache = must(createRedisClient);
// this.#bucket = must(createS3Client);
// this.#cache = must(createRedisClient);
this.tableName = options.tableName;
this.parser = options.parser;
}
async getAll(projection?: string): Promise<Maybe<T[]>> {
'use server';
try {
// we'll enable cache here later
await this.#db.connect();
const result = await this.#db.query("SELECT $1 FROM $2", [projection ?? "*", this.tableName]);
return result.rows;
} catch (error) {
console.log({ error });
return null;
} finally {
await this.#db.end();
}
}
async getByID(id: number, projection?: string): Promise<Maybe<T>> {
try {
const result = await this.#db.query("SELECT $1 FROM $2 WHERE id = $3", [projection ?? "*", this.tableName, id]);
await this.#db.connect();
const result = await this.#db.query(`SELECT ${projection ?? "*"} FROM ${this.tableName} WHERE id = ${id}`);
return result.rows[0];
} catch (error) {
console.log({ error });
return null;
} finally {
await this.#db.end();
}
}
}

View File

@@ -8,8 +8,8 @@ export const ZMusicStreamingEntry = z.object({
name: z.string().max(100),
shortDescription: z.string().max(100),
longDescription: z.string().max(1000),
pathToEntry: z.string(),
tags: z.array(z.string().max(100)).optional(),
pathToEntry: z.string().optional(),
});
export type MusicStreamingEntry = z.infer<typeof ZMusicStreamingEntry>;

View File

@@ -1,5 +1,7 @@
import { env } from "@/env.mjs";
import { S3Client, S3ClientConfig } from "@aws-sdk/client-s3";
import { NodeHttpHandler } from "@smithy/node-http-handler";
import https from "https";
export default function createS3Client() {
if (typeof process.env.S3_ACCESS_KEY !== "string") {
@@ -11,7 +13,17 @@ export default function createS3Client() {
}
const config: S3ClientConfig = {
endpoint: env.S3_ENDPOINT
endpoint: env.S3_ENDPOINT,
region: "us-east-1",
requestHandler: new NodeHttpHandler({
httpsAgent: new https.Agent({
rejectUnauthorized: false,
ciphers: "ALL",
}),
}),
forcePathStyle: true,
}
if (env.S3_SECRET) {

View File

@@ -2,8 +2,6 @@ import { ListObjectsV2Command, PutObjectCommand, PutObjectCommandOutput, S3Clien
import { env } from "@/env.mjs";
import createS3Client from "./createClient";
import { Maybe, must } from "@/util/helpers";
import { readFile } from "fs/promises";
import { readFileSync, readdir } from "fs";
export default class S3Service {
static async getFiles(key: string): Promise<Maybe<_Object[]>> {
@@ -34,71 +32,4 @@ export default class S3Service {
return null;
}
}
static async upload(filePath: string, key: string) {
if (env.NODE_ENV != "development") {
throw new Error("Cannot upload files in production");
}
try {
const client = must<S3Client>(createS3Client);
const Body = await readFile(filePath);
const cmd = new PutObjectCommand({
Bucket: env.S3_BUCKET,
Key: key,
Body,
});
const result = await client.send(cmd);
return result.$metadata.httpStatusCode == 200;
} catch (error) {
console.log({ error });
return null;
}
}
static async uploadAllInDirectory(dirPath: string, prefix: string): Promise<boolean> {
if (env.NODE_ENV != "development") {
throw new Error("Cannot upload files in production");
}
try {
const client = must<S3Client>(createS3Client);
const promises = new Array<Promise<PutObjectCommandOutput>>();
const results = new Array<boolean>();
readdir(dirPath, (err, files) => {
if (err) {
console.log({ err });
return null;
}
files.forEach(file => {
const Key = `${prefix}/${file}`
const Body = readFileSync(Key);
const cmd = new PutObjectCommand({
Bucket: env.S3_BUCKET,
Key,
Body,
})
promises.push(client.send(cmd));
})
})
promises.forEach(p => {
setTimeout(async() => {
const output = await Promise.resolve(p);
results.push(output.$metadata.httpStatusCode == 200);
}, 1000)
})
return results.every(r => true);
} catch (error) {
console.log({ error });
return false;
}
}
}

View File

@@ -0,0 +1,20 @@
CREATE TABLE IF NOT EXISTS music (
id INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
name varchar(100),
shortDescription varchar(100),
longDescription varchar(1000),
pathToEntry varchar
);
CREATE TABLE IF NOT EXISTS tags (
id INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
name varchar(100)
);
CREATE TABLE IF NOT EXISTS music_tags (
id INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
music_id INTEGER,
tag_id INTEGER,
FOREIGN KEY(music_id) REFERENCES music(id),
FOREIGN KEY(tag_id) REFERENCES tags(id)
);

26
server/sql/seed.sql Normal file
View File

@@ -0,0 +1,26 @@
INSERT INTO music (name, shortDescription, longDescription, pathToEntry) VALUES (
'Jisei',
'My first major undertaking as a singer-songwriter',
'Released in October 2020',
'Jisei'
);
INSERT INTO tags (name) VALUES
(
'indie-rock'
),
(
'alternative'
),
(
'guitar'
),
(
'cello'
);
INSERT INTO music_tags (music_id, tag_id) VALUES
(1, 1),
(1, 2),
(1, 3),
(1, 4);

View File

@@ -1,5 +1,5 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
export default {
content: [
'./pages/**/*.{js,ts,jsx,tsx,mdx}',
'./components/**/*.{js,ts,jsx,tsx,mdx}',