feat(backend): add standalone data plane

Replace the Supabase application data plane with the Node/PostgreSQL API, migrate mobile, admin, web, and MCP callers, add controlled cutover tooling, and define hardened Dokploy infrastructure.
This commit is contained in:
jjbrunton committed 2026-08-28 14:42:11 +01:00
1 parent 01bb8225ae
commit 006761d3b0
294 files changed
+16259 -6138

No files matched your search

+74
View File
@@ -0,0 +1,74 @@
# Standalone backend migration
Sauci's standalone Node API, PostgreSQL data plane, worker, and private filesystem media
store are implemented; production cutover is still pending. Hosted Supabase remains only
as the identity provider: the mobile and admin applications obtain a Supabase access token,
and the API verifies that token against the project's public JWKS. Product data, files,
realtime events, webhooks, scheduled work, admin operations, and MCP tools do not use the
Supabase data plane.
## Target runtime
- `api`: Node 20, Hono, Drizzle, PostgreSQL 17
- `worker`: Node 20 process sharing the API's domain modules and PostgreSQL
- `storage`: private filesystem-backed Docker volume with authenticated uploads and
API-issued, expiring HMAC download URLs; the metadata/deletion queue lives in PostgreSQL
- `realtime`: authenticated polling, with optimistic concurrency for collaborative state
- `auth`: hosted Supabase Auth only. A server-only service-role credential is used solely
for the Auth Admin account-deletion endpoint, never for database, Storage, Realtime,
or Functions access.
The API derives every user identifier from the verified bearer token. Mobile-supplied
user IDs are never an authorization boundary.
## Delivery slices
| Slice | Backend | Mobile proof | Status |
|---|---|---|---|
| Identity | profiles, feature interests | bootstrap and interest hook tests; native launch | implemented |
| Couples | create/join/read/leave relationship | pairing and profile state | implemented |
| Catalogue | categories, packs, enabled packs, progress | pack list and toggling | implemented |
| Answers | questions, responses, matches, streaks | swipe/answer/match flows | implemented |
| Chat | messages, reports, deletions, typing/polling | message and typing store tests | implemented |
| Media | avatars, response media, chat media, feedback screenshots, cleanup | upload/view/expiry | implemented |
| Account | settings, notifications, feedback, deletion | settings and account lifecycle | implemented |
| Billing | subscriptions and RevenueCat webhook idempotency | entitlement refresh | implemented |
| Operations | reminders, digests, scheduled releases, moderation and cleanup | worker/integration tests | implemented |
| Admin/MCP | role-scoped management API and audited automation | admin and MCP contract tests | implemented |
## Gate for every slice
1. Add an idempotent standalone PostgreSQL migration and domain repository tests against
a temporary real schema.
2. Add API authorization/validation tests, including a second-user isolation test where
the domain is user-scoped.
3. Move the mobile domain to `apiClient`; hosted Supabase remains reachable only through
the dedicated Auth client.
4. Run focused mobile tests, the complete mobile Jest suite, API tests/typecheck/build,
and a native simulator flow when the slice is user-visible.
5. Do not remove the legacy source or cut production traffic until a stopped-source final
sync, row/file parity checks, and application-level staging flows have passed.
Authenticated native acceptance requires an isolated hosted **non-production** Supabase
Auth project plus an onboarding-complete test user whose Auth subject has a matching profile
in the staging PostgreSQL database. Supply its public URL/anon key to the mobile build and
`E2E_EMAIL`/`E2E_PASSWORD` to Maestro at execution time. Never use a production project,
subscriber, service-role key, or production data-plane credential for local or E2E work.
The credential-free `e2e/maestro/login-screen-smoke.yaml` remains the safe build/login-shell
check when that fixture is unavailable; it is not proof of authenticated API behaviour.
## Deployment sequence
Dokploy staging receives separate persistent PostgreSQL and private-media volumes. The
API migration job must complete before the API starts. Cutover is deliberately later:
1. Export a first production snapshot into staging and compare table counts plus sampled
relational invariants.
2. Exercise the mobile app against staging while it still uses hosted Supabase Auth.
3. Stop product-data writes to the old backend, perform the final delta sync, and repeat
parity and app-level tests.
4. Switch the mobile/API endpoint. Retain the Supabase data plane read-only until rollback
is no longer required; only then reduce the hosted project to the Auth-only footprint.
The executable import, parity, final-sync, and rollback procedure is maintained in
[`docs/operations/backend-cutover.md`](../operations/backend-cutover.md).
+25 -12
View File
@@ -7,29 +7,42 @@ Sauci is an npm workspace coordinated by Turborepo.
| Mobile | Expo / React Native / Expo Router | primary customer product |
| Admin | Vite / React | content and administrative operations |
| Web | Next.js App Router | marketing, policies, redemption |
| Backend | Supabase Postgres/Auth/Realtime/Edge Functions | identity, data, workflows |
| API | Node.js / Hono / PostgreSQL | customer data, authorization, and workflows |
| Worker | Node.js / PostgreSQL-backed jobs | scheduled and asynchronous work |
| Auth | Hosted Supabase Auth | identity issuance and account recovery only |
| MCP | Hono / MCP SDK | authenticated internal administrative tools |
| Shared | TypeScript types | cross-application contracts only |
## Trust boundaries
- Mobile and web use public Supabase credentials and depend on RLS.
- Mobile and web use public Supabase credentials only for Auth. Application data
is available exclusively through the versioned Sauci API.
- The API verifies Supabase access tokens against the hosted public JWKS and
derives user, couple, and administrative authority server-side.
- PostgreSQL and object storage are private infrastructure and are never exposed
directly to clients.
- Administrative access requires both an authenticated identity and role checks.
- Service-role edge functions authenticate and authorize callers before user-data
operations.
- MCP has privileged access and requires `SAUCI_MCP_API_KEY`; absent credentials
must never make a non-local deployment public.
- Local verification uses local Supabase, never a remote project.
- The hosted Auth service-role credential is confined to the API's Auth Admin
account-deletion and identity-enrichment calls; it is not a product-data
credential and is not provided to the worker, MCP, or clients.
- MCP authenticates callers with `SAUCI_MCP_API_KEY`, then calls the standalone
admin API using `SAUCI_ADMIN_API_URL` and a scoped `SAUCI_ADMIN_API_TOKEN`.
It has no database, Supabase data-plane, or storage credentials. The API maps
the service token to an active admin identity and owns authorization and audit
evidence for every mutation.
- Local product-data verification uses only loopback PostgreSQL/API services.
Authenticated native acceptance may use only the designated hosted
non-production Auth project and disposable fixture identities, never production.
## Product flow
Two profiles join a couple, answer questions independently, and the
`submit-response` edge function creates a match when answers are compatible.
Realtime subscriptions surface matches and chat messages to the partner.
Two profiles join a couple, answer questions independently, and an API
transaction creates a match when answers are compatible. Authenticated domain
events surface matches and chat messages to the partner.
## Change boundaries
- Database shape: migration plus generated/shared types plus affected clients.
- Edge function contract: function tests plus calling UI/store behavior.
- Database shape: API-owned migration plus shared contracts and affected clients.
- API contract: integration tests plus calling UI/store behavior.
- Shared contract: update every consumer in the same change.
- UI flow: component/unit coverage and observable E2E evidence.