Answer
The backend is one FastAPI service, core-service, in front of Supabase Postgres. Most rules for access, XP and completion run as Postgres RPCs that core-service calls. AI, video, voice, billing, push and email are outside providers.
- core-service registers 28 routers in one app. The
ai-service/folder is a stub that returns fake text and is not deployed.12 - Access, XP and completion live in SQL functions such as
check_content_access_rpcandupdate_daily_stats_rpc.34 - Kids call with a 12-hour HS256 kid JWT, and core-service then runs their queries as the parent.56
- Production is a manual deploy to DigitalOcean droplets in blr1 and nyc1 behind one global load balancer.78
Applies todattavani-backend at 2574334 (branch dev-dattajayanthi)code read on 30 Oct 2026, nothing runpayments are in the separate payments setseverities are our judgment
What are the parts and how do they connect?
Diagram structure
- Mobile app
app - Admin console
admin - Load balancer + Caddy
edge - core-service
core - Supabase Postgres
db - Cloudflare R2
r2 - CHRAG RAG
chrag - LiveAvatar + Swamiji
live - Stripe + RevenueCat
bill - FCM + SMTP
notify - Host cron + pg_cron
cron app→edge· HTTPSedge→core· proxycore→db· RPC + SQLcore→chrag· /querycore→live· sessionscore→r2· mediacore→notify· push, emailbill→core· webhookscron→core· jobsadmin→db· service roleadmin→r2· uploads
- Mobile app
Parents sign in with a Supabase OTP; kids then call with a kid JWT. Media plays from the R2 CDN and LiveKit directly.
- Load balancer + Caddy
A global load balancer feeds regional droplets. Caddy on each droplet hides
/docsin production. - core-service
FastAPI holds the HTTP shape, auth and orchestration. It keeps small caches in each process.
- Supabase Postgres
Holds all state, plus the RPCs that decide access and write XP, rings and streaks.
- CHRAG RAG
Answers Ask Tataji questions. A circuit breaker returns 503 when it is slow or failing.
- LiveAvatar + Swamiji
Live video and voice. core-service only creates, records and bills the sessions.
- Stripe + RevenueCat
Billing events arrive as signed webhooks; see the payments set.
- Host cron + pg_cron
Host cron runs
python -m app.jobs.run_*; pg_cron runs two SQL clean-up jobs. - Admin console
Edits content straight in Supabase with the service-role key, and uploads media to R2.
Which page answers which question?
| Page | Question it answers | Diagram |
|---|---|---|
01-system-overview.html | What runs where, and who calls whom? | Architecture |
02-core-service-map.html | Where does each area live inside core-service? | Tree |
03-request-pipeline.html | What happens to one request, from middleware to response? | Sequence |
04-auth-and-identity.html | How do parent sign-in, the PIN and kid sessions work? | Sequence |
05-ask-tataji.html | How is a chat question checked, answered and billed? | Sequence |
06-live-voice-and-avatar.html | How do live video and Swamiji voice sessions start, stop and bill? | Sequence |
07-learning-content.html | How does a kid browse, read, take a quiz and complete content? | Sequence |
08-gamification.html | Which events earn XP, coins, rings, streaks, badges and rank? | Dataflow |
09-access-and-limits.html | Which gates decide whether a kid may use a feature now? | Workflow |
10-notifications-and-email.html | How is a push, inbox or email message made and sent? | Dataflow |
11-background-jobs.html | Which scheduled jobs exist, and what do they touch? | Workflow |
12-data-model-family-progress.html | Which tables hold parents, kids, progress and rewards? | ERD |
13-data-model-content-ai.html | Which tables hold content, chat, live sessions and config? | ERD |
14-admin-console.html | How does the admin console reach data and media? | Architecture |
15-deployment.html | How do staging and production get built and run? | Architecture |
../payments/index.html | How do Stripe and RevenueCat payments work? | Separate set |
All pages sit in this folder. Each diagram node links to its pinned source lines at commit 2574334.
Which rules does every change need to respect?
| Rule | Where it lives | Why it matters |
|---|---|---|
| Change the schema only with a new migration | supabase/migrations/ | Nobody edits the database or an applied migration by hand.9 |
| Access is decided in SQL, in a fixed order | check_content_access_rpc | Consent, then membership, then the kid's minutes, then the trial cap.3 |
| One function writes XP, coins, rings and streak | update_daily_stats_rpc | A second writer gets out of step with the leaderboard.4 |
| Two error shapes exist | main.py path list | Chat, avatar and Swamiji routes always return {success, message, data, code}; others may return {detail}.10 |
| Caches live in each worker process | cachetools TTL caches | A cache clear reaches one worker only; content can be up to 5 minutes stale.11 |
| Two schedulers run jobs | host crontab and pg_cron | A new job needs its schedule added in docs/CRON_JOBS_SETUP.md and on the host.12 |
What risks did the analysis find?
| Risk | Where | Effect | Severity |
|---|---|---|---|
| Admin bundle ships the Supabase service-role key and R2 secrets | admin/lib/supabase.ts, admin/lib/r2.ts | Anyone who loads the hosted admin can read and write every table and the R2 bucket.1314 | P0 |
| PIN verify has no attempt limit | routers/user_management.py | A 4-digit parent PIN has 10,000 values to try.15 | P1 |
| A video session the app never stops is never billed | services/avatar_session.py | The next start closes it with a plain stop, not stop-and-bill.16 | P1 |
| Credit deduction reads, then writes | services/tataji_app_config.py | Two paid turns at once can under-charge.17 | P1 |
| Badge XP and coins bypass the daily-stats RPC | services/badge_service.py | Updates can be lost, and badge XP never counts on the leaderboard.18 | P1 |
| Python and SQL disagree on a RevenueCat trial | services/trial_service.py | A RevenueCat trial skips the trial cap on v1 routes only.19 | P1 |
| Registry keeps only 3 images | deploy-production.yml | Retagging an image older than the last 3 fails, so rollback needs a rebuild.20 | P1 |
| Admin calls a story cache route that does not exist | admin/services/contentService.ts | Story edits wait out the cache.21 | P2 |
| CHRAG concurrency cap is never applied | lifespan.py | The semaphore is created but unused, so CHRAG calls are uncapped.22 | P2 |
| Device-token insert omits NOT NULL columns | routers/notifications.py | A parent without a session gets a 500.23 | P2 |
Each page's cards list more findings with file and line. The admin allowlist screen still queries a dropped table. PIN reset-token reuse is tracked in per-process memory.