/** * GET /api/admin/integrations — list every integration_settings row * PATCH /api/admin/integrations — body: { key, disabled, reason? } * * Backed by the `integration_settings` table (migration 081). Toggling * here takes effect within the 5-minute health cache without restarting * the container. Forces a cache refresh on PATCH so the UI reflects the * change immediately. */ import { NextRequest, NextResponse } from 'next/server'; import { requirePermission } from '@/lib/auth-utils'; import postgresClient from '@/lib/services/postgres-client'; import { clearIntegrationHealthCache } from '@/lib/services/integration-health'; interface IntegrationSettingRow { key: string; disabled: boolean; disabled_reason: string | null; disabled_by: string | null; disabled_at: string | null; updated_at: string; } interface IntegrationSetting { key: string; disabled: boolean; reason: string | null; disabledBy: string | null; disabledAt: string | null; updatedAt: string; } function rowToDto(r: IntegrationSettingRow): IntegrationSetting { return { key: r.key, disabled: r.disabled, reason: r.disabled_reason, disabledBy: r.disabled_by, disabledAt: r.disabled_at, updatedAt: r.updated_at, }; } export async function GET() { const { error } = await requirePermission('admin', 'access'); if (error) return error; const res = await postgresClient.query( `SELECT key, disabled, disabled_reason, disabled_by, disabled_at::text, updated_at::text FROM integration_settings ORDER BY key`, ); return NextResponse.json({ items: res.rows.map(rowToDto) }); } export async function PATCH(request: NextRequest) { const { session, error } = await requirePermission('admin', 'access'); if (error) return error; let body: { key?: unknown; disabled?: unknown; reason?: unknown }; try { body = (await request.json()) as typeof body; } catch { return NextResponse.json({ error: 'Invalid JSON body' }, { status: 400 }); } const key = typeof body.key === 'string' ? body.key.trim() : ''; const disabled = body.disabled === true; const reason = typeof body.reason === 'string' && body.reason.trim().length > 0 ? body.reason.trim().slice(0, 500) : null; if (!key) { return NextResponse.json({ error: '`key` is required' }, { status: 400 }); } const actor = (session?.user as { email?: string } | undefined)?.email ?? null; const res = await postgresClient.query( `INSERT INTO integration_settings (key, disabled, disabled_reason, disabled_by, disabled_at, updated_at) VALUES ($1, $2, $3, $4, CASE WHEN $2 THEN NOW() ELSE NULL END, NOW()) ON CONFLICT (key) DO UPDATE SET disabled = EXCLUDED.disabled, disabled_reason = CASE WHEN EXCLUDED.disabled THEN EXCLUDED.disabled_reason ELSE NULL END, disabled_by = CASE WHEN EXCLUDED.disabled THEN EXCLUDED.disabled_by ELSE NULL END, disabled_at = CASE WHEN EXCLUDED.disabled THEN NOW() ELSE NULL END, updated_at = NOW() RETURNING key, disabled, disabled_reason, disabled_by, disabled_at::text, updated_at::text`, [key, disabled, reason, disabled ? actor : null], ); // Force the next /api/dashboard/integration-health request to re-check. clearIntegrationHealthCache(); return NextResponse.json({ item: rowToDto(res.rows[0]) }); }