import { NextResponse, type NextRequest } from 'next/server'; import { requireAdminApi } from '@/lib/auth/admin-guard'; import { getSupabaseAdmin } from '@/lib/supabase/admin'; import { logAdminAction } from '@/lib/auth/audit'; import { isUuid, parseBoolean } from '@/lib/admin/validators'; import { redisSet } from '@/lib/redis'; export const runtime = 'nodejs'; export const dynamic = 'force-dynamic'; export async function POST( req: NextRequest, { params }: { params: { id: string } }, ) { const auth = await requireAdminApi(); if (!auth.ok) return auth.response; const { id } = params; if (!isUuid(id)) { return NextResponse.json({ error: 'invalid tunnel id' }, { status: 400 }); } let body: { is_active?: unknown }; try { body = (await req.json()) as { is_active?: unknown }; } catch { return NextResponse.json({ error: 'invalid json' }, { status: 400 }); } const isActive = parseBoolean(body.is_active); if (isActive === null) { return NextResponse.json( { error: 'is_active must be a boolean' }, { status: 400 }, ); } const admin = getSupabaseAdmin(); const { data, error } = await admin .from('tunnels') .update({ is_active: isActive }) .eq('user_id', id) .select('subdomain') .maybeSingle<{ subdomain: string }>(); if (error) { return NextResponse.json({ error: error.message }, { status: 500 }); } if (!data) { return NextResponse.json({ error: 'tunnel not found' }, { status: 404 }); } // Best-effort live kill-switch (never throws). await redisSet(`tunnel:active:${data.subdomain}`, isActive ? '1' : '0'); await logAdminAction(auth.user, { action: isActive ? 'tunnel.activate' : 'tunnel.deactivate', target_type: 'tunnel', target_id: id, details: { subdomain: data.subdomain, is_active: isActive }, }); return NextResponse.json({ ok: true, is_active: isActive }); }