import { createHash } from "crypto"; import { promises as fs } from "fs"; import path from "path"; import { AppConfig } from "../../../../shared/infrastructure/config/app-config"; import { DocumentStoragePort, DownloadedBinary, StoredDocumentDescriptor, UploadedBinary } from "../../application/ports/document-storage.port"; export class LocalDocumentStorageService implements DocumentStoragePort { private readonly rootPath: string; constructor(private readonly basePath: string) { this.rootPath = path.resolve(process.cwd(), basePath); } public async upload( tenantId: string, documentId: string, file: UploadedBinary ): Promise { const sanitizedFileName = sanitizeFileName(file.originalFileName); const storageKey = path.posix.join(tenantId, documentId, sanitizedFileName); const fullPath = this.resolveAbsolutePath(storageKey); await fs.mkdir(path.dirname(fullPath), { recursive: true }); await fs.writeFile(fullPath, file.buffer); return { storageProvider: "local", storageKey, checksumSha256: createHash("sha256").update(file.buffer).digest("hex") }; } public async download(_tenantId: string, storageKey: string): Promise { const fullPath = this.resolveAbsolutePath(storageKey); const buffer = await fs.readFile(fullPath); return { buffer }; } public async delete(_tenantId: string, storageKey: string): Promise { const fullPath = this.resolveAbsolutePath(storageKey); await fs.rm(fullPath, { force: true }); } private resolveAbsolutePath(storageKey: string): string { return path.resolve(this.rootPath, storageKey.split("/").join(path.sep)); } } function sanitizeFileName(fileName: string): string { const normalized = fileName.trim().replace(/[^a-zA-Z0-9._-]+/g, "-"); return normalized || "document.bin"; }