import { NextRequest, NextResponse } from 'next/server'; import { createClient } from '@/lib/supabase/server'; import { INTEGRATION_PROVIDERS, isAgencyRole, serviceHeaders, supabaseUrl, userRole, } from '@/lib/agency'; export const dynamic = 'force-dynamic'; async function requireAgency() { const supabase = await createClient(); const { data: { user }, } = await supabase.auth.getUser(); if (!user) return { error: NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) }; if (!isAgencyRole(userRole(user))) { return { error: NextResponse.json({ error: 'Forbidden' }, { status: 403 }) }; } const headers = serviceHeaders(); if (!headers) { return { error: NextResponse.json( { error: 'Falta SUPABASE_SERVICE_ROLE_KEY' }, { status: 503 } ), }; } return { user, headers }; } /** GET ?tenantId= — integrations for one tenant (seed missing providers). */ export async function GET(req: NextRequest) { const gate = await requireAgency(); if ('error' in gate && gate.error) return gate.error; const { headers } = gate as { headers: { apikey: string; Authorization: string } }; const tenantId = req.nextUrl.searchParams.get('tenantId'); if (!tenantId) { return NextResponse.json({ error: 'tenantId requerido' }, { status: 400 }); } const base = supabaseUrl(); const listRes = await fetch( `${base}/rest/v1/integrations?tenant_id=eq.${encodeURIComponent(tenantId)}&select=*`, { headers, cache: 'no-store' } ); if (!listRes.ok) { const msg = await listRes.text(); return NextResponse.json( { integrations: [], providers: INTEGRATION_PROVIDERS, warning: msg.includes('does not exist') ? 'Aplicar sql/agency_portfolio.sql' : msg.slice(0, 200), }, { status: 200 } ); } let integrations = await listRes.json(); const have = new Set( (integrations as { provider: string }[]).map((i) => i.provider) ); const missing = INTEGRATION_PROVIDERS.filter((p) => !have.has(p.id)); if (missing.length) { const rows = missing.map((p) => ({ tenant_id: tenantId, provider: p.id, status: 'disconnected', label: p.label, })); await fetch(`${base}/rest/v1/integrations`, { method: 'POST', headers: { ...headers, 'Content-Type': 'application/json', Prefer: 'return=representation' }, body: JSON.stringify(rows), }); const again = await fetch( `${base}/rest/v1/integrations?tenant_id=eq.${encodeURIComponent(tenantId)}&select=*`, { headers, cache: 'no-store' } ); if (again.ok) integrations = await again.json(); } return NextResponse.json({ integrations, providers: INTEGRATION_PROVIDERS, }); } /** PATCH — update one integration status/account (manual connect for now). */ export async function PATCH(req: NextRequest) { const gate = await requireAgency(); if ('error' in gate && gate.error) return gate.error; const { headers } = gate as { headers: { apikey: string; Authorization: string } }; const body = await req.json().catch(() => ({})); const tenantId = typeof body.tenantId === 'string' ? body.tenantId.trim() : ''; const provider = typeof body.provider === 'string' ? body.provider.trim() : ''; const status = typeof body.status === 'string' ? body.status.trim() : ''; const externalAccountId = typeof body.externalAccountId === 'string' ? body.externalAccountId.trim() : undefined; const label = typeof body.label === 'string' ? body.label.trim() : undefined; const errorMessage = typeof body.errorMessage === 'string' ? body.errorMessage.trim() : undefined; if (!tenantId || !provider || !status) { return NextResponse.json( { error: 'tenantId, provider y status requeridos' }, { status: 400 } ); } if ( !['disconnected', 'pending', 'connected', 'error'].includes(status) ) { return NextResponse.json({ error: 'status inválido' }, { status: 400 }); } const patch: Record = { status, updated_at: new Date().toISOString(), }; if (externalAccountId !== undefined) { patch.external_account_id = externalAccountId || null; } if (label !== undefined) patch.label = label || null; if (errorMessage !== undefined) patch.error_message = errorMessage || null; if (status === 'connected') { patch.last_sync_at = new Date().toISOString(); patch.error_message = null; } const base = supabaseUrl(); const url = `${base}/rest/v1/integrations?tenant_id=eq.${encodeURIComponent(tenantId)}&provider=eq.${encodeURIComponent(provider)}`; const r = await fetch(url, { method: 'PATCH', headers: { ...headers, 'Content-Type': 'application/json', Prefer: 'return=representation', }, body: JSON.stringify(patch), }); if (!r.ok) { // upsert if missing const ins = await fetch(`${base}/rest/v1/integrations`, { method: 'POST', headers: { ...headers, 'Content-Type': 'application/json', Prefer: 'return=representation,resolution=merge-duplicates', }, body: JSON.stringify({ tenant_id: tenantId, provider, ...patch, }), }); if (!ins.ok) { return NextResponse.json( { error: await ins.text() }, { status: 500 } ); } const created = await ins.json(); return NextResponse.json({ integration: created[0] || created }); } const updated = await r.json(); return NextResponse.json({ integration: updated[0] || updated }); }