feat: /list endpoint

This commit is contained in:
b1ek 2024-05-11 21:06:05 +10:00
parent 8c8ac3523b
commit 5e1c610614
Signed by: blek
GPG Key ID: 14546221E3595D0C
6 changed files with 84 additions and 26 deletions

23
src/client.ts Normal file
View File

@ -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;
}

View File

@ -1,3 +1,5 @@
import { Client, checkKey } from './client.js';
import fsp from 'node:fs/promises'; import fsp from 'node:fs/promises';
import Ajv from 'ajv'; import Ajv from 'ajv';
const ajv = new Ajv(); const ajv = new Ajv();
@ -23,14 +25,7 @@ const config_validate = ajv.compile({
}); });
export class Config { export class Config {
clients: { clients: Client[];
name: string;
keySha256Sum: string;
/**
* In megabytes
*/
quota?: number;
}[];
storePath: string; storePath: string;
static async load(): Promise<Config> { static async load(): Promise<Config> {
@ -56,6 +51,10 @@ export class Config {
return raw; return raw;
} }
static getClientByKey(config: Config, key: string): Client | undefined {
return config.clients.find((client) => checkKey(key, client));
}
} }
export const config = await Config.load(); export const config = await Config.load();

View File

@ -1,6 +1,8 @@
import { FastifyPluginAsync } from 'fastify'; import { FastifyPluginAsync } from 'fastify';
import upload from './upload.js'; import upload from './upload.js';
import list from './list.js';
export default (async function (fastify) { export default (async function (fastify) {
await fastify.register(upload); await fastify.register(upload);
await fastify.register(list);
} as FastifyPluginAsync); } as FastifyPluginAsync);

41
src/routes/list.ts Normal file
View File

@ -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);

View File

@ -1,6 +1,5 @@
import { FastifyPluginAsync } from 'fastify'; import { FastifyPluginAsync } from 'fastify';
import Ajv, { JSONSchemaType, type Schema } from 'ajv'; import Ajv, { JSONSchemaType, type Schema } from 'ajv';
import crypto from 'node:crypto';
import { import {
InvalidAuthorization, InvalidAuthorization,
@ -8,7 +7,7 @@ import {
QuotaExceeded, QuotaExceeded,
ValidationError, ValidationError,
} from '../errors.js'; } from '../errors.js';
import { config } from '../config.js'; import { Config, config } from '../config.js';
import { save } from '../store.js'; import { save } from '../store.js';
type UploadPayload = { type UploadPayload = {
@ -27,11 +26,9 @@ const upload_schema = {
export default (async function (fastify) { export default (async function (fastify) {
fastify.post('/upload', async (req) => { fastify.post('/upload', async (req) => {
const sha = crypto.createHash('sha256'); const key = req.headers.authorization?.replace('Bearer ', '') ?? 'none';
sha.update(req.headers.authorization?.replace('Bearer ', '') ?? 'none');
const key = sha.digest().toString('hex');
const client = config.clients.find((x) => x.keySha256Sum === key); const client = Config.getClientByKey(config, key);
if (!client) { if (!client) {
throw InvalidAuthorization(); throw InvalidAuthorization();

View File

@ -2,10 +2,9 @@ import { config } from './config.js';
import path from 'path'; import path from 'path';
import fsp from 'node:fs/promises'; import fsp from 'node:fs/promises';
import { NoKeyClient } from './client.js';
function getClient( function getClient(clientName: string): NoKeyClient | undefined {
clientName: string,
): { name: string; quota: number } | undefined {
const client = config.clients.find((x) => x.name === clientName) as const client = config.clients.find((x) => x.name === clientName) as
| { name: string; quota: number; keySha256Sum?: string } | { name: string; quota: number; keySha256Sum?: string }
| undefined; | undefined;
@ -14,13 +13,9 @@ function getClient(
return client; return client;
} }
function getClientOrThrowErr( function getClientOrThrowErr(clientName: string, error?: string): NoKeyClient {
clientName: string,
error?: string,
): { name: string; quota: number } {
const client = getClient(clientName); const client = getClient(clientName);
if (typeof client === 'undefined') if (!client) throw Error(error ?? 'Client does not exist.');
throw Error(error ?? 'Client does not exist.');
return client; return client;
} }
@ -56,9 +51,10 @@ export async function save(
const client = getClientOrThrowErr(clientName); const client = getClientOrThrowErr(clientName);
const quota = await getTakenQuota(clientName); const quota = await getTakenQuota(clientName);
if (data.length + quota > client.quota) { if (client.quota)
return 'Quota exceeded'; if (data.length + quota > client.quota) {
} return 'Quota exceeded';
}
await fsp.writeFile(getPathFor(name, clientName), data); await fsp.writeFile(getPathFor(name, clientName), data);
} }