aboutsummaryrefslogtreecommitdiff
path: root/src/storage
diff options
context:
space:
mode:
authorElizabeth Hunt <me@liz.coffee>2025-12-14 22:43:24 -0800
committerElizabeth Hunt <me@liz.coffee>2025-12-14 22:43:24 -0800
commitcdb1a57614068fcfefa145bc6df45c9def7ccc6a (patch)
tree92cadbecda8658c143b7625d5925e3411976a892 /src/storage
parent6d318665a08c0d4564d8de23cc39425d2c0bac49 (diff)
downloadposthook-cdb1a57614068fcfefa145bc6df45c9def7ccc6a.tar.gz
posthook-cdb1a57614068fcfefa145bc6df45c9def7ccc6a.zip
Updates
Diffstat (limited to 'src/storage')
-rw-r--r--src/storage/index.ts79
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)));