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:
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user