Hygiene R140: Datei-Loesch-Helfer konsolidieren + DB-vor-Datei
Pentester-Hygiene zu a6b1dac:
1) Konsolidierung: neuer utils/fileCleanup.ts mit deleteFileAbsolute
(absoluter Pfad, z.B. Multer-Temp) + deleteUploadByRelativePath
(in DB gespeicherter /uploads/-Pfad). Ersetzt die 3x kopierten
deleteFileIfExists/cleanupFile in creditNote-, upload- und
customer-Service.
2) Reihenfolge: In deleteCreditNote/updateCreditNote erst die DB-
Operation, DANN die Datei loeschen. Schlaegt der DB-Schritt fehl,
bleibt die Datei erhalten (kein ins-Leere-zeigender Zustand).
Verifiziert: Update -> pdfPath null + alte Datei weg; Delete -> gibt
geloeschte Row zurueck (Audit) + Datei weg. Kein Regression.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2,26 +2,12 @@
|
||||
// CRUD für Vertrags-Gutschriften (Subventionen: Geld/Sachwert) inkl.
|
||||
// USt-Berechnung (pro Gutschrift wählbar: vatRelevant + Basis Netto/Brutto).
|
||||
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import prisma from '../lib/prisma.js';
|
||||
import { ApiError } from '../utils/apiError.js';
|
||||
import { assignNextNumber } from './creditNoteNumberRange.service.js';
|
||||
import { deleteUploadByRelativePath } from '../utils/fileCleanup.js';
|
||||
import { CreditNoteType, CreditNoteCustomerType, CreditNoteAmountBasis } from '@prisma/client';
|
||||
|
||||
// Löscht eine hochgeladene/erzeugte Datei von der Platte (best effort).
|
||||
function deleteFileIfExists(filePath: string | null) {
|
||||
if (!filePath) return;
|
||||
const absolute = path.join(process.cwd(), filePath);
|
||||
if (fs.existsSync(absolute)) {
|
||||
try {
|
||||
fs.unlinkSync(absolute);
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Löschen der Gutschrift-Datei:', absolute, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const round2 = (n: number) => Math.round((n + Number.EPSILON) * 100) / 100;
|
||||
|
||||
export interface AmountResult {
|
||||
@@ -245,10 +231,11 @@ export async function updateCreditNote(id: number, input: CreateCreditNoteInput)
|
||||
}
|
||||
// Nummer bleibt unverändert (einmal vergeben = fix). Ein evtl. schon
|
||||
// erzeugtes PDF ist nach inhaltlicher Änderung veraltet → Pfad leeren
|
||||
// (der User erzeugt es bei Bedarf neu) und die alte Datei entfernen,
|
||||
// damit sie nicht verwaist liegen bleibt.
|
||||
if (existing.pdfPath) deleteFileIfExists(existing.pdfPath);
|
||||
return prisma.creditNote.update({ where: { id }, data: { ...normalized, pdfPath: null } });
|
||||
// (der User erzeugt es bei Bedarf neu). Reihenfolge (R140): erst DB-Update,
|
||||
// DANN die alte Datei löschen – schlägt das Update fehl, bleibt die Datei.
|
||||
const updated = await prisma.creditNote.update({ where: { id }, data: { ...normalized, pdfPath: null } });
|
||||
deleteUploadByRelativePath(existing.pdfPath);
|
||||
return updated;
|
||||
}
|
||||
|
||||
export async function deleteCreditNote(id: number) {
|
||||
@@ -256,11 +243,13 @@ export async function deleteCreditNote(id: number) {
|
||||
if (!existing) {
|
||||
throw new ApiError(404, 'Gutschrift nicht gefunden');
|
||||
}
|
||||
// Verwaiste Dateien vermeiden: generiertes PDF + Überweisungsbeleg von der
|
||||
// Platte entfernen (Pentest R138-Hinweis).
|
||||
deleteFileIfExists(existing.pdfPath);
|
||||
deleteFileIfExists(existing.receiptPath);
|
||||
return prisma.creditNote.delete({ where: { id } });
|
||||
// Reihenfolge (R140): erst den DB-Datensatz löschen, DANN die Dateien –
|
||||
// schlägt das DB-Delete fehl, bleiben PDF + Beleg erhalten (kein
|
||||
// ins-Leere-zeigender Eintrag).
|
||||
const deleted = await prisma.creditNote.delete({ where: { id } });
|
||||
deleteUploadByRelativePath(existing.pdfPath);
|
||||
deleteUploadByRelativePath(existing.receiptPath);
|
||||
return deleted;
|
||||
}
|
||||
|
||||
// Setzt/aktualisiert den Pfad des hochgeladenen Überweisungsbelegs.
|
||||
|
||||
@@ -2,21 +2,7 @@ import { CustomerType, ContractStatus } from '@prisma/client';
|
||||
import prisma from '../lib/prisma.js';
|
||||
import { generateCustomerNumber, paginate, buildPaginationResponse } from '../utils/helpers.js';
|
||||
import { ApiError } from '../utils/apiError.js';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
// Helper zum Löschen von Dateien
|
||||
function deleteFileIfExists(filePath: string | null) {
|
||||
if (!filePath) return;
|
||||
const absolutePath = path.join(process.cwd(), filePath);
|
||||
if (fs.existsSync(absolutePath)) {
|
||||
try {
|
||||
fs.unlinkSync(absolutePath);
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Löschen der Datei:', absolutePath, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
import { deleteUploadByRelativePath } from '../utils/fileCleanup.js';
|
||||
|
||||
export interface CustomerFilters {
|
||||
search?: string;
|
||||
@@ -205,17 +191,17 @@ export async function deleteCustomer(id: number) {
|
||||
|
||||
// Kundendokumente löschen
|
||||
if (customer) {
|
||||
deleteFileIfExists(customer.businessRegistrationPath);
|
||||
deleteFileIfExists(customer.commercialRegisterPath);
|
||||
deleteFileIfExists(customer.privacyPolicyPath);
|
||||
deleteUploadByRelativePath(customer.businessRegistrationPath);
|
||||
deleteUploadByRelativePath(customer.commercialRegisterPath);
|
||||
deleteUploadByRelativePath(customer.privacyPolicyPath);
|
||||
}
|
||||
|
||||
// Bankkarten- und Ausweisdokumente löschen
|
||||
for (const card of bankCards) {
|
||||
deleteFileIfExists(card.documentPath);
|
||||
deleteUploadByRelativePath(card.documentPath);
|
||||
}
|
||||
for (const doc of identityDocs) {
|
||||
deleteFileIfExists(doc.documentPath);
|
||||
deleteUploadByRelativePath(doc.documentPath);
|
||||
}
|
||||
|
||||
// Jetzt DB-Eintrag löschen (Cascade löscht die verknüpften Einträge)
|
||||
@@ -353,7 +339,7 @@ export async function deleteBankCard(id: number) {
|
||||
// Erst Datei-Pfad holen, dann Datei löschen, dann DB-Eintrag löschen
|
||||
const bankCard = await prisma.bankCard.findUnique({ where: { id } });
|
||||
if (bankCard?.documentPath) {
|
||||
deleteFileIfExists(bankCard.documentPath);
|
||||
deleteUploadByRelativePath(bankCard.documentPath);
|
||||
}
|
||||
return prisma.bankCard.delete({ where: { id } });
|
||||
}
|
||||
@@ -417,7 +403,7 @@ export async function deleteDocument(id: number) {
|
||||
// Erst Datei-Pfad holen, dann Datei löschen, dann DB-Eintrag löschen
|
||||
const document = await prisma.identityDocument.findUnique({ where: { id } });
|
||||
if (document?.documentPath) {
|
||||
deleteFileIfExists(document.documentPath);
|
||||
deleteUploadByRelativePath(document.documentPath);
|
||||
}
|
||||
return prisma.identityDocument.delete({ where: { id } });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user