gemtrack-backend/src/modules/document-management/infrastructure/storage/local-document-storage.serv...

55 lines
1.8 KiB
TypeScript

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<StoredDocumentDescriptor> {
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<DownloadedBinary> {
const fullPath = this.resolveAbsolutePath(storageKey);
const buffer = await fs.readFile(fullPath);
return { buffer };
}
public async delete(_tenantId: string, storageKey: string): Promise<void> {
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";
}