diff options
| author | Elizabeth Hunt <me@liz.coffee> | 2025-12-14 22:43:24 -0800 |
|---|---|---|
| committer | Elizabeth Hunt <me@liz.coffee> | 2025-12-14 22:43:24 -0800 |
| commit | cdb1a57614068fcfefa145bc6df45c9def7ccc6a (patch) | |
| tree | 92cadbecda8658c143b7625d5925e3411976a892 /src/storage/index.ts | |
| parent | 6d318665a08c0d4564d8de23cc39425d2c0bac49 (diff) | |
| download | posthook-cdb1a57614068fcfefa145bc6df45c9def7ccc6a.tar.gz posthook-cdb1a57614068fcfefa145bc6df45c9def7ccc6a.zip | |
Updates
Diffstat (limited to 'src/storage/index.ts')
| -rw-r--r-- | src/storage/index.ts | 79 |
1 files changed, 63 insertions, 16 deletions
diff --git a/src/storage/index.ts b/src/storage/index.ts index 2c8ffb2..631fc2e 100644 --- a/src/storage/index.ts +++ b/src/storage/index.ts @@ -1,9 +1,23 @@ import { randomUUID } from 'crypto'; import { mkdir, writeFile, readFile } from 'fs/promises'; -import { join } from 'path'; +import { basename, join } from 'path'; import { isSafeRouteName, type RouteConfig, type StoredRequest } from '../types/index.js'; import { Either, type IEither } from '@emprespresso/pengueno'; +type IncomingUpload = { + fieldName: string; + filename: string; + contentType: string; + size: number; + data: Uint8Array; +}; + +function sanitizeFilename(filename: string): string { + const base = basename(filename); + const safe = base.replace(/[^a-zA-Z0-9._-]/g, '_'); + return safe.length > 0 ? safe.slice(0, 200) : 'upload.bin'; +} + export class Storage { private routes: Map<string, RouteConfig> = new Map(); @@ -83,7 +97,7 @@ export class Storage { method: string, headers: Record<string, string>, body: unknown, - files?: StoredRequest['files'], + uploads?: IncomingUpload[], ): Promise<IEither<Error, StoredRequest>> { if (!isSafeRouteName(routeName)) { return Either.left(new Error('Invalid route name')); @@ -91,21 +105,54 @@ export class Storage { const timestamp = Date.now(); const uuid = randomUUID(); - const filename = `${timestamp}_${uuid}.json`; - - const stored: StoredRequest = { - timestamp, - uuid, - routeName, - method, - headers, - body, - files, - }; - - const filepath = join(this.dataDir, routeName, filename); + const baseName = `${timestamp}_${uuid}`; + const routeDir = join(this.dataDir, routeName); + try { - await writeFile(filepath, JSON.stringify(stored, null, 2)); + await mkdir(routeDir, { recursive: true }); + + const requestDir = join(routeDir, baseName); + await mkdir(requestDir, { recursive: true }); + + const files: StoredRequest['files'] = uploads?.length + ? await (async () => { + const filesDir = join(requestDir, 'files'); + await mkdir(filesDir, { recursive: true }); + + const storedFiles: NonNullable<StoredRequest['files']> = []; + for (let i = 0; i < uploads.length; i++) { + const upload = uploads[i]; + const safeOriginal = sanitizeFilename(upload.filename); + const savedName = `${i}_${safeOriginal}`; + const diskPath = join(filesDir, savedName); + await writeFile(diskPath, Buffer.from(upload.data)); + + storedFiles.push({ + fieldName: upload.fieldName, + originalFilename: upload.filename, + filename: savedName, + contentType: upload.contentType, + size: upload.size, + path: join('files', savedName), + }); + } + + return storedFiles; + })() + : undefined; + + const stored: StoredRequest = { + timestamp, + uuid, + routeName, + method, + headers, + body, + files, + }; + + await writeFile(join(requestDir, 'request.json'), JSON.stringify(stored, null, 2)); + await writeFile(join(requestDir, 'body.json'), JSON.stringify(body, null, 2)); return Either.right(stored); } catch (err) { return Either.left(err instanceof Error ? err : new Error(String(err))); |
