feat: OAuth Google unificado (Drive+GA4+GSC) y sync a Orgánico

Conectar marketing@ con scopes Analytics/GSC; descubrir propiedades,
emparejar fichas y sincronizar organic_daily 28d.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
4Ventures
2026-09-24 12:04:05 +02:00
parent 81ede58330
commit aded4c3514
7 changed files with 1034 additions and 9 deletions

View File

@@ -3,8 +3,12 @@
import { serviceHeaders, supabaseUrl } from '@/lib/agency';
export const DRIVE_PROVIDER = 'google_drive';
/** Unified agency Google scopes: Drive + GA4 + Search Console. */
export const DRIVE_SCOPES = [
'https://www.googleapis.com/auth/drive',
'https://www.googleapis.com/auth/analytics.readonly',
'https://www.googleapis.com/auth/webmasters.readonly',
'openid',
'email',
'profile',
@@ -166,12 +170,16 @@ export async function getAgencyOAuthStatus(): Promise<{
email?: string | null;
expiresAt?: string | null;
configured: boolean;
scopes?: string | null;
hasAnalytics?: boolean;
hasSearchConsole?: boolean;
hasDrive?: boolean;
}> {
const configured = driveConfigured();
const headers = serviceHeaders();
if (!headers) return { connected: false, configured };
const r = await fetch(
`${supabaseUrl()}/rest/v1/agency_oauth?provider=eq.${DRIVE_PROVIDER}&select=account_email,expires_at,refresh_token,access_token`,
`${supabaseUrl()}/rest/v1/agency_oauth?provider=eq.${DRIVE_PROVIDER}&select=account_email,expires_at,refresh_token,access_token,scopes`,
{ headers, cache: 'no-store' }
);
if (!r.ok) {
@@ -180,16 +188,22 @@ export async function getAgencyOAuthStatus(): Promise<{
configured,
};
}
const rows = (await r.json()) as OAuthRow[];
const rows = (await r.json()) as (OAuthRow & { scopes?: string | null })[];
const row = rows[0];
if (!row?.access_token && !row?.refresh_token) {
return { connected: false, configured };
}
const scopes = row.scopes || '';
return {
connected: true,
email: row.account_email,
expiresAt: row.expires_at,
configured,
scopes,
hasAnalytics: scopes.includes('analytics'),
hasSearchConsole:
scopes.includes('webmasters') || scopes.includes('searchconsole'),
hasDrive: scopes.includes('drive') || !scopes,
};
}