Skip to content

feat: Google Business Profile connection (local SEO foundation) - #169

Open
oscaroyomba wants to merge 1 commit into
every-app:mainfrom
oscaroyomba:feat/local-seo-listings
Open

feat: Google Business Profile connection (local SEO foundation)#169
oscaroyomba wants to merge 1 commit into
every-app:mainfrom
oscaroyomba:feat/local-seo-listings

Conversation

@oscaroyomba

Copy link
Copy Markdown

Foundation for the local-SEO / listing-management gap identified against Semrush's feature set: connects a project to a verified Google Business Profile location.

Architecture mirrors the existing Search Console integration exactly -- same generic-OAuth provider pattern (reuses GOOGLE_CLIENT_ID/SECRET, just adds the business.manage scope), same token encryption via Better Auth, same HMAC-signed state for CSRF protection, same same-origin redirect validation.

What it does: OAuth connect flow, list accounts + locations with per-grant reconnect status, bind a location to a project, disconnect, and fetch the full location profile (title, address, phone, categories, regular hours, website) via the Business Information API.

Scope: read-only vertical slice (accounts.list, locations.list, locations.get) -- no write operations, Q&A, or reviews yet, matching the incremental approach from #167 and #168.

Real-world caveat: Business Profile API access requires a separate approval application from Google (unlike Search Console). This PR builds the correct integration; a live connection needs that approval granted first.

Verified: full suite 733/733 passing, schema-parity 120/120 passing. tsc --noEmit has exactly one error -- the new callback route isn't in routeTree.gen.ts yet because this dev environment (Node 20.20.2) can't run vite dev/build at all (@cloudflare/vite-plugin needs node:module's registerHooks, Node 22+) -- confirmed via git stash as the exact same pre-existing issue already documented in #167's knip failure, unrelated to this change. CI will regenerate the route tree normally.

Adds the OAuth connection layer for Google Business Profile, mirroring
the existing Search Console integration's architecture exactly:

- gbp_connections table (D1 + Postgres, schema-parity tested) mapping a
  verified location to a project
- Registered as a genericOAuth provider (google-business-profile) in
  auth-config.ts, reusing the same GOOGLE_CLIENT_ID/SECRET as Search
  Console -- no new credentials needed, just the additional
  business.manage scope
- Self-hosted OAuth flow (selfHostedOAuth.ts): HMAC-signed state for
  CSRF protection, same-origin-only redirect validation, tokens
  encrypted at rest via Better Auth's own symmetricEncrypt (same key,
  same encryptOAuthTokens gate GSC uses)
- gbpClient.ts: Account Management + Business Information API wrapper.
  Access tokens are minted/refreshed by Better Auth's own
  getAccessToken -- no custom refresh logic needed
- GbpConnectionRepository / GbpService: list accounts+locations with
  per-grant reconnect-needed status, set/disconnect a location,
  fetch full location profile (title, address, phone, categories,
  regular hours, website)
- serverFunctions + callback route, following the GSC pattern exactly

Scope: OAuth + read (accounts.list, locations.list, locations.get).
Does not yet cover write operations, Q&A, or reviews -- kept to a
first vertical slice matching the pattern from the brand-monitoring
and content-quality PRs.

Real-world caveat: Google Business Profile API access requires a
separate approval application from Google (unlike Search Console,
which is self-serve) -- this PR builds the correct integration but a
live connection needs that approval in place first.

Verified: full test suite 733/733 passing (zero regressions),
schema-parity 120/120 passing. tsc --noEmit has exactly one error,
src/routes/api/gbp/oauth/callback.ts referencing the new route before
routeTree.gen.ts is regenerated -- confirmed via git stash that this
environment (Node 20.20.2) cannot run vite dev/build at all due to
@cloudflare/vite-plugin requiring node:module's registerHooks (Node 22+),
the same root cause already documented as pre-existing in PR every-app#167's
knip failure. CI, which runs a supported Node version, will regenerate
the route tree automatically as part of its normal build step.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant