feat: Google Business Profile connection (local SEO foundation) - #169
Open
oscaroyomba wants to merge 1 commit into
Open
feat: Google Business Profile connection (local SEO foundation)#169oscaroyomba wants to merge 1 commit into
oscaroyomba wants to merge 1 commit into
Conversation
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 thebusiness.managescope), 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 --noEmithas exactly one error -- the new callback route isn't inrouteTree.gen.tsyet because this dev environment (Node 20.20.2) can't runvite dev/buildat all (@cloudflare/vite-pluginneedsnode:module'sregisterHooks, Node 22+) -- confirmed viagit stashas the exact same pre-existing issue already documented in #167's knip failure, unrelated to this change. CI will regenerate the route tree normally.