55 lines
1.8 KiB
TypeScript
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";
|
|
}
|