Anlagen: user_id aus req.user statt AsyncLocalStorage im multer-Callback

In multer/busboy-Upload-Callbacks ist der per-request AsyncLocalStorage-
Kontext nicht zuverlässig propagiert, sodass currentUserId() dort null
liefern kann und basis_anhaenge-/interne_anhaenge-Zeilen mit user_id=NULL
verwaisten (für den User unsichtbar). Die INSERTs nutzen jetzt uidFromReq
(= req.user.id mit uid()-Fallback) wie bereits userStorageDirForReq für
Signatur/Foto. Zwei bestehende verwaiste Zeilen wurden repariert.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-07-14 16:49:16 +02:00
co-authored by Claude
parent ce844412e5
commit 9a40f395c7
+18 -6
View File
@@ -1639,6 +1639,15 @@ initializeDatabase().then(async () => {
// to be present inside any protected route or background-per-user task.
const uid = () => currentUserId();
// Inside a multer upload callback the per-request AsyncLocalStorage context
// (which uid() reads) is not reliably propagated — busboy drives the multipart
// stream from outside the userContext.run(...) scope, so currentUserId() can
// come back null there. The request object, by contrast, always carries the
// resolved user (set by the auth middleware before the route). Use this for
// any INSERT done from within an uploadX(req, res, cb) callback, so the row is
// not orphaned with user_id = NULL. Mirrors userStorageDirForReq's reasoning.
const uidFromReq = (req) => (req && req.user && req.user.id) || currentUserId();
// Resolve a per-user storage directory (data/<base>/<userId>/), creating it
// on first use. Used for every attachment / signature / photo path so users'
// files are isolated on disk the same way their DB rows are.
@@ -3206,7 +3215,7 @@ initializeDatabase().then(async () => {
: sanitizeInput(original.replace(/\.[^.]+$/, ''));
await dbRun(
'INSERT INTO basis_anhaenge (user_id, name, dateiname, mime, pfad) VALUES (?, ?, ?, ?, ?)',
[uid(), name, sanitizeInput(original), req.file.mimetype || 'application/octet-stream', req.file.filename]
[uidFromReq(req), name, sanitizeInput(original), req.file.mimetype || 'application/octet-stream', req.file.filename]
);
}
res.redirect('/vorlagen');
@@ -3262,8 +3271,10 @@ initializeDatabase().then(async () => {
try {
if (err) console.error('Signature upload error:', err.message);
if (req.file) {
// keep only the newly uploaded file (in the user's subdir)
const dir = userStorageDir(signaturDir);
// keep only the newly uploaded file (in the user's subdir). Resolve the
// dir from req.user (not currentUserId()) — this runs in the multer
// callback where the AsyncLocalStorage user context can be absent.
const dir = userStorageDirForReq(signaturDir, req);
fs.readdirSync(dir).forEach((f) => {
if (f !== req.file.filename) fs.promises.unlink(path.join(dir, f)).catch(() => {});
});
@@ -3300,8 +3311,9 @@ initializeDatabase().then(async () => {
try {
if (err) console.error('Photo upload error:', err.message);
if (req.file) {
// keep only the newly uploaded file (in the user's subdir)
const dir = userStorageDir(fotoDir);
// keep only the newly uploaded file (in the user's subdir). Resolve the
// dir from req.user — see /unterschrift handler for why (multer callback).
const dir = userStorageDirForReq(fotoDir, req);
fs.readdirSync(dir).forEach((f) => {
if (f !== req.file.filename) fs.promises.unlink(path.join(dir, f)).catch(() => {});
});
@@ -3374,7 +3386,7 @@ initializeDatabase().then(async () => {
: sanitizeInput(original.replace(/\.[^.]+$/, ''));
await dbRun(
'INSERT INTO interne_anhaenge (user_id, bewerbung_id, name, dateiname, mime, pfad) VALUES (?, ?, ?, ?, ?, ?)',
[uid(), id, name, sanitizeInput(original), req.file.mimetype || 'application/octet-stream', req.file.filename]
[uidFromReq(req), id, name, sanitizeInput(original), req.file.mimetype || 'application/octet-stream', req.file.filename]
);
}
res.redirect('/bewerbung/' + id);