feat: /list endpoint
This commit is contained in:
parent
8c8ac3523b
commit
5e1c610614
|
@ -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;
|
||||||
|
}
|
|
@ -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();
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
|
@ -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();
|
||||||
|
|
20
src/store.ts
20
src/store.ts
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue