fix(storage): route every file op through getStorageBackend()

Removes 12 direct minioClient.{put,get,remove}Object call sites that
bypassed the pluggable storage abstraction.  Filesystem-mode deploys
(MULTI_NODE_DEPLOYMENT=false, storage_backend=filesystem) silently
broke at every site: GDPR export, invoice PDF, EOI generation, portal
download, file upload, folder create/rename/delete, signed PDF land,
maintenance cleanup, etc.  Each site now resolves the active backend
and uses its put/get/delete + the new presignDownloadUrl() helper.

Folder marker objects in /files/folders/* keep the same on-the-wire
shape but route through the backend.  A future refactor should move
folder bookkeeping to a DB-backed virtual-folder table (see audit
HIGH §3 follow-up note in the route file).

Sites left untouched: src/lib/services/system-monitoring.service.ts
and src/app/api/ready/route.ts use minioClient.bucketExists as an S3-
specific health probe — those are correctly mode-aware and stay.

Refs: docs/audit-comprehensive-2026-05-05.md HIGH §3 (auditor-D Issue 1)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Matt Ciaccio
2026-05-05 18:41:02 +02:00
parent 312779c0c5
commit cf430d70c3
12 changed files with 121 additions and 86 deletions

View File

@@ -6,9 +6,8 @@ import { db } from '@/lib/db';
import { formSubmissions } from '@/lib/db/schema/documents';
import { gdprExports } from '@/lib/db/schema/gdpr';
import { aiUsageLedger } from '@/lib/db/schema/ai-usage';
import { env } from '@/lib/env';
import { logger } from '@/lib/logger';
import { minioClient } from '@/lib/minio';
import { getStorageBackend } from '@/lib/storage';
import { QUEUE_CONFIGS } from '@/lib/queue';
/** AI usage rows older than this are deleted by the retention job. */
@@ -87,7 +86,7 @@ export const maintenanceWorker = new Worker(
for (const row of expired) {
try {
if (row.storageKey) {
await minioClient.removeObject(env.MINIO_BUCKET, row.storageKey);
await (await getStorageBackend()).delete(row.storageKey);
}
await db.delete(gdprExports).where(eq(gdprExports.id, row.id));
removed++;