From 5e1c610614a0431fb65d7c20ac48f1f06f486348 Mon Sep 17 00:00:00 2001 From: b1ek Date: Sat, 11 May 2024 21:06:05 +1000 Subject: [PATCH] feat: /list endpoint --- src/client.ts | 23 +++++++++++++++++++++++ src/config.ts | 15 +++++++-------- src/routes/index.ts | 2 ++ src/routes/list.ts | 41 +++++++++++++++++++++++++++++++++++++++++ src/routes/upload.ts | 9 +++------ src/store.ts | 20 ++++++++------------ 6 files changed, 84 insertions(+), 26 deletions(-) create mode 100644 src/client.ts create mode 100644 src/routes/list.ts diff --git a/src/client.ts b/src/client.ts new file mode 100644 index 0000000..82e9a89 --- /dev/null +++ b/src/client.ts @@ -0,0 +1,23 @@ +import crypto from 'node:crypto'; + +export type Client = { + name: string; + keySha256Sum: string; + /** + * In megabytes + */ + quota?: number; +}; + +export type NoKeyClient = { + name: string; + quota?: number; +}; + +export function checkKey(key: string, client: Client): boolean { + const sha = crypto.createHash('sha256'); + sha.update(key); + const hashed = sha.digest().toString('hex'); + + return client.keySha256Sum === hashed; +} diff --git a/src/config.ts b/src/config.ts index 47aef37..eaf2ab0 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,3 +1,5 @@ +import { Client, checkKey } from './client.js'; + import fsp from 'node:fs/promises'; import Ajv from 'ajv'; const ajv = new Ajv(); @@ -23,14 +25,7 @@ const config_validate = ajv.compile({ }); export class Config { - clients: { - name: string; - keySha256Sum: string; - /** - * In megabytes - */ - quota?: number; - }[]; + clients: Client[]; storePath: string; static async load(): Promise { @@ -56,6 +51,10 @@ export class Config { return raw; } + + static getClientByKey(config: Config, key: string): Client | undefined { + return config.clients.find((client) => checkKey(key, client)); + } } export const config = await Config.load(); diff --git a/src/routes/index.ts b/src/routes/index.ts index b3afad0..65cb5ae 100644 --- a/src/routes/index.ts +++ b/src/routes/index.ts @@ -1,6 +1,8 @@ import { FastifyPluginAsync } from 'fastify'; import upload from './upload.js'; +import list from './list.js'; export default (async function (fastify) { await fastify.register(upload); + await fastify.register(list); } as FastifyPluginAsync); diff --git a/src/routes/list.ts b/src/routes/list.ts new file mode 100644 index 0000000..21447e5 --- /dev/null +++ b/src/routes/list.ts @@ -0,0 +1,41 @@ +import { FastifyPluginAsync } from 'fastify'; +import fsp from 'node:fs/promises'; +import path from 'node:path'; + +import { Config, config } from '../config.js'; +import { InvalidAuthorization } from '../errors.js'; +import { Stats } from 'node:fs'; + +type ListFilesResponse = { + name: string; + created: Date; +}; + +export default (async function (fastify) { + fastify.get('/list', async (req) => { + const key = req.headers.authorization?.replace('Bearer ', '') ?? 'none'; + + const client = Config.getClientByKey(config, key); + if (!client) { + throw InvalidAuthorization(); + } + + const rawfl = await fsp.readdir(config.storePath); + const files: [Stats, string][] = await Promise.all( + rawfl + .filter((x) => x.startsWith(client.name + '-')) + .map(async (x) => [ + await fsp.stat(path.join(config.storePath, x)), + x, + ]), + ); + + return files.map( + (x) => + ({ + name: x[1].replace(client.name + '-', ''), + created: x[0].birthtime, + }) as ListFilesResponse, + ); + }); +} as FastifyPluginAsync); diff --git a/src/routes/upload.ts b/src/routes/upload.ts index b2f6317..1f8ebaf 100644 --- a/src/routes/upload.ts +++ b/src/routes/upload.ts @@ -1,6 +1,5 @@ import { FastifyPluginAsync } from 'fastify'; import Ajv, { JSONSchemaType, type Schema } from 'ajv'; -import crypto from 'node:crypto'; import { InvalidAuthorization, @@ -8,7 +7,7 @@ import { QuotaExceeded, ValidationError, } from '../errors.js'; -import { config } from '../config.js'; +import { Config, config } from '../config.js'; import { save } from '../store.js'; type UploadPayload = { @@ -27,11 +26,9 @@ const upload_schema = { export default (async function (fastify) { fastify.post('/upload', async (req) => { - const sha = crypto.createHash('sha256'); - sha.update(req.headers.authorization?.replace('Bearer ', '') ?? 'none'); - const key = sha.digest().toString('hex'); + const key = req.headers.authorization?.replace('Bearer ', '') ?? 'none'; - const client = config.clients.find((x) => x.keySha256Sum === key); + const client = Config.getClientByKey(config, key); if (!client) { throw InvalidAuthorization(); diff --git a/src/store.ts b/src/store.ts index 317e677..8efb2a8 100644 --- a/src/store.ts +++ b/src/store.ts @@ -2,10 +2,9 @@ import { config } from './config.js'; import path from 'path'; import fsp from 'node:fs/promises'; +import { NoKeyClient } from './client.js'; -function getClient( - clientName: string, -): { name: string; quota: number } | undefined { +function getClient(clientName: string): NoKeyClient | undefined { const client = config.clients.find((x) => x.name === clientName) as | { name: string; quota: number; keySha256Sum?: string } | undefined; @@ -14,13 +13,9 @@ function getClient( return client; } -function getClientOrThrowErr( - clientName: string, - error?: string, -): { name: string; quota: number } { +function getClientOrThrowErr(clientName: string, error?: string): NoKeyClient { const client = getClient(clientName); - if (typeof client === 'undefined') - throw Error(error ?? 'Client does not exist.'); + if (!client) throw Error(error ?? 'Client does not exist.'); return client; } @@ -56,9 +51,10 @@ export async function save( const client = getClientOrThrowErr(clientName); const quota = await getTakenQuota(clientName); - if (data.length + quota > client.quota) { - return 'Quota exceeded'; - } + if (client.quota) + if (data.length + quota > client.quota) { + return 'Quota exceeded'; + } await fsp.writeFile(getPathFor(name, clientName), data); }