cms.c2sgmbh/src/app/(payload)/api/admin/queues/route.ts
Martin Porwoll ce4962e74b feat: BullMQ queue system for email and PDF processing
- Add BullMQ-based job queue with Redis backend
- Implement email worker with tenant-specific SMTP support
- Add PDF worker with Playwright for HTML/URL-to-PDF generation
- Create /api/generate-pdf endpoint with job status polling
- Fix TypeScript errors in Tenants, TenantBreadcrumb, TenantDashboard
- Fix type casts in auditAuthEvents and audit-service
- Remove credentials from ecosystem.config.cjs (now loaded via dotenv)
- Fix ESM __dirname issue with fileURLToPath for PM2 compatibility

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-09 22:59:17 +00:00

179 lines
5 KiB
TypeScript

/**
* Queue Admin API
*
* Admin-Endpunkt für Queue-Monitoring und -Verwaltung.
* Nur für Super-Admins zugänglich.
*/
import { getPayload } from 'payload'
import config from '@payload-config'
import { NextRequest, NextResponse } from 'next/server'
import {
getQueuesStatus,
isQueueAvailable,
getQueue,
QUEUE_NAMES,
type QueueStatus,
} from '@/lib/queue'
import { createSafeLogger } from '@/lib/security'
const logger = createSafeLogger('API:AdminQueues')
interface UserWithAdmin {
id: number
isSuperAdmin?: boolean
}
/**
* GET /api/admin/queues
*
* Gibt den Status aller Queues zurück.
* Nur für Super-Admins zugänglich.
*/
export async function GET(req: NextRequest) {
try {
const payload = await getPayload({ config })
// Authentifizierung prüfen
const { user } = await payload.auth({ headers: req.headers })
if (!user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
const typedUser = user as UserWithAdmin
// Nur Super-Admins haben Zugriff
if (!typedUser.isSuperAdmin) {
return NextResponse.json(
{ error: 'Forbidden - Super admin access required' },
{ status: 403 },
)
}
// Queue-Verfügbarkeit prüfen
const available = await isQueueAvailable()
if (!available) {
return NextResponse.json(
{
available: false,
error: 'Queue system unavailable (Redis connection failed)',
queues: [],
},
{ status: 503 },
)
}
// Queue-Status abrufen
const statuses = await getQueuesStatus()
return NextResponse.json({
available: true,
timestamp: new Date().toISOString(),
queues: statuses,
summary: {
totalWaiting: statuses.reduce((sum: number, q: QueueStatus) => sum + q.waiting, 0),
totalActive: statuses.reduce((sum: number, q: QueueStatus) => sum + q.active, 0),
totalCompleted: statuses.reduce((sum: number, q: QueueStatus) => sum + q.completed, 0),
totalFailed: statuses.reduce((sum: number, q: QueueStatus) => sum + q.failed, 0),
totalDelayed: statuses.reduce((sum: number, q: QueueStatus) => sum + q.delayed, 0),
},
})
} catch (error) {
logger.error('admin-queues error', error)
return NextResponse.json(
{ error: error instanceof Error ? error.message : 'Unknown error' },
{ status: 500 },
)
}
}
/**
* POST /api/admin/queues
*
* Queue-Aktionen ausführen (pause, resume, clean, etc.)
* Nur für Super-Admins zugänglich.
*/
export async function POST(req: NextRequest) {
try {
const payload = await getPayload({ config })
// Authentifizierung prüfen
const { user } = await payload.auth({ headers: req.headers })
if (!user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
const typedUser = user as UserWithAdmin
// Nur Super-Admins haben Zugriff
if (!typedUser.isSuperAdmin) {
return NextResponse.json(
{ error: 'Forbidden - Super admin access required' },
{ status: 403 },
)
}
const body = await req.json()
const { action, queueName } = body
if (!action || !queueName) {
return NextResponse.json(
{ error: 'Missing required fields: action, queueName' },
{ status: 400 },
)
}
// Queue-Namen validieren
const validQueues = Object.values(QUEUE_NAMES)
if (!validQueues.includes(queueName)) {
return NextResponse.json(
{ error: `Invalid queue name. Valid: ${validQueues.join(', ')}` },
{ status: 400 },
)
}
const queue = getQueue(queueName)
switch (action) {
case 'pause':
await queue.pause()
logger.info(`Queue ${queueName} paused by user ${typedUser.id}`)
return NextResponse.json({ success: true, message: `Queue ${queueName} paused` })
case 'resume':
await queue.resume()
logger.info(`Queue ${queueName} resumed by user ${typedUser.id}`)
return NextResponse.json({ success: true, message: `Queue ${queueName} resumed` })
case 'clean':
const { status = 'completed', grace = 0 } = body
const cleanedCount = await queue.clean(grace, 1000, status)
logger.info(`Queue ${queueName} cleaned (${status}): ${cleanedCount.length} jobs`)
return NextResponse.json({
success: true,
message: `Cleaned ${cleanedCount.length} ${status} jobs from ${queueName}`,
count: cleanedCount.length,
})
case 'drain':
await queue.drain()
logger.info(`Queue ${queueName} drained by user ${typedUser.id}`)
return NextResponse.json({ success: true, message: `Queue ${queueName} drained` })
default:
return NextResponse.json(
{ error: `Unknown action: ${action}. Valid: pause, resume, clean, drain` },
{ status: 400 },
)
}
} catch (error) {
logger.error('admin-queues action error', error)
return NextResponse.json(
{ error: error instanceof Error ? error.message : 'Unknown error' },
{ status: 500 },
)
}
}