import { createReadStream } from 'node:fs'; import { mkdir, rm, writeFile } from 'node:fs/promises'; import { join } from 'node:path'; import type { Readable } from 'node:stream'; import { Injectable } from '@nestjs/common'; import { AppConfig } from '../config/app-config.service'; /** * Filesystem binding for uploaded files (ADR 0011, issue #27): opaque * layout `//`, original filenames and metadata * live in the database, not on disk. Kept behind this interface so an S3 * binding stays possible later without touching callers. */ @Injectable() export class FileStorageService { constructor(private readonly config: AppConfig) {} private pathFor(pondId: string, fileId: string): string { return join(this.config.env.UPLOADS_DIR, pondId, fileId); } async save(pondId: string, fileId: string, data: Buffer): Promise { await mkdir(join(this.config.env.UPLOADS_DIR, pondId), { recursive: true }); await writeFile(this.pathFor(pondId, fileId), data); } createReadStream(pondId: string, fileId: string): Readable { return createReadStream(this.pathFor(pondId, fileId)); } /** Idempotent — removing an already-absent file is not an error. */ async delete(pondId: string, fileId: string): Promise { await rm(this.pathFor(pondId, fileId), { force: true }); } }