fix: prevent backend crash on archive when admin_email is null (#318)
Archiving an event with no admin_email queued an email_queue row with
recipient_email=null, violating the NOT NULL constraint. The error was
thrown inside the output.on('close') callback (detached from the caller),
becoming an unhandled rejection that crashed Node and dropped admin
sessions on bulk archive.
- Skip queueEmail when event.admin_email is null/empty (admin_email has
been nullable since migration 073).
- Wrap the close handler in try/catch so any post-archive failure logs
instead of crashing the process.
This commit is contained in:
@@ -61,32 +61,43 @@ async function archiveEvent(event) {
|
||||
}
|
||||
|
||||
output.on('close', async () => {
|
||||
logger.info(`Archive created: ${archiveName} (${archive.pointer()} bytes)`);
|
||||
try {
|
||||
logger.info(`Archive created: ${archiveName} (${archive.pointer()} bytes)`);
|
||||
|
||||
// Update database
|
||||
await db('events').where('id', event.id).update({
|
||||
is_archived: true,
|
||||
archive_path: path.relative(getStoragePath(), archivePath),
|
||||
archived_at: new Date()
|
||||
});
|
||||
// Update database
|
||||
await db('events').where('id', event.id).update({
|
||||
is_archived: true,
|
||||
archive_path: path.relative(getStoragePath(), archivePath),
|
||||
archived_at: new Date()
|
||||
});
|
||||
|
||||
// Delete original files
|
||||
await fs.rm(eventPath, { recursive: true });
|
||||
// Delete original files
|
||||
await fs.rm(eventPath, { recursive: true });
|
||||
|
||||
// Delete thumbnails
|
||||
const photos = await db('photos').where('event_id', event.id);
|
||||
for (const photo of photos) {
|
||||
if (photo.thumbnail_path) {
|
||||
const thumbPath = path.join(getStoragePath(), photo.thumbnail_path);
|
||||
await fs.unlink(thumbPath).catch(() => {}); // Ignore if already deleted
|
||||
// Delete thumbnails
|
||||
const photos = await db('photos').where('event_id', event.id);
|
||||
for (const photo of photos) {
|
||||
if (photo.thumbnail_path) {
|
||||
const thumbPath = path.join(getStoragePath(), photo.thumbnail_path);
|
||||
await fs.unlink(thumbPath).catch(() => {}); // Ignore if already deleted
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Queue completion email
|
||||
await queueEmail(event.id, event.admin_email, 'archive_complete', {
|
||||
event_name: event.event_name,
|
||||
archive_size: (archive.pointer() / 1024 / 1024).toFixed(2) + ' MB'
|
||||
});
|
||||
// Queue completion email — admin_email is nullable on events (migration 073);
|
||||
// skip queueing rather than violating email_queue.recipient_email NOT NULL.
|
||||
if (event.admin_email) {
|
||||
await queueEmail(event.id, event.admin_email, 'archive_complete', {
|
||||
event_name: event.event_name,
|
||||
archive_size: (archive.pointer() / 1024 / 1024).toFixed(2) + ' MB'
|
||||
});
|
||||
} else {
|
||||
logger.info(`Skipping archive_complete email for event ${event.slug}: no admin_email set`);
|
||||
}
|
||||
} catch (err) {
|
||||
// Never let the close handler reject — it runs detached from the caller,
|
||||
// and an unhandled rejection here crashes the backend process.
|
||||
logger.error(`Post-archive cleanup failed for event ${event.slug}:`, err);
|
||||
}
|
||||
});
|
||||
|
||||
archive.pipe(output);
|
||||
|
||||
Reference in New Issue
Block a user