10 KiB
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 directpostgres://connection string. - Service-role client is constructed per-request inside the API in
apps/api/src/middlewares/middleware.ts(supabaseMiddleware):
Mounted globally for every route increateClient(config.SUPABASE_URL, config.SUPABASE_SERVICE_ROLE_KEY)apps/api/src/routers/index.ts. - Browser client factory at
packages/shared/src/lib/supabase.tsre-exportscreateClient; instances are produced inside@xtablo/sharedconsumers and used directly (supabase.from("table")...). - RLS — bypassed by the API using the service-role key when needed (see
CLAUDE.mdnotes); browser clients use the anon key plus the user's JWT and rely on RLS policies. - Direct Postgres —
SUPABASE_CONNECTION_STRING(with base64-encodedSUPABASE_CA_CERT) is used by the Stripe sync enginepoolConfiginapps/api/src/middlewares/stripeSync.ts. - Schema, migrations, snippets, tests —
supabase/config.toml,supabase/migrations/,supabase/snippets/,supabase/tests/. - Type codegen — regenerate
packages/shared-types/src/database.types.tswith:
These types are then refined into domain/API types insidenpx supabase gen types typescript > packages/shared-types/src/database.types.ts@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 tosupabase.auth.onAuthStateChange(). - API extracts and validates the bearer token via
authenticateFromHeaderinapps/api/src/helpers/auth.ts, wired inauthMiddleware(apps/api/src/middlewares/middleware.ts). maybeAuthenticatedMiddleware(same file) setsc.var.user = nullwhen the header is missing — used by booking and other "maybe authed" endpoints inapps/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 byadminAuthMiddleware(Bearer scheme) inapps/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_sessionon domainclients.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 usingCLIENT_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 bybasicAuthMiddlewareforapps/api/src/routers/tasks.ts.
Chat worker auth
- Independent JWT verification using
joseagainstJWT_SECRETWrangler 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 SDK —
stripe ^20.0.0instantiated per-request insidestripeMiddlewarewithapiVersion: "2025-11-17.clover"(apps/api/src/middlewares/middleware.ts). - Browser SDK —
@stripe/stripe-jsinapps/main. - Webhook router —
apps/api/src/routers/stripe.ts, mounted at/api/v1/stripe-webhookinapps/api/src/routers/index.ts. Plan tiers (solo,team,founder) keyed offSTRIPE_*_PRICE_IDenv vars. - Sync engine —
@supabase/stripe-sync-enginewrites Stripe objects directly into thestripeschema of Supabase via a direct Postgres pool (apps/api/src/middlewares/stripeSync.ts). Refetchessubscriptionandcustomerobjects to avoid stale state. - Secrets —
stripe-secret-key,stripe-webhook-secret(prod) and…-stagingvariants pulled from Google Secret Manager (apps/api/src/secrets.ts). - Billing helpers —
apps/api/src/helpers/billing.ts. Active-plan enforcement viaactivePlanAccessMiddleware; only the org's billing owner can manage billing. - Docs —
docs/STRIPE_ARCHITECTURE.md,docs/STRIPE_README.md,docs/STRIPE_WITH_SYNC_ENGINE.md,docs/STRIPE_INTEGRATION_COMPLETE.mdplus 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-s3pointed athttps://${R2_ACCOUNT_ID}.r2.cloudflarestorage.comwithregion: "auto".- Instantiated per-request in
r2Middleware(apps/api/src/middlewares/middleware.ts) and exposed asc.get("s3_client"). - Credentials:
R2_ACCESS_KEY_ID/R2_SECRET_ACCESS_KEYfrom Secret Manager. - Upload pipelines use
multer+sharpfor image processing (declared inapps/api/package.json).
Email — Gmail OAuth2 (Nodemailer)
- SMTP through
smtp.gmail.com:465using Google OAuth2 tokens. - Built in
apps/api/src/middlewares/transporter.tsvianodemailer.createTransportwithEMAIL_USER,EMAIL_CLIENT_ID,EMAIL_CLIENT_SECRET,EMAIL_REFRESH_TOKEN. - Exposed as
c.get("transporter")everywheretransporterMiddlewareis applied (mounted globally inapps/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 inapps/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 byapps/main,apps/clients,@xtablo/tablo-views). - Custom domain:
chat.xtablo.com.
Observability
Frontend — Datadog RUM
apps/main/src/lib/rum.tsinitialises@datadog/browser-rumwithapplicationId: "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.tsdoes the same for the clients app.- User is set in
apps/main/src/providers/UserStoreProvider.tsx(datadogRum.setUseron login,clearUseron logout). - Manual view names via
apps/main/src/hooks/useDatadogRumViewName.tsx.
Backend — dd-trace
tracer.init({ logInjection: true })is the very first call inapps/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.tsfetches every sensitive value fromprojects/xtablo/secrets/{name}/versions/latestusing@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…-stagingvariants. - 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.tomlbindsapp.xtablo.com(prod) andapp-staging.xtablo.com(staging), usesnot_found_handling = "single-page-application".apps/external/wrangler.toml,apps/admin/wrangler.toml,apps/clients/wrangler.tomleach have their own custom domain.apps/chat-worker/wrangler.tomldeclares theChatRoomDurable Object SQLite class and bindschat.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 owncompose.yaml/justfileand is independent of the TypeScript build pipeline (useschi,templ,sqlc,pgx).
Infra utilities
infra/docker-compose.yaml,infra/docker-compose.traefik.yaml,infra/Dockerfile— generic infra/traefik setup.infra/app/main.pyis 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). - PWA —
vite-plugin-pwainapps/maingenerates the service worker;workbox-windowregisters it. - Sourcemaps to Datadog —
@datadog/datadog-ciis available at the workspace root fordatadog-ci sourcemaps upload. - Chromatic —
chromatic ^11.5.0listed inapps/maindev deps for visual regression (no CI workflow inspected here).