JK
>CHARGEMENT // CHARGEMENT
INITIALISATION DE LA VUE
Plateforme SaaS multi-tenant conçue avec Go, PostgreSQL, Redis et Docker fournissant des diagnostics d'apprentissage avancés.
Les instituts manquent d'outils automatisés pour identifier tôt les lacunes pédagogiques, tout en nécessitant une surveillance intégrée et un traitement asynchrone évolutif.
| Technologie | Version | Objectif | Pourquoi ce choix |
|---|---|---|---|
| Go | 1.25.1 | Backend language | Strong concurrency, fast compilation |
| Gin | 1.12.0 | HTTP framework | High performance, mature middleware ecosystem |
| PostgreSQL | 12+ | Primary database | JSONB for flexible analysis storage, pg_trgm for fuzzy search |
| React | 19.2.4 | Frontend SPA | Modern hooks-based architecture |
| TypeScript | ~6.0.2 | Type-safe JS | Compile-time safety |
| Vite | 8.0.4 | Build tool/dev server | Fast HMR, ESM-native |
| Tailwind CSS | 4.2.2 | Styling | Utility-first, no CSS-in-JS runtime |
| Redis | 9.7.0 (go-redis) | Cache, queues, rate limiting | Optional; degrades gracefully without it |
| JWT (golang-jwt) | 5.3.1 | Auth tokens | Stateless auth |
| lib/pq | 1.12.3 | PostgreSQL driver | Mature, standard library |
| Méthode | Route | Objectif |
|---|---|---|
| POST | /auth/login | User login (admin/coach) |
| POST | /student/login | Student login (student_code) |
| POST | /student/assignments/:id/start | Start exam attempt |
| POST | /student/assignments/:id/submit | Submit exam answers |
| POST | /student/assignments/:id/autosave | Autosave answers |
| POST | /student/assignments/:id/video-chunk | Upload video chunk |
| GET | /admin/tests | List tests |
| POST | /admin/tests | Create test |
| POST | /admin/assignments | Create assignment |
| POST | /admin/sqi/compute | Compute SQI for attempt |
| POST | /admin/sqi/compute-batch | Batch SQI computation |
| GET | /admin/students/:id/sqi | Get student SQI metrics |
| GET | /super-admin/tenants | List tenants |
| POST | /super-admin/plans | Create subscription plan |
| GET | /health | Health check (PostgreSQL ping) |
| Colonne | Type | Notes |
|---|---|---|
| idPK | SERIAL | Auto-generated primary key |
| name | VARCHAR(100) | Organization/institute name |
| created_at | TIMESTAMP | Default NOW() |
| suspended_at | TIMESTAMP | Null if active |
| plan_id | INT | FK to subscription_plans (reference only) |
Multi-tenant root entity. Every other table has tenant_id FK with CASCADE delete.
| Colonne | Type | Notes |
|---|---|---|
| idPK | SERIAL | Auto-generated primary key |
| tenant_id | INT | FK → tenants CASCADE |
| VARCHAR(100) | UNIQUE NOT NULL | |
| password | TEXT | bcrypt hashed |
| role | VARCHAR(20) | CHECK super_admin/admin/coach |
Students do NOT have user accounts; they authenticate via student_code.
| Colonne | Type | Notes |
|---|---|---|
| idPK | SERIAL | Auto-generated primary key |
| tenant_id | INT | FK → tenants CASCADE |
| coach_id | INT | FK → coaches CASCADE |
| student_code | VARCHAR(50) | Auth credential (no password) |
| name | VARCHAR(100) | Student display name |
| batch_id | INT | FK → batches (optional) |
Partial unique index on (tenant_id, student_code) WHERE deleted_at IS NULL.
| Colonne | Type | Notes |
|---|---|---|
| idPK | SERIAL | Auto-generated primary key |
| tenant_id | INT | FK → tenants CASCADE |
| title | TEXT | Test title |
| subject_id | INT | FK → subjects |
| coach_id | INT | FK → coaches CASCADE |
| duration | INT | Duration in seconds |
| subject_name | VARCHAR(100) | Denormalized for SQI display |
subject_name denormalized in migration 6 to avoid JOIN for SQI.
| Colonne | Type | Notes |
|---|---|---|
| idPK | SERIAL | Auto-generated primary key |
| test_id | INT | FK → tests CASCADE |
| question_text | TEXT | NOT NULL |
| correct_answer | CHAR(1) | CHECK A/B/C/D |
| marks | FLOAT | Positive marks |
| neg_marks | FLOAT | Negative marks |
| importance | VARCHAR(10) | high/medium/low |
| difficulty | CHAR(1) | E(asy)/M(edium)/H(ard) |
| expected_time | FLOAT | Expected time in seconds |
| concept_tag | VARCHAR(100) | Concept grouping |
MCQ questions with metadata for SQI weight computation.
| Colonne | Type | Notes |
|---|---|---|
| idPK | SERIAL | Auto-generated primary key |
| attempt_id | INT | UNIQUE FK → attempts CASCADE |
| sqi_score | FLOAT | Overall SQI score |
| raw_score | FLOAT | Raw marks scored |
| analysis_json | JSONB | Full DiagnosticPayloadV2 |
| version | VARCHAR(10) | Default 'v2' |
SQI analysis stored as JSONB — schema evolves without migrations.
tenants → users/coaches/students/subjects/tests/batches (1:N, tenant_id FK CASCADE). coaches → students (1:N). tests → questions (1:N CASCADE). students → assignments → attempts → answer_logs → attempt_results. 22+ tables total across 18 migrations.
Décision : Every table has tenant_id FK with CASCADE delete instead of schema-per-tenant
Pourquoi : Simpler than schema-per-tenant; adequate for expected scale
Avantages : Single database, simple queries, easy cross-tenant analytics
Compromis : Must include tenant_id in every query; no physical data isolation
Décision : Students login with student_code only, no password or user account
Pourquoi : Students are numerous, short-lived, and don't need full user accounts
Avantages : Simplified onboarding, no password management
Compromis : Weaker auth (code-only), no email/password recovery
Décision : SQI v2 engine has no DB dependencies; takes inputs, returns payload
Pourquoi : Separation of analytics from data access; testable in isolation
Avantages : Unit testable, deterministic, reusable
Compromis : Requires data to be fetched and assembled before calling
Décision : Store full DiagnosticPayloadV2 as JSONB in attempt_results
Pourquoi : Schema evolves faster than migrations; analysis payload is read-heavy, write-once
Avantages : No migration needed for analysis schema changes; flexible querying
Compromis : No referential integrity on nested data; harder to query specific fields
Décision : Redis is optional — in-process queue and in-memory rate limiter as fallbacks
Pourquoi : Development simplicity; single-binary deployment possible
Avantages : Works without Redis in development; scales with Redis in production
Compromis : In-process queue loses jobs on restart; in-memory rate limiter isn't shared
Décision : ffmpeg concat on first stream request, not on upload
Pourquoi : Most videos are never watched; avoid wasted compute
Avantages : Saves storage and compute for unwatched recordings
Compromis : First viewer experiences delay; requires ffmpeg on server