import { NextRequest, NextResponse } from 'next/server'; import { createClient } from '@/lib/supabase/server'; import { INTEGRATION_PROVIDERS, isAgencyRole, serviceHeaders, supabaseUrl, userRole, } from '@/lib/agency'; import { activateGoogleFromFicha, LIVE_GOOGLE_PROVIDERS, normalizeDriveFolderId, } from '@/lib/integration-activate'; 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 integration; GA4/GSC/Drive verify + sync via agency Google OAuth. */ 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; const enabled = typeof body.enabled === 'boolean' ? body.enabled : undefined; if (!tenantId || !provider) { return NextResponse.json( { error: 'tenantId y provider requeridos' }, { status: 400 } ); } if (status && !['disconnected', 'pending', 'connected', 'error'].includes(status)) { return NextResponse.json({ error: 'status inválido' }, { status: 400 }); } const wantsLiveConnect = LIVE_GOOGLE_PROVIDERS.has(provider) && (status === 'connected' || (status === 'pending' && Boolean(externalAccountId))); if (wantsLiveConnect) { let rawId = externalAccountId || ''; if (!rawId) { const cur = await fetch( `${supabaseUrl()}/rest/v1/integrations?tenant_id=eq.${encodeURIComponent(tenantId)}&provider=eq.${encodeURIComponent(provider)}&select=external_account_id`, { headers, cache: 'no-store' } ); if (cur.ok) { const rows = (await cur.json()) as { external_account_id?: string }[]; rawId = rows[0]?.external_account_id || ''; } } if (!rawId) { return NextResponse.json( { error: 'Indica ID o URL antes de conectar', activation: { ok: false, message: 'Falta ID' }, }, { status: 400 } ); } const activation = await activateGoogleFromFicha({ tenantId, provider: provider as 'ga4' | 'search_console' | 'google_drive', rawId, }); const list = await fetch( `${supabaseUrl()}/rest/v1/integrations?tenant_id=eq.${encodeURIComponent(tenantId)}&provider=eq.${encodeURIComponent(provider)}&select=*`, { headers, cache: 'no-store' } ); const rows = list.ok ? await list.json() : []; return NextResponse.json( { integration: rows[0] || null, activation, error: activation.ok ? undefined : activation.errorMessage || activation.message, }, { status: activation.ok ? 200 : 422 } ); } const patch: Record = { updated_at: new Date().toISOString(), }; if (status) patch.status = status; if (externalAccountId !== undefined) { let id = externalAccountId || null; if (provider === 'google_drive' && id) { id = normalizeDriveFolderId(id) || id; } patch.external_account_id = id; } if (label !== undefined) patch.label = label || null; if (errorMessage !== undefined) patch.error_message = errorMessage || null; if (enabled !== undefined) patch.enabled = enabled; if (status === 'connected') { patch.error_message = null; } if (!status && enabled === undefined && externalAccountId === undefined) { return NextResponse.json( { error: 'status, enabled o externalAccountId requeridos' }, { status: 400 } ); } 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) { 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, status: status || 'disconnected', ...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 }); }