mirror of
https://github.com/complexcaresolutions/cms.c2sgmbh.git
synced 2026-03-17 20:54:11 +00:00
- Add automatic cleanup for email-logs (90 days default) - Add automatic cleanup for audit-logs (90 days default) - Add consent-logs archival based on expiresAt (3 years GDPR) - Add media orphan cleanup for unreferenced files (30 days min age) - Add BullMQ-based retention worker with daily scheduler - Add /api/retention endpoint for manual triggers (super-admin only) - Update queue worker to include retention worker - Add comprehensive documentation to CLAUDE.md and TODO.md New files: - src/lib/retention/retention-config.ts - src/lib/retention/cleanup-service.ts - src/lib/retention/index.ts - src/lib/queue/jobs/retention-job.ts - src/lib/queue/workers/retention-worker.ts - src/app/(payload)/api/retention/route.ts 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
121 lines
4.2 KiB
JavaScript
121 lines
4.2 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* Queue Worker Runner
|
|
*
|
|
* Standalone script zum Starten der Queue Workers.
|
|
* Wird von PM2 als separater Prozess gestartet.
|
|
*
|
|
* Usage:
|
|
* npx tsx scripts/run-queue-worker.ts
|
|
* # oder via PM2
|
|
* pm2 start ecosystem.config.cjs --only queue-worker
|
|
*/
|
|
|
|
// Load .env with explicit path BEFORE any other imports
|
|
import { config as dotenvConfig } from 'dotenv'
|
|
import { resolve, dirname } from 'path'
|
|
import { fileURLToPath } from 'url'
|
|
|
|
// ES Module equivalent of __dirname
|
|
const __filename = fileURLToPath(import.meta.url)
|
|
const __dirname = dirname(__filename)
|
|
|
|
// Explicitly load .env from project root (handles PM2 cwd issues)
|
|
const envPath = resolve(__dirname, '..', '.env')
|
|
dotenvConfig({ path: envPath })
|
|
|
|
console.log(`[QueueRunner] Loading env from: ${envPath}`)
|
|
console.log(`[QueueRunner] PAYLOAD_SECRET loaded: ${process.env.PAYLOAD_SECRET ? 'yes' : 'NO!'}`)
|
|
|
|
// Dynamic imports after dotenv is loaded
|
|
async function main() {
|
|
const { startEmailWorker, stopEmailWorker } = await import('../src/lib/queue/workers/email-worker')
|
|
const { startPdfWorker, stopPdfWorker } = await import('../src/lib/queue/workers/pdf-worker')
|
|
const { startRetentionWorker, stopRetentionWorker } = await import('../src/lib/queue/workers/retention-worker')
|
|
const { scheduleRetentionJobs } = await import('../src/lib/queue/jobs/retention-job')
|
|
const { retentionSchedule } = await import('../src/lib/retention/retention-config')
|
|
|
|
// Konfiguration via Umgebungsvariablen
|
|
const ENABLE_EMAIL_WORKER = process.env.QUEUE_ENABLE_EMAIL !== 'false'
|
|
const ENABLE_PDF_WORKER = process.env.QUEUE_ENABLE_PDF !== 'false'
|
|
const ENABLE_RETENTION_WORKER = process.env.QUEUE_ENABLE_RETENTION !== 'false'
|
|
const ENABLE_RETENTION_SCHEDULER = process.env.QUEUE_ENABLE_RETENTION_SCHEDULER !== 'false'
|
|
|
|
console.log('='.repeat(50))
|
|
console.log('[QueueRunner] Starting queue workers...')
|
|
console.log(`[QueueRunner] PID: ${process.pid}`)
|
|
console.log(`[QueueRunner] Node: ${process.version}`)
|
|
console.log(`[QueueRunner] Email Worker: ${ENABLE_EMAIL_WORKER ? 'enabled' : 'disabled'}`)
|
|
console.log(`[QueueRunner] PDF Worker: ${ENABLE_PDF_WORKER ? 'enabled' : 'disabled'}`)
|
|
console.log(`[QueueRunner] Retention Worker: ${ENABLE_RETENTION_WORKER ? 'enabled' : 'disabled'}`)
|
|
console.log(`[QueueRunner] Retention Scheduler: ${ENABLE_RETENTION_SCHEDULER ? 'enabled' : 'disabled'}`)
|
|
console.log('='.repeat(50))
|
|
|
|
// Workers starten
|
|
if (ENABLE_EMAIL_WORKER) {
|
|
startEmailWorker()
|
|
}
|
|
|
|
if (ENABLE_PDF_WORKER) {
|
|
startPdfWorker()
|
|
}
|
|
|
|
if (ENABLE_RETENTION_WORKER) {
|
|
startRetentionWorker()
|
|
|
|
// Retention Scheduler starten (nur wenn Worker aktiv)
|
|
if (ENABLE_RETENTION_SCHEDULER) {
|
|
const cronSchedule = process.env.RETENTION_CRON_SCHEDULE || retentionSchedule.cron
|
|
console.log(`[QueueRunner] Scheduling retention jobs with cron: ${cronSchedule}`)
|
|
await scheduleRetentionJobs(cronSchedule)
|
|
}
|
|
}
|
|
|
|
// Graceful Shutdown
|
|
async function shutdown(signal: string) {
|
|
console.log(`\n[QueueRunner] Received ${signal}, shutting down gracefully...`)
|
|
|
|
try {
|
|
const stopPromises: Promise<void>[] = []
|
|
|
|
if (ENABLE_EMAIL_WORKER) {
|
|
stopPromises.push(stopEmailWorker())
|
|
}
|
|
if (ENABLE_PDF_WORKER) {
|
|
stopPromises.push(stopPdfWorker())
|
|
}
|
|
if (ENABLE_RETENTION_WORKER) {
|
|
stopPromises.push(stopRetentionWorker())
|
|
}
|
|
|
|
await Promise.all(stopPromises)
|
|
console.log('[QueueRunner] All workers stopped')
|
|
process.exit(0)
|
|
} catch (error) {
|
|
console.error('[QueueRunner] Error during shutdown:', error)
|
|
process.exit(1)
|
|
}
|
|
}
|
|
|
|
// Signal Handlers
|
|
process.on('SIGTERM', () => shutdown('SIGTERM'))
|
|
process.on('SIGINT', () => shutdown('SIGINT'))
|
|
|
|
// Unhandled Errors
|
|
process.on('unhandledRejection', (reason) => {
|
|
console.error('[QueueRunner] Unhandled Rejection:', reason)
|
|
})
|
|
|
|
process.on('uncaughtException', (error) => {
|
|
console.error('[QueueRunner] Uncaught Exception:', error)
|
|
shutdown('uncaughtException')
|
|
})
|
|
|
|
// Keep process alive
|
|
console.log('[QueueRunner] Workers started, waiting for jobs...')
|
|
}
|
|
|
|
main().catch((error) => {
|
|
console.error('[QueueRunner] Fatal error:', error)
|
|
process.exit(1)
|
|
})
|