From 82472ee6659128c28648f6ab24ee97a020ea38a0 Mon Sep 17 00:00:00 2001 From: Schalli Date: Tue, 22 Sep 2026 15:33:55 +0200 Subject: [PATCH] fix(quick-260922-hk4): fehlende Bilddatei aus der alten data-Spalte wiederherstellen MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Beim Rundgang aufgefallen: nach dem Umzug zeigt eine Zeile auf eine Datei, die es auf diesem Server nicht gibt — lokal, weil der Umzug am Host lief und der Container ein eigenes Volume hat. Derselbe Zustand entsteht im Betrieb, wenn jemand einen `pg_dump` von vor dem Umzug zurueckspielt: die Bytes stecken noch in der Spalte `data`, das getrennt gesicherte Volume `user-files` ist aber leer. `getBytes` schreibt die Datei in diesem Fall aus `data` neu und liefert sie aus, statt 404 zu melden. Fehlt beides, bleibt es bei 404. Nach dem Entfernen der Spalte (eigenes Todo) faellt der Zweig ersatzlos weg. api-Tests 1186 -> 1188, type-check und lint unveraendert gruen. Co-Authored-By: Claude Opus 5 (1M context) --- .../dashboard-images.service.spec.ts | 31 +++++++++++++++++++ .../src/dashboard/dashboard-images.service.ts | 20 ++++++++++++ 2 files changed, 51 insertions(+) diff --git a/apps/api/src/dashboard/dashboard-images.service.spec.ts b/apps/api/src/dashboard/dashboard-images.service.spec.ts index 0de835d..16dcc70 100644 --- a/apps/api/src/dashboard/dashboard-images.service.spec.ts +++ b/apps/api/src/dashboard/dashboard-images.service.spec.ts @@ -340,6 +340,37 @@ describe('DashboardImagesService (quick-260921-pi9)', () => { expect(Buffer.from(result.data).equals(PNG)).toBe(true); }); + it('Test 10b: getBytes — Datei fehlt, aber die alte Spalte `data` traegt die Bytes noch: wiederherstellen statt 404', async () => { + // Fall aus dem Browser-Rundgang 22.09.2026: ein `pg_dump` von vor dem Umzug + // traegt die Bytes noch, das Volume `user-files` wird getrennt gesichert — + // wer nur den Abzug zurueckspielt, haette sonst Zeilen ohne Datei. + const row = makeRow({ + id: 'img-alt', + data: PNG, + storagePath: 'user-files/dashboard-images/user-1/img-alt.png', + }); + const prisma = makeFakePrisma([row]); + expect(fs.existsSync(storedFile('user-1', 'img-alt'))).toBe(false); + + const result = await makeService(prisma).getBytes('img-alt', 'user-1', 'tenant-1'); + + expect(Buffer.from(result.data).equals(PNG)).toBe(true); + expect(fs.existsSync(storedFile('user-1', 'img-alt'))).toBe(true); + expect(fs.readFileSync(storedFile('user-1', 'img-alt')).equals(PNG)).toBe(true); + }); + + it('Test 10c: getBytes — Datei fehlt UND `data` ist leer -> 404', async () => { + const row = makeRow({ + id: 'img-weg', + data: null, + storagePath: 'user-files/dashboard-images/user-1/img-weg.png', + }); + const prisma = makeFakePrisma([row]); + await expect(makeService(prisma).getBytes('img-weg', 'user-1', 'tenant-1')).rejects.toThrow( + NotFoundException, + ); + }); + it('Test 11: remove — eigenes Bild wird geloescht und { id } geliefert; fremdes (Benutzer ODER Mandant) -> 404 ohne Loeschung', async () => { const prisma = makeFakePrisma([ makeStoredRow({ id: 'eigen' }), diff --git a/apps/api/src/dashboard/dashboard-images.service.ts b/apps/api/src/dashboard/dashboard-images.service.ts index 6461599..757d306 100644 --- a/apps/api/src/dashboard/dashboard-images.service.ts +++ b/apps/api/src/dashboard/dashboard-images.service.ts @@ -321,6 +321,26 @@ export class DashboardImagesService implements OnApplicationBootstrap { const data = await fs.readFile(absolute); return { mimeType: row.mimeType, data }; } catch (error) { + // Selbstheilung waehrend der Umstellung (T-HK4-03): fehlt die Datei, + // steckt aber noch die alte Spalte `data` in der Zeile, wird die Datei + // daraus neu geschrieben und ausgeliefert. Der Fall ist real: ein + // `pg_dump` aus der Zeit vor dem Umzug traegt die Bytes noch, das + // Volume `user-files` wird getrennt gesichert — wer nur den Abzug + // zurueckspielt, haette sonst Zeilen ohne Datei. Nach dem Entfernen der + // Spalte (eigenes Todo) faellt dieser Zweig ersatzlos weg. + if (row.data !== null) { + try { + await this.writeImageFile(row.userId, row.id, row.mimeType, row.data); + this.logger.log(`Bilderrahmen-Bild ${id} aus der Datenbank wiederhergestellt`); + return { mimeType: row.mimeType, data: row.data }; + } catch (writeError) { + this.logger.error( + `Bilderrahmen-Bild ${id} konnte nicht wiederhergestellt werden: ${ + writeError instanceof Error ? writeError.message : String(writeError) + }`, + ); + } + } this.logger.warn( `Bilderrahmen-Bild ${id} fehlt im Dateibereich: ${ error instanceof Error ? error.message : String(error)