feat: conectar GA4/GSC/Drive desde la ficha (verificar + sync real)
Al guardar ID o URL en el cliente se valida con OAuth de agencia; Drive acepta URL o folder id; Orgánico se rellena si hay acceso. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -7,6 +7,11 @@ import {
|
||||
supabaseUrl,
|
||||
userRole,
|
||||
} from '@/lib/agency';
|
||||
import {
|
||||
activateGoogleFromFicha,
|
||||
LIVE_GOOGLE_PROVIDERS,
|
||||
normalizeDriveFolderId,
|
||||
} from '@/lib/integration-activate';
|
||||
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
@@ -93,7 +98,7 @@ export async function GET(req: NextRequest) {
|
||||
});
|
||||
}
|
||||
|
||||
/** PATCH — update one integration status/account (manual connect for now). */
|
||||
/** 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;
|
||||
@@ -124,34 +129,69 @@ export async function PATCH(req: NextRequest) {
|
||||
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<string, unknown> = {
|
||||
updated_at: new Date().toISOString(),
|
||||
};
|
||||
if (status) patch.status = status;
|
||||
if (externalAccountId !== undefined) {
|
||||
let id = externalAccountId || null;
|
||||
// Drive: accept full folder URL → store folder id only
|
||||
if (provider === 'google_drive' && id) {
|
||||
const m = id.match(/\/folders\/([a-zA-Z0-9_-]+)/);
|
||||
if (m) id = m[1];
|
||||
else if (id.includes('http')) {
|
||||
const q = id.match(/[?&]id=([a-zA-Z0-9_-]+)/);
|
||||
if (q) id = q[1];
|
||||
}
|
||||
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;
|
||||
// Manual «Conectar» only marks the reference — do not fake a data sync timestamp.
|
||||
// Real syncs (Ads discover, Orgánico, Superhuman…) set last_sync_at themselves.
|
||||
if (status === 'connected') {
|
||||
patch.error_message = null;
|
||||
}
|
||||
if (status === 'disconnected') {
|
||||
// keep external id for re-link unless cleared
|
||||
}
|
||||
|
||||
if (!status && enabled === undefined && externalAccountId === undefined) {
|
||||
return NextResponse.json(
|
||||
@@ -173,7 +213,6 @@ export async function PATCH(req: NextRequest) {
|
||||
});
|
||||
|
||||
if (!r.ok) {
|
||||
// upsert if missing
|
||||
const ins = await fetch(`${base}/rest/v1/integrations`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
|
||||
Reference in New Issue
Block a user