import { Injectable, OnModuleInit } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { createReadStream, promises as fs } from 'fs'; import * as path from 'path'; import type { Readable } from 'stream'; @Injectable() export class LocalFileStorage implements OnModuleInit { private readonly root: string; constructor(private readonly config: ConfigService) { this.root = this.config.get('UPLOAD_DIR')?.trim() || path.join(process.cwd(), 'uploads'); } async onModuleInit() { await fs.mkdir(this.root, { recursive: true }); } absolutePath(storageKey: string): string { const resolved = path.resolve(this.root, storageKey); if (!resolved.startsWith(path.resolve(this.root))) { throw new Error('Invalid storage key'); } return resolved; } async save(storageKey: string, buffer: Buffer): Promise { const abs = this.absolutePath(storageKey); await fs.mkdir(path.dirname(abs), { recursive: true }); await fs.writeFile(abs, buffer); } async remove(storageKey: string): Promise { const abs = this.absolutePath(storageKey); try { await fs.unlink(abs); } catch (err) { const code = (err as NodeJS.ErrnoException).code; if (code !== 'ENOENT') throw err; } } openReadStream(storageKey: string): Readable { return createReadStream(this.absolutePath(storageKey)); } }