Sifa professional network API (Fastify, AT Protocol, Jetstream)
sifa.id/
1import { randomUUID } from 'node:crypto';
2import { mkdir, writeFile, unlink } from 'node:fs/promises';
3import { join } from 'node:path';
4import type { FastifyBaseLogger } from 'fastify';
5
6export interface StorageService {
7 store(data: Buffer, mimeType: string, prefix: string): Promise<string>;
8 delete(url: string): Promise<void>;
9}
10
11const MIME_TO_EXT: Record<string, string> = {
12 'image/jpeg': '.jpg',
13 'image/png': '.png',
14 'image/webp': '.webp',
15 'image/gif': '.gif',
16};
17
18export function createLocalStorage(
19 uploadDir: string,
20 baseUrl: string,
21 logger: Pick<FastifyBaseLogger, 'debug'>,
22): StorageService {
23 return {
24 async store(data: Buffer, mimeType: string, prefix: string): Promise<string> {
25 const ext = MIME_TO_EXT[mimeType] ?? '.bin';
26 const filename = `${prefix}-${randomUUID()}${ext}`;
27 const dir = join(uploadDir, prefix);
28 await mkdir(dir, { recursive: true });
29 const filepath = join(dir, filename);
30 await writeFile(filepath, data);
31 logger.debug({ filepath, size: data.length }, 'File stored');
32 return `${baseUrl}/uploads/${prefix}/${filename}`;
33 },
34
35 async delete(url: string): Promise<void> {
36 try {
37 const uploadsIdx = url.indexOf('/uploads/');
38 if (uploadsIdx === -1) return;
39 const relativePath = url.slice(uploadsIdx + '/uploads/'.length);
40 const filepath = join(uploadDir, relativePath);
41 await unlink(filepath);
42 logger.debug({ filepath }, 'File deleted');
43 } catch {
44 // Best-effort deletion
45 }
46 },
47 };
48}