How the platform is built

Capacity Connect · Technical atlas

How the platform is built

Capacity Connect is a learning management platform for the India Meteorological Department (SIH 2026, PS 26075). Trainees enroll and take assessments, trainers publish resources and quizzes, admins approve accounts and see competency data. This atlas is the technical approach the team builds from: components, data flow, function names, SQL and rules.

Explore the diagrams

Every diagram opens full screen: tap or click it, or use Zoom / full screen. In the viewer, scroll or pinch to zoom, drag to pan, double-click to jump in.

System map

The whole platform on one sheet: clients, edge, the four-stage API chain, the control and data paths, Postgres with pgvector, local file storage, the AI provider switch, and the embedding worker pool. Read it top to bottom. The two thick arrows are the two main request paths.

System map

CLIENTS DNS Admin ConsoleVite MPA + react-router Trainer / Trainee Portalone app, role-branched Flutter Mobilego_router + dio Cross-cutting concerns Authentication · Better Auth + JWT Authorization · nav_item_roles guards Row-level security · vector isolation Rate limiting · Nginx and per user Nginx reverse proxy TLS · per-section try_files · CORS fail2banLayer 0 · SSH and Nginx logs Cloudflareafter launch auth_strict 2r/s · api_general 15r/s Express API · middleware chain verifyJWTBetter Auth session attachRoleapproved = true authorizeRoutenav_item_roles injectRLSContextSET LOCAL per txn Better Auth rateLimit/api/auth/* onlylogin, signup, reset express-rate-limitkeyed by user id, else IPAI gen 10/h · certs 20/min Control path Data path Route handlers · control courses · enrollment · profiles notices · dropdowns · approvals questionnaires · attempts · feedback Route handlers · data POST /resources (multipart to local storage) POST /certificates/issue GET /courses/:id/resources GotenbergHTML to PDF · internal net certificate PDF PostgreSQL 17 + pgvector Relational core user_roles · profilescourses · enrollmentsquestionnairesnotices · dropdowns resource_chunks VECTOR(768)ivfflat · cosinechild oflearning_resources Competency trainer_profilescoursesVECTOR(768) RLS · super_admin writes chunksprofiles · resources · chunks Job queueFOR UPDATE SKIP LOCKED reads and writes under RLS insert row, enqueue job Local File Storage Docker volume · uploads/ · certs/ · served via authenticated API only uploads/course resourcesfile_path in Postgres certs/{trainee}/{course}.pdffrom Gotenberg snapshots/nightly pg_dumpgzip · dated No public links · every file goes through the API Access mirrors the RLS scoping of the database rows. fetch file multipart upload Embedding pipeline · Python ai-service Job queuePostgres table Worker supervisorclaim job · SKIP LOCKEDwrites as super_admin N workers Worker 1 Worker 2 Worker 3 Each worker runs, per job: 1 Extractpptx · pdf · transcript 2 Chunkabout 500 tokens 3 Embednomic-embed-text 4 Storeresource_chunks Ollama :11434nomic-embed-text · 768-d Fallback: call Ollama inline on upload. Drops the queue if time runs short. claim job INSERT chunks Question generatorPOST /questionnaires/:id/generate · top-k chunksreturns a draft to review Competency mappingcosine distance queryruns as BYPASSRLS roletop 5 trainers per course chunks in, draft out vectors AI_PROVIDER switchJSON-only MCQ draft, one env var Vertex AI (Gemini) · cloud Ollama (Qwen3.5 9B/4B) · local Admin consoleranked suggestion widget Infrastructure Cron02:00 daily · pg_dump, gzip to snapshots/03:00 Sunday · verify latest dump GCP Compute Engineheavier training work onlynot on the embedding path Docker Compose networknginx · api · ai-service · gotenberg · postgres · ollamaGotenberg has no public exposure LEGEND services control data and blob queue data tier AI and embeddings external or optional
Clients, edge, API, storage and AI on one sheet.

Components

One Express API holds all business logic as modules. Two things run outside it because they are slow or need other tooling: the Python ai-service and Gotenberg.

ComponentTechResponsibilityPath
Admin consoleVite multi-page build, React Router per sectionApprovals, courses, competency widget, dropdown manager, noticesapps/admin-console
PortalVite multi-page build, React Router per sectionOne app for trainers and trainees; each page branches on roleapps/portal
MobileFlutter, go_router, dioTrainee and trainer screens over the same REST APIapps/mobile
APINode.js, Express, Better Auth, pgAuth chain, REST handlers, rate limits, RLS contextservices/api
ai-servicePythonEmbedding workers, quiz drafting support, competency matchingservices/ai-service
GotenbergDocker image behind the APIHTML to PDF for certificates, internal network onlyservices/pdf-service
DatabasePostgreSQL 17, pgvector, DrizzleAll tables, vectors and row-level security policiespackages/db
Local AIOllamanomic-embed-text embeddings always; Qwen3.5 for quizzes in local moderuns on the deployment host

Two request paths

The map draws two thick arrows out of the API. They behave differently and are worth keeping apart in your head.

Control path

JSON in, JSON out. Courses, enrollment, profiles, notices, dropdowns, approvals, questionnaires, attempts and feedback. Small, fast, transactional. Every handler runs inside the RLS transaction described on the Security page.

Data path

Bytes in, bytes out. Resource uploads go to the volume, certificate PDFs come back from Gotenberg and land in the volume, and downloads stream from it. These routes are the only way a file leaves storage.

Background path

Uploading a resource queues an embedding job. The ai-service worker turns the file into chunks and vectors without holding up the request.

Design rules

  1. The API is the only real gateHiding a sidebar item is decoration. Every protected route passes verifyJWT, attachRole, authorizeRoute and injectRLSContext, on web and mobile alike.
  2. Row-level security carries the identityEach request opens a transaction and sets the caller id and role for that transaction. Policies filter rows and vectors. No pooler sits between the API and Postgres, so the setting cannot leak across requests.
  3. Embeddings are always localEvery vector comes from Ollama nomic-embed-text at 768 dimensions. The model name is stored in an embedding_model column next to each vector so a future model swap is detectable.
  4. One switch for quiz AIAI_PROVIDER selects Vertex AI (Gemini) or local Ollama (Qwen3.5 9B/4B). Both must return the same JSON shape, so the rest of the system never branches on the provider.
  5. Files never have public URLsUploads, certificates and dumps sit on a Docker volume. The database stores a file_path. Downloads go through the API and inherit the same row-level checks.
  6. A human reviews AI outputGenerated questions land as drafts. Nothing reaches trainees until the trainer edits and publishes.

Roles

RoleKindCan do
super_adminservice roleUsed by the embedding workers to write resource_chunks. Not a person-facing role.
adminpersonApprove and reject accounts, manage dropdowns and notices, see dashboards, run competency suggestions.
trainerpersonCreate courses, upload resources, generate and publish questionnaires, monitor trainee results.
traineepersonEnroll, read resources, attempt questionnaires once, give feedback, download certificates.
Capacity Connect · Team Syntax Squad · SIH 2026 · PS 26075Code samples are implementation sketches.