feat: switch to local filestore

The plan is to adopt a sidecar model where files are written to disk
then transcoded by a sidecar written in rust where they will then be
written to another location (ie. Backblaze B2, S3, NFS)
This commit is contained in:
Roman Godmaire 2024-02-10 14:44:10 -05:00
parent d03ebf3f7a
commit 054e3635f3
7 changed files with 59 additions and 52 deletions

5
plice/src/app.d.ts vendored
View file

@ -1,6 +1,6 @@
import type { Auth, AuthRequest } from 'lucia'; import type { Auth, AuthRequest } from 'lucia';
import type { Database } from '$lib/server/db'; import type { Database } from '$lib/server/db';
import type { ObjectStorage } from '$lib/server/storage'; import type { ReadFileStore, WriteFileStore } from '$lib/server/storage';
declare global { declare global {
namespace App { namespace App {
@ -8,7 +8,8 @@ declare global {
interface Locals { interface Locals {
database: Database; database: Database;
auth: Auth; auth: Auth;
objectStorage: ObjectStorage; readStore: ReadFileStore;
writeStore: WriteFileStore;
authReq: AuthRequest; authReq: AuthRequest;
} }

View file

@ -3,18 +3,20 @@ import type { Handle } from '@sveltejs/kit';
import { env } from '$env/dynamic/private'; import { env } from '$env/dynamic/private';
import { auth } from '$lib/server/lucia'; import { auth } from '$lib/server/lucia';
import { ObjectStorageNoop } from '$lib/server/storage/noop'; import { LocalFileStore } from '$lib/server/storage/local';
import { DatabasePrisma } from '$lib/server/db/prisma'; import { DatabasePrisma } from '$lib/server/db/prisma';
const s3Client = new ObjectStorageNoop(); const localFileStore = new LocalFileStore(env.FILE_BASE_PATH, env.FILE_EXTENSION);
const prismaClient = new DatabasePrisma(env.DATABASE_URL); const prismaClient = new DatabasePrisma(env.DATABASE_URL);
export const handle: Handle = async ({ event, resolve }) => { export const handle: Handle = async ({ event, resolve }) => {
event.locals.auth = auth; event.locals = {
event.locals.database = prismaClient; auth,
event.locals.objectStorage = s3Client; authReq: auth.handleRequest(event),
database: prismaClient,
event.locals.authReq = auth.handleRequest(event); writeStore: localFileStore,
readStore: localFileStore
};
return await resolve(event); return await resolve(event);
}; };

View file

@ -1,3 +1,7 @@
export interface ObjectStorage { export interface ReadFileStore {
putObject: (key: string, obj: Uint8Array) => Promise<Error | null>; read: (key: string) => Promise<Buffer | Error>;
}
export interface WriteFileStore {
write: (key: string, obj: Buffer) => Promise<null | Error>;
} }

View file

@ -0,0 +1,29 @@
import type { ReadFileStore, WriteFileStore } from '.';
import { promises as fs } from 'node:fs';
export class LocalFileStore implements ReadFileStore, WriteFileStore {
basePath: string;
extension: string;
constructor(basePath: string, extension: string) {
this.basePath = basePath;
this.extension = extension;
}
private getPath = (key: string) => {
return `${this.basePath}/${key}.${this.extension}`;
};
write = async (key: string, obj: Buffer) => {
const path = this.getPath(key);
await fs.writeFile(path, obj);
return null;
};
read = async (key: string) => {
const path = this.getPath(key);
const obj = await fs.readFile(path);
return obj;
};
}

View file

@ -1,9 +1,13 @@
import type { ObjectStorage } from '.'; import type { ReadFileStore, WriteFileStore } from '.';
class ObjectStorageNoop implements ObjectStorage { class NoopFileStore implements ReadFileStore, WriteFileStore {
putObject = async (_key: string, _obj: Uint8Array) => { read = async (_key: string) => {
return new ArrayBuffer(0) as Buffer;
};
write = async (_key: string, _obj: Buffer) => {
return null; return null;
}; };
} }
export { ObjectStorageNoop }; export { NoopFileStore };

View file

@ -1,33 +0,0 @@
import type { ObjectStorage } from '.';
import { PutObjectCommand, S3Client, S3ServiceException } from '@aws-sdk/client-s3';
class ObjectStorageS3 implements ObjectStorage {
client: S3Client;
bucket: string;
constructor(url: string, bucket: string) {
this.bucket = bucket;
this.client = new S3Client({
endpoint: url,
region: 'us-east-1'
});
}
putObject = async (key: string, obj: Uint8Array) => {
const command = new PutObjectCommand({
Bucket: this.bucket,
Key: key,
Body: obj
});
try {
await this.client.send(command);
} catch (err) {
return err as S3ServiceException;
}
return null;
};
}
export { ObjectStorageS3 };

View file

@ -10,7 +10,7 @@ export const load: PageServerLoad = async ({ locals: { authReq } }) => {
}; };
export const actions: Actions = { export const actions: Actions = {
default: async ({ request, locals: { authReq, database, objectStorage } }) => { default: async ({ request, locals: { authReq, database, writeStore } }) => {
const session = await authReq.validate(); const session = await authReq.validate();
if (!session) redirect(302, '/login'); if (!session) redirect(302, '/login');
@ -20,9 +20,9 @@ export const actions: Actions = {
const title = formData.get('title') as string; const title = formData.get('title') as string;
const file = formData.get('file') as File; const file = formData.get('file') as File;
const objectKey = `${uuidv4()}.mp3`; const objectKey = `${uuidv4()}`;
const err = await objectStorage.putObject(objectKey, new Uint8Array(await file.arrayBuffer())); const err = await writeStore.write(objectKey, (await file.arrayBuffer()) as Buffer);
if (err) { if (err) {
console.log(err); console.log(err);
return fail(500, { return fail(500, {
@ -30,7 +30,7 @@ export const actions: Actions = {
}); });
} }
const track = await database.createTrack(producerId, title, objectKey); const track = await database.createTrack(producerId, title);
await database.createTrackVersion(track.id); await database.createTrackVersion(track.id);
redirect(302, '/'); redirect(302, '/');