/** * Component: Notification Backend Individual API * Documentation: documentation/backend/services/notifications.md */ import { NextRequest, NextResponse } from 'next/server'; import { requireAuth, requireAdmin, AuthenticatedRequest } from '@/lib/middleware/auth'; import { prisma } from '@/lib/db'; import { getNotificationService, NotificationBackendType } from '@/lib/services/notification.service'; import { RMABLogger } from '@/lib/utils/logger'; import { z } from 'zod'; const logger = RMABLogger.create('API.Admin.Notifications.Id'); const UpdateBackendSchema = z.object({ name: z.string().min(1).optional(), config: z.record(z.any()).optional(), events: z.array(z.enum(['request_pending_approval', 'request_approved', 'request_available', 'request_error'])).min(1).optional(), enabled: z.boolean().optional(), }); /** * GET /api/admin/notifications/[id] * Get single notification backend (sensitive values masked) */ export async function GET( request: NextRequest, { params }: { params: Promise<{ id: string }> } ) { return requireAuth(request, async (req: AuthenticatedRequest) => { return requireAdmin(req, async () => { try { const { id } = await params; const backend = await prisma.notificationBackend.findUnique({ where: { id }, }); if (!backend) { return NextResponse.json( { error: 'NotFound', message: 'Notification backend not found' }, { status: 404 } ); } const notificationService = getNotificationService(); // Mask sensitive config values return NextResponse.json({ success: true, backend: { ...backend, config: notificationService.maskConfig(backend.type as NotificationBackendType, backend.config), }, }); } catch (error) { logger.error('Failed to fetch notification backend', { error: error instanceof Error ? error.message : String(error), }); return NextResponse.json( { error: 'FetchError', message: 'Failed to fetch notification backend', }, { status: 500 } ); } }); }); } /** * PUT /api/admin/notifications/[id] * Update notification backend */ export async function PUT( request: NextRequest, { params }: { params: Promise<{ id: string }> } ) { return requireAuth(request, async (req: AuthenticatedRequest) => { return requireAdmin(req, async () => { try { const { id } = await params; const body = await request.json(); const updates = UpdateBackendSchema.parse(body); // Get existing backend const existing = await prisma.notificationBackend.findUnique({ where: { id }, }); if (!existing) { return NextResponse.json( { error: 'NotFound', message: 'Notification backend not found' }, { status: 404 } ); } const notificationService = getNotificationService(); // Handle config updates (preserve masked values, encrypt new values) let finalConfig = existing.config; if (updates.config) { const existingConfig = existing.config as any; const updatedConfig = updates.config as any; // Check if masked values need to be preserved Object.keys(updatedConfig).forEach((key) => { if (updatedConfig[key] === '••••••••') { // Preserve existing encrypted value updatedConfig[key] = existingConfig[key]; } }); // Encrypt new/changed values finalConfig = notificationService.encryptConfig(existing.type as NotificationBackendType, updatedConfig); } // Update backend const updateData: any = {}; if (updates.name) updateData.name = updates.name; if (updates.config) updateData.config = finalConfig; if (updates.events) updateData.events = updates.events; if (updates.enabled !== undefined) updateData.enabled = updates.enabled; const updated = await prisma.notificationBackend.update({ where: { id }, data: updateData, }); logger.info(`Updated notification backend: ${updated.name}`, { backendId: id, adminId: req.user?.sub, }); // Return with masked values return NextResponse.json({ success: true, backend: { ...updated, config: notificationService.maskConfig(updated.type as NotificationBackendType, updated.config), }, }); } catch (error) { logger.error('Failed to update notification backend', { error: error instanceof Error ? error.message : String(error), }); if (error instanceof z.ZodError) { return NextResponse.json( { error: 'ValidationError', details: error.errors, }, { status: 400 } ); } return NextResponse.json( { error: 'UpdateError', message: 'Failed to update notification backend', }, { status: 500 } ); } }); }); } /** * DELETE /api/admin/notifications/[id] * Delete notification backend */ export async function DELETE( request: NextRequest, { params }: { params: Promise<{ id: string }> } ) { return requireAuth(request, async (req: AuthenticatedRequest) => { return requireAdmin(req, async () => { try { const { id } = await params; // Check if backend exists const backend = await prisma.notificationBackend.findUnique({ where: { id }, }); if (!backend) { return NextResponse.json( { error: 'NotFound', message: 'Notification backend not found' }, { status: 404 } ); } // Delete backend await prisma.notificationBackend.delete({ where: { id }, }); logger.info(`Deleted notification backend: ${backend.name}`, { backendId: id, adminId: req.user?.sub, }); return NextResponse.json({ success: true, message: 'Notification backend deleted', }); } catch (error) { logger.error('Failed to delete notification backend', { error: error instanceof Error ? error.message : String(error), }); return NextResponse.json( { error: 'DeleteError', message: 'Failed to delete notification backend', }, { status: 500 } ); } }); }); }