The schema has carried `storageKey` pointers and the migration has written blobs to MinIO since day one, but the API had no S3 client — documents could only be deleted, never uploaded or retrieved. This adds the missing wiring. API - StorageModule/StorageService (@aws-sdk/client-s3, path-style for MinIO): put/getStream/delete, best-effort bucket ensure on boot, gracefully disabled when S3 env is absent (ServiceUnavailable on use). - Reads S3_ENDPOINT/S3_BUCKET + S3_ACCESS_KEY/S3_SECRET_KEY, falling back to MINIO_ROOT_USER/MINIO_ROOT_PASSWORD so one credential set drives both the migration and the API. - Property service documents: POST :id/documents (multipart), GET :id/documents/:childId/download (streamed), delete now also drops the blob. - Policy documents: same upload/download/delete (previously had none). - Keys stay under the service/<id>/… and policy/<id>/… prefixes the migration established. Web - api.ts: shared uploadFile() helper (uploadIngest refactored onto it), upload/download/remove helpers for property & policy documents. - Servicios, polizas, clientes detail pages: real Descargar links and an upload control (gated by policy:update / property:update) replacing the "storage pending" notes. Infra - docker-compose: minio service (9000/9001, healthcheck, named volume) + S3 env wired into the api service. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
33 lines
1.1 KiB
TypeScript
33 lines
1.1 KiB
TypeScript
import { extname } from "node:path";
|
|
|
|
/** Multer file shape we rely on (subset of Express.Multer.File). */
|
|
export interface UploadedFileLike {
|
|
buffer: Buffer;
|
|
originalname?: string;
|
|
mimetype?: string;
|
|
size?: number;
|
|
}
|
|
|
|
const MIME_EXT: Record<string, string> = {
|
|
"application/pdf": ".pdf",
|
|
"image/jpeg": ".jpg",
|
|
"image/png": ".png",
|
|
"image/gif": ".gif",
|
|
"image/tiff": ".tif",
|
|
"image/bmp": ".bmp",
|
|
};
|
|
|
|
/** File extension for a stored blob, from the original name, else the mimetype. */
|
|
export function extForUpload(file: UploadedFileLike): string {
|
|
const fromName = file.originalname ? extname(file.originalname).toLowerCase() : "";
|
|
if (fromName) return fromName;
|
|
return (file.mimetype && MIME_EXT[file.mimetype]) || "";
|
|
}
|
|
|
|
/** Download filename for a stored document, from its key + document type. */
|
|
export function downloadName(storageKey: string, documentType: string): string {
|
|
const ext = extname(storageKey) || "";
|
|
const base = documentType.replace(/[^\w.-]+/g, "_") || "document";
|
|
return base.toLowerCase().endsWith(ext.toLowerCase()) ? base : `${base}${ext}`;
|
|
}
|