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:
parent
d03ebf3f7a
commit
054e3635f3
7 changed files with 59 additions and 52 deletions
5
plice/src/app.d.ts
vendored
5
plice/src/app.d.ts
vendored
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
};
|
};
|
||||||
|
|
|
@ -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>;
|
||||||
}
|
}
|
||||||
|
|
29
plice/src/lib/server/storage/local.ts
Normal file
29
plice/src/lib/server/storage/local.ts
Normal 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;
|
||||||
|
};
|
||||||
|
}
|
|
@ -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 };
|
||||||
|
|
|
@ -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 };
|
|
|
@ -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, '/');
|
||||||
|
|
Loading…
Reference in a new issue