fix(quick-260922-hk4): fehlende Bilddatei aus der alten data-Spalte wiederherstellen

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) <noreply@anthropic.com>
This commit is contained in:
2026-09-22 15:33:55 +02:00
parent 8cbfb8b69d
commit 82472ee665
2 changed files with 51 additions and 0 deletions
@@ -340,6 +340,37 @@ describe('DashboardImagesService (quick-260921-pi9)', () => {
expect(Buffer.from(result.data).equals(PNG)).toBe(true); 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 () => { it('Test 11: remove — eigenes Bild wird geloescht und { id } geliefert; fremdes (Benutzer ODER Mandant) -> 404 ohne Loeschung', async () => {
const prisma = makeFakePrisma([ const prisma = makeFakePrisma([
makeStoredRow({ id: 'eigen' }), makeStoredRow({ id: 'eigen' }),
@@ -321,6 +321,26 @@ export class DashboardImagesService implements OnApplicationBootstrap {
const data = await fs.readFile(absolute); const data = await fs.readFile(absolute);
return { mimeType: row.mimeType, data }; return { mimeType: row.mimeType, data };
} catch (error) { } 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( this.logger.warn(
`Bilderrahmen-Bild ${id} fehlt im Dateibereich: ${ `Bilderrahmen-Bild ${id} fehlt im Dateibereich: ${
error instanceof Error ? error.message : String(error) error instanceof Error ? error.message : String(error)