mirror of
https://github.com/complexcaresolutions/cms.c2sgmbh.git
synced 2026-03-17 17:24:12 +00:00
- 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>
179 lines
5 KiB
TypeScript
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 },
|
|
)
|
|
}
|
|
}
|