CreatorTrack Personal Contacts <-> Google Contacts sync¶
URL: https://mkdocs.justinsforge.com/memory/general/reference_creatortrack_contacts_google_sync/
Two-way sync between the Personal Contacts app (crm.people) and Google Contacts, built
2026-07-08 on branch feat/food-ai-contacts-sync, merged to main (c6fdcc1). Faithful analog of
the Google Tasks two-way sync (gtasks pattern). Reuses the shared per-user Google connection
plumbing (google-connections); does NOT hand-roll auth. [Claude Code]
Files¶
lib/google-contacts.ts, thin People API v1 client (incrementallistConnectionsw/ syncToken,create/update/deleteContact, etag + updateTime cursors,SyncTokenExpiredErroron 410).lib/google-contacts-sync.ts, the engine: pull (Google->CT) + push (CT->Google) + deletes both ways,app.google_contacts_linksmapping,content_hashecho-suppression BOTH directions, newest-wins conflict (source updateTime vs nodeupdated_at).runGoogleContactsSync(loud) +runGoogleContactsSyncBestEffort(swallows for piggy-backed runs).lib/google-contacts-settings.ts,users.prefs.crm_app.gcontacts_enabled+.gcontacts_state(chosen account + People syncToken cursor + lastSyncAt), sibling-preserving jsonb_set idiom.db/migrations/20260708T190500_..._google_contacts_links_table.sql+db/schema.tsgoogleContactsLinks(workspace RLS, mirrorsgoogle_tasks_links).app/api/apps/crm/route.tsops:get_gcontacts_settings,set_gcontacts_settings,run_gcontacts_sync.components/apps/crm/ContactsSyncView.tsx, the "Google Sync" rail view (connect account, pick account, enable toggle, Sync now + last-synced).- Scope group
contacts: [auth/contacts]added tolib/google-connections.ts.
Field mapping (round-trips)¶
name, nickname, birthday, email, phone, address, notes. CT-only (never touched): relationship, anniversary, lastContacted, interaction timeline. Conflict = last-writer-wins (loser logged as a skip, never silently dropped).
ACTIVATION (required before it works , GCP + DB)¶
DORMANT until these are done; the code ships gated behind connect + enable so it's safe undeployed.
1. GCP console (project creatortrack-499820, oauth project): enable the People API; add
scope https://www.googleapis.com/auth/contacts to the OAuth consent screen; keep the app in
Production (Testing = 7-day token expiry). Re-verification may be required for the sensitive
contacts scope.
2. Apply the migration to the CreatorTrack DB before the first sync:
WORKSPACE_MIGRATIONS_DIR=db/migrations python3 ~/forge/scripts/forge_workspace_migrate.py
(idempotent; creates app.google_contacts_links). The settings VIEW loads without it; only a
running sync needs the table.
3. In the app: Personal Contacts -> Google Sync -> Connect account -> pick it -> toggle on ->
Sync now. First run does a full import (no syncToken); subsequent runs are incremental.
Timer (not yet wired)¶
v1 is on-demand (Sync now). A scheduled runGoogleContactsSync on a systemd timer (cadence like
forge-creatortrack-sales-gmail-sync.timer, every 30 min) is the obvious follow-up once activated.