xtablo-source/.planning/codebase/INTEGRATIONS.md
2026-05-14 16:01:31 +02:00

10 KiB

INTEGRATIONS

External systems wired into the xtablo monorepo. Last updated 2026-05-14.

Database — Supabase Postgres

  • Hosted Postgres managed by Supabase. The frontend talks to it via the JS SDK; the API talks to it via the service-role key and (for stripe-sync-engine) a direct postgres:// connection string.
  • Service-role client is constructed per-request inside the API in apps/api/src/middlewares/middleware.ts (supabaseMiddleware):
    createClient(config.SUPABASE_URL, config.SUPABASE_SERVICE_ROLE_KEY)
    
    Mounted globally for every route in apps/api/src/routers/index.ts.
  • Browser client factory at packages/shared/src/lib/supabase.ts re-exports createClient; instances are produced inside @xtablo/shared consumers and used directly (supabase.from("table")...).
  • RLS — bypassed by the API using the service-role key when needed (see CLAUDE.md notes); browser clients use the anon key plus the user's JWT and rely on RLS policies.
  • Direct PostgresSUPABASE_CONNECTION_STRING (with base64-encoded SUPABASE_CA_CERT) is used by the Stripe sync engine poolConfig in apps/api/src/middlewares/stripeSync.ts.
  • Schema, migrations, snippets, testssupabase/config.toml, supabase/migrations/, supabase/snippets/, supabase/tests/.
  • Type codegen — regenerate packages/shared-types/src/database.types.ts with:
    npx supabase gen types typescript > packages/shared-types/src/database.types.ts
    
    These types are then refined into domain/API types inside @xtablo/shared-types (tablos.types.ts, tablo-data.types.ts, events.types.ts, stripe.types.ts, kanban.types.ts).

Authentication

Supabase Auth (primary users)

  • JWT-based, with sessions managed by SessionContext (packages/shared/src/contexts/SessionContext.tsx) listening to supabase.auth.onAuthStateChange().
  • API extracts and validates the bearer token via authenticateFromHeader in apps/api/src/helpers/auth.ts, wired in authMiddleware (apps/api/src/middlewares/middleware.ts).
  • maybeAuthenticatedMiddleware (same file) sets c.var.user = null when the header is missing — used by booking and other "maybe authed" endpoints in apps/api/src/routers/maybeAuthRouter.ts.
  • Passwordless temporary accounts (is_temporary: true) created via the public booking flow.
  • Front-end auth UI lives in @xtablo/auth-ui.

Admin auth (internal)

  • Custom JWTs signed with ADMIN_TOKEN_SIGNING_SECRET (Google Secret Manager).
  • Helpers in apps/api/src/helpers/adminTokens.ts, verified by adminAuthMiddleware (Bearer scheme) in apps/api/src/middlewares/middleware.ts.
  • Routes: apps/api/src/routers/admin.ts, adminActions.ts, adminAuth.ts, adminDatasets.ts, adminOverview.ts, adminTables.ts.

Client portal auth (read-only client users)

  • Magic-link → cookie-based session JWT, stored in cookie xtablo_client_session on domain clients.xtablo.com.
  • Issuance: apps/api/src/helpers/clientMagicLinks.ts, apps/api/src/helpers/clientSessions.ts.
  • Middlewares clientAuthMiddleware / maybeClientAuthMiddleware (apps/api/src/middlewares/middleware.ts) verify cookies using CLIENT_AUTH_JWT_SECRET.
  • Routers: apps/api/src/routers/clientAuth.ts, clientInvites.ts, clientPortal.ts.

Task auth (cron / job runners)

  • HTTP Basic with TASKS_SECRET, enforced by basicAuthMiddleware for apps/api/src/routers/tasks.ts.

Chat worker auth

  • Independent JWT verification using jose against JWT_SECRET Wrangler secret. WebSocket connections receive the token via ?token= query string; REST via Authorization header (apps/chat-worker/src/index.ts, apps/chat-worker/src/lib/auth.ts).

Payments — Stripe

  • Server SDKstripe ^20.0.0 instantiated per-request inside stripeMiddleware with apiVersion: "2025-11-17.clover" (apps/api/src/middlewares/middleware.ts).
  • Browser SDK@stripe/stripe-js in apps/main.
  • Webhook routerapps/api/src/routers/stripe.ts, mounted at /api/v1/stripe-webhook in apps/api/src/routers/index.ts. Plan tiers (solo, team, founder) keyed off STRIPE_*_PRICE_ID env vars.
  • Sync engine@supabase/stripe-sync-engine writes Stripe objects directly into the stripe schema of Supabase via a direct Postgres pool (apps/api/src/middlewares/stripeSync.ts). Refetches subscription and customer objects to avoid stale state.
  • Secretsstripe-secret-key, stripe-webhook-secret (prod) and …-staging variants pulled from Google Secret Manager (apps/api/src/secrets.ts).
  • Billing helpersapps/api/src/helpers/billing.ts. Active-plan enforcement via activePlanAccessMiddleware; only the org's billing owner can manage billing.
  • Docsdocs/STRIPE_ARCHITECTURE.md, docs/STRIPE_README.md, docs/STRIPE_WITH_SYNC_ENGINE.md, docs/STRIPE_INTEGRATION_COMPLETE.md plus several setup/testing guides.

Mobile Payments — RevenueCat

  • Webhook router apps/api/src/routers/revenuecat.ts, mounted at /api/v1/revenuecat-webhook.
  • Maps Apple in-app product IDs to plans via apps/api/src/helpers/appleBilling.ts.
  • Env: REVENUECAT_WEBHOOK_AUTH_HEADER, REVENUECAT_SOLO_PRODUCT_ID, REVENUECAT_ANNUAL_PRODUCT_ID.

Storage — Cloudflare R2 (S3 SDK)

  • @aws-sdk/client-s3 pointed at https://${R2_ACCOUNT_ID}.r2.cloudflarestorage.com with region: "auto".
  • Instantiated per-request in r2Middleware (apps/api/src/middlewares/middleware.ts) and exposed as c.get("s3_client").
  • Credentials: R2_ACCESS_KEY_ID / R2_SECRET_ACCESS_KEY from Secret Manager.
  • Upload pipelines use multer + sharp for image processing (declared in apps/api/package.json).

Email — Gmail OAuth2 (Nodemailer)

  • SMTP through smtp.gmail.com:465 using Google OAuth2 tokens.
  • Built in apps/api/src/middlewares/transporter.ts via nodemailer.createTransport with EMAIL_USER, EMAIL_CLIENT_ID, EMAIL_CLIENT_SECRET, EMAIL_REFRESH_TOKEN.
  • Exposed as c.get("transporter") everywhere transporterMiddleware is applied (mounted globally in apps/api/src/routers/index.ts).

Chat — Custom Cloudflare Durable Objects

The CLAUDE.md mentions Stream Chat historically, but the current implementation is a custom WebSocket chat running on Cloudflare Workers + Durable Objects (no stream-chat dependency found anywhere in the repo).

  • Worker entry: apps/chat-worker/src/index.ts (Hono).
  • Durable Object class: apps/chat-worker/src/durable-objects/ChatRoom.ts. Declared in apps/chat-worker/wrangler.toml:
    [durable_objects]
    bindings = [{ name = "CHAT_ROOM", class_name = "ChatRoom" }]
    [[migrations]]
    tag = "v1"
    new_sqlite_classes = ["ChatRoom"]
    
  • Membership enforced by querying Supabase via PostgREST (apps/chat-worker/src/lib/supabase.ts).
  • UI consumed via @xtablo/chat-ui (used by apps/main, apps/clients, @xtablo/tablo-views).
  • Custom domain: chat.xtablo.com.

Observability

Frontend — Datadog RUM

  • apps/main/src/lib/rum.ts initialises @datadog/browser-rum with applicationId: "8e268e1a-1be0-44c6-b12a-978530d497c7", service: "xtablo-ui", sessionSampleRate: 100, sessionReplaySampleRate: 80, defaultPrivacyLevel: "mask-user-input", and the React plugin with router instrumentation.
  • apps/clients/src/lib/rum.ts does the same for the clients app.
  • User is set in apps/main/src/providers/UserStoreProvider.tsx (datadogRum.setUser on login, clearUser on logout).
  • Manual view names via apps/main/src/hooks/useDatadogRumViewName.tsx.

Backend — dd-trace

  • tracer.init({ logInjection: true }) is the very first call in apps/api/src/index.ts.
  • Datadog CI tooling (@datadog/datadog-ci, @datadog/datadog-ci-plugin-cloud-run) is wired for Cloud Run instrumentation uploads.
  • Static analysis is configured at the repo root in static-analysis.datadog.yml.

Secrets Management — Google Secret Manager

  • apps/api/src/secrets.ts fetches every sensitive value from projects/xtablo/secrets/{name}/versions/latest using @google-cloud/secret-manager.
  • Secrets loaded: supabase-service-role-key, supabase-connection-string, supabase-ca-cert, admin-token-signing-secret, client-auth-jwt-secret, email-client-secret, email-refresh-token, r2-access-key-id, r2-secret-access-key, stripe-secret-key, stripe-webhook-secret, plus their …-staging variants.
  • Setup guide: docs/GOOGLE_SECRET_MANAGER_SETUP.md.

Deployment Platforms

Cloudflare Workers (web apps + chat)

  • All Vite-built frontends are deployed to Workers via Wrangler (see apps/*/wrangler.toml).
  • apps/main/wrangler.toml binds app.xtablo.com (prod) and app-staging.xtablo.com (staging), uses not_found_handling = "single-page-application".
  • apps/external/wrangler.toml, apps/admin/wrangler.toml, apps/clients/wrangler.toml each have their own custom domain.
  • apps/chat-worker/wrangler.toml declares the ChatRoom Durable Object SQLite class and binds chat.xtablo.com.
  • Worker scripts include observability blocks ([observability] enabled = true).

Google Cloud Run (API)

  • Containerised by apps/api/Dockerfile (multi-stage Node 20 Alpine, pnpm-driven).
  • CI/CD via Google Cloud Build using apps/api/cloudbuild.yaml.
  • Runtime configuration uses Cloud Run env vars + Secret Manager (no env files inside the image).
  • Deployment guides: docs/CLOUD_BUILD_SETUP.md, docs/CLOUD_BUILD_ENV_CONFIG.md, docs/DOCKER_BUILD.md, docs/DOCKER_PNPM_OPTIMIZATION.md, docs/DOCKER_BUILD_PERFORMANCE.md.

Go backend (separate)

  • go-backend/ deploys via its own compose.yaml / justfile and is independent of the TypeScript build pipeline (uses chi, templ, sqlc, pgx).

Infra utilities

  • infra/docker-compose.yaml, infra/docker-compose.traefik.yaml, infra/Dockerfile — generic infra/traefik setup. infra/app/main.py is a small Python helper.

Other Integrations / Tooling

  • Google APIs (googleapis ^161.0.0) — currently used to mint OAuth2 access tokens for the Gmail SMTP transport (apps/api/src/middlewares/transporter.ts).
  • PWAvite-plugin-pwa in apps/main generates the service worker; workbox-window registers it.
  • Sourcemaps to Datadog@datadog/datadog-ci is available at the workspace root for datadog-ci sourcemaps upload.
  • Chromaticchromatic ^11.5.0 listed in apps/main dev deps for visual regression (no CI workflow inspected here).