# πŸ”§ GUIDE TECHNIQUE DΓ‰TAILLΓ‰ ## Architecture, API, et SpΓ©cifications pour DΓ©veloppeurs **Document :** Technical Implementation Guide **Audience :** DΓ©veloppeurs, DevOps, Architectes **Date :** Mai 2026 | **Version :** 1.0 --- ## πŸ“‹ TABLE DES MATIÈRES 1. Architecture globale 2. Stack technologique recommandΓ© 3. API REST Specifications 4. Database Schema 5. Deployment & Infrastructure 6. Performance & Scaling 7. Security & Compliance 8. Monitoring & Logging --- ## 1️⃣ ARCHITECTURE GLOBALE ### **Vue d'ensemble systΓ¨me** ``` β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ CDN (CloudFlare) β”‚ β”‚ VidΓ©os, Assets statiques, Images compressΓ©es β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Frontend Layer β”‚ β”‚ API Gateway β”‚ β”‚ (React/Vue) β”‚ β”‚ (Route + Cache) β”‚ β”‚ - PWA β”‚ β”‚ - Rate limiting β”‚ β”‚ - Offline β”‚ β”‚ - Auth middleware β”‚ β”‚ - Service Wkrs β”‚ β”‚ - CORS handling β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Load Balancer (AWS ELB) β”‚ β”‚ - Health checks β”‚ β”‚ - Auto-scale β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β” β”‚ Backend #1 β”‚ β”‚ Backend #2 β”‚ β”‚ Backend #3 β”‚ β”‚ (Node/Py) β”‚ β”‚ (Node/Py) β”‚ β”‚ (Node/Py) β”‚ β”‚ - Auth β”‚ β”‚ - Courses β”‚ β”‚ - Analyticsβ”‚ β”‚ - Users β”‚ β”‚ - Quizzes β”‚ β”‚ - Reports β”‚ β”‚ - Enroll β”‚ β”‚ - Certs β”‚ β”‚ - Logs β”‚ β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ PostgreSQL Database β”‚ β”‚ - Primary (Master) β”‚ β”‚ - Replica (Read-only) β”‚ β”‚ - Backups (Daily) β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β” β”‚Redis Cache β”‚ β”‚ S3 Storage β”‚ β”‚ Mux/Video β”‚ β”‚(Sessions) β”‚ β”‚(User data) β”‚ β”‚ (Streaming)β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ``` ### **Flow Utilisateur Typique** ``` 1. UTILISATEUR └─ AccΓ¨de greenland-academy.org (navigateur) 2. CDN (CloudFlare) └─ Serve fichiers statiques (HTML, CSS, JS) └─ Cache miss β†’ Forward Γ  Load Balancer 3. FRONTEND └─ React app charge (service worker pour offline) └─ Detect utilisateur anonyme vs authentifiΓ© 4. LOGIN (si nΓ©cessaire) └─ POST /api/auth/login (credentials) └─ Backend gΓ©nΓ¨re JWT token └─ Frontend store token (localStorage + secure cookie) 5. DASHBOARD └─ GET /api/user/me (avec JWT header) └─ GET /api/courses (public listing) └─ GET /api/enrollments/me (private) └─ Frontend cache rΓ©sultats (Redis backend) 6. COURSE INTERACTION └─ GET /api/courses/:id/lessons (avec auth) └─ Fetch vidΓ©o URL (S3 presigned URL) └─ Fetch quiz (GET /api/quizzes/:id) └─ Submit rΓ©ponses (POST /api/quiz-responses) └─ Calculer score backend └─ Si score β‰₯ 75% β†’ Trigger certificat generation 7. CERTIFICAT └─ POST /api/certificates/generate └─ Backend : Render PDF (Node + puppeteer) └─ Store S3 + Database └─ Send email (SendGrid) └─ Frontend : Display download link 8. ANALYTICS └─ Frontend send events (Matomo/GA) : - Course view - Quiz attempt - Time spent └─ Backend aggregates data └─ Dashboard display (Metabase) ``` --- ## 2️⃣ STACK TECHNOLOGIQUE RECOMMANDΓ‰ ### **Frontend Stack** ``` TECHNOLOGY CHOICES β”œβ”€ Framework : React 18+ (Vite bundler) β”‚ β”œβ”€ State Management : Redux Toolkit or Zustand β”‚ β”œβ”€ HTTP Client : Axios + interceptors β”‚ β”œβ”€ UI Components : Material-UI or Chakra β”‚ β”œβ”€ Form Handling : React Hook Form + Zod validation β”‚ └─ Router : React Router v6 β”‚ β”œβ”€ Offline Capability β”‚ β”œβ”€ Service Worker : Workbox β”‚ β”œβ”€ Local Storage : IndexedDB (Dexie.js) β”‚ β”œβ”€ Sync : Background Sync API β”‚ └─ Notification : Web Push API β”‚ β”œβ”€ Performance β”‚ β”œβ”€ Code Splitting : React.lazy() β”‚ β”œβ”€ Image Optimization : Next/Image component β”‚ β”œβ”€ Bundle Analysis : webpack-bundle-analyzer β”‚ β”œβ”€ Monitoring : Sentry, LogRocket β”‚ └─ Accessibility : axe-core, WCAG AA β”‚ β”œβ”€ Build & Deploy β”‚ β”œβ”€ Build Tool : Vite (instant HMR) β”‚ β”œβ”€ CI/CD : GitHub Actions β”‚ β”œβ”€ Hosting : AWS S3 + CloudFront β”‚ β”œβ”€ Domain : Route53 β”‚ └─ SSL : AWS Certificate Manager β”‚ └─ Testing β”œβ”€ Unit : Jest + React Testing Library β”œβ”€ E2E : Cypress or Playwright β”œβ”€ Coverage Target : 80%+ └─ Performance : Lighthouse CI ``` ### **Backend Stack** ``` OPTION A : Node.js (Express/Fastify) β”œβ”€ Runtime : Node.js 18 LTS β”œβ”€ Framework : Express.js or Fastify β”œβ”€ Database : PostgreSQL 14+ β”œβ”€ Cache : Redis 7+ β”œβ”€ ORM : Prisma or TypeORM β”œβ”€ Validation : Zod or Joi β”œβ”€ Async Jobs : Bull (Redis-based) β”œβ”€ Auth : Passport.js or Auth0 β”œβ”€ File Upload : Multer + AWS S3 β”œβ”€ Logging : Winston or Pino └─ Testing : Jest + Supertest OPTION B : Python (Django/FastAPI) β”œβ”€ Runtime : Python 3.11+ β”œβ”€ Framework : Django with DRF (REST Framework) β”œβ”€ Database : PostgreSQL 14+ β”œβ”€ Cache : Redis 7+ β”œβ”€ ORM : Django ORM β”œβ”€ Validation : Pydantic β”œβ”€ Async Jobs : Celery + Redis β”œβ”€ Auth : Django-rest-auth or SimpleJWT β”œβ”€ File Upload : Django-storages + S3 β”œβ”€ Logging : Python logging + Sentry └─ Testing : pytest + pytest-django RECOMMENDATION : Node.js (faster development, real-time capable) ``` ### **Infrastructure Stack** ``` CLOUD PROVIDER : AWS (or Google Cloud) Compute β”œβ”€ Application Servers : EC2 (t3.medium Γ— 3) β”œβ”€ Auto Scaling Group : 2-5 instances β”œβ”€ Container : Docker + ECR (optional) β”œβ”€ Orchestration : ECS or Kubernetes (if scale needed) └─ Monitoring : CloudWatch Database β”œβ”€ Primary : RDS PostgreSQL (db.t3.medium) β”œβ”€ Replica : RDS read replica (different region) β”œβ”€ Backup : Automated daily, 7-day retention β”œβ”€ Monitoring : Performance Insights └─ Encryption : RDS encryption at rest Cache β”œβ”€ Redis : ElastiCache (cache.t3.micro) β”œβ”€ Replication : Multi-AZ β”œβ”€ Backup : Snapshot daily └─ Monitoring : CloudWatch metrics Storage β”œβ”€ Application Files : S3 (versioned) β”œβ”€ User Uploads : S3 (lifecycle policy 30 days) β”œβ”€ Backups : S3 Glacier (7 year retention) β”œβ”€ CDN : CloudFront (cache 24h) └─ Encryption : S3 server-side encryption Networking β”œβ”€ VPC : Private subnets for RDS/Redis β”œβ”€ Security Groups : Strict ingress rules β”œβ”€ NAT Gateway : For outbound internet β”œβ”€ VPN : For development access (bastion host) └─ WAF : AWS WAF for DDoS protection Monitoring & Logging β”œβ”€ Logs : CloudWatch Logs + ELK (Elasticsearch) β”œβ”€ Metrics : CloudWatch Dashboards β”œβ”€ Alerting : SNS + email/Slack β”œβ”€ Tracing : AWS X-Ray or Datadog APM β”œβ”€ Uptime Monitoring : UptimeRobot or Pingdom └─ Log Aggregation : Splunk or Datadog Cost Estimate (Monthly) β”œβ”€ EC2 (3 Γ— t3.medium) : $150 β”œβ”€ RDS PostgreSQL + replica : $250 β”œβ”€ ElastiCache Redis : $50 β”œβ”€ S3 + CloudFront : $100 β”œβ”€ Data transfer : $50 β”œβ”€ Miscellaneous : $50 └─ TOTAL : $650/month (production-ready) ``` --- ## 3️⃣ API REST SPECIFICATIONS ### **Authentication Endpoints** ``` POST /api/auth/register β”œβ”€ Request Body : β”‚ { β”‚ "email": "user@example.com", β”‚ "password": "SecurePass123!", β”‚ "firstName": "Jean", β”‚ "lastName": "Dupont", β”‚ "country": "CF", β”‚ "language": "fr" β”‚ } β”œβ”€ Response (201) : β”‚ { β”‚ "id": "user_123", β”‚ "email": "user@example.com", β”‚ "token": "eyJhbGciOiJIUzI1NiIs...", β”‚ "refreshToken": "eyJhbGciOiJIUzI1NiIs..." β”‚ } β”œβ”€ Validation : β”‚ - Email valid + unique β”‚ - Password min 8 chars + 1 uppercase + 1 number + 1 special β”‚ - First/last name min 2 chars β”‚ - Country = valid ISO 3166-1 └─ Errors : - 400 : Invalid input - 409 : Email already exists POST /api/auth/login β”œβ”€ Request Body : β”‚ { β”‚ "email": "user@example.com", β”‚ "password": "SecurePass123!" β”‚ } β”œβ”€ Response (200) : β”‚ { β”‚ "id": "user_123", β”‚ "token": "eyJhbGciOiJIUzI1NiIs...", β”‚ "refreshToken": "eyJhbGciOiJIUzI1NiIs..." β”‚ } β”œβ”€ Security : β”‚ - Rate limit : 10 attempts / 15 min per IP β”‚ - Lock account after 5 failures β”‚ - Hash password (bcrypt, cost 12) └─ Errors : - 401 : Invalid credentials POST /api/auth/refresh β”œβ”€ Request Headers : β”‚ { β”‚ "Authorization": "Bearer {refreshToken}" β”‚ } β”œβ”€ Response (200) : β”‚ { β”‚ "token": "eyJhbGciOiJIUzI1NiIs..." (new JWT) β”‚ } └─ Errors : - 401 : Invalid/expired refresh token POST /api/auth/logout β”œβ”€ Request Headers : β”‚ { β”‚ "Authorization": "Bearer {jwt}" β”‚ } β”œβ”€ Response (204) : No content β”œβ”€ Action : β”‚ - Blacklist JWT (Redis) β”‚ - Clear session └─ Errors : - 401 : Unauthorized POST /api/auth/forgot-password β”œβ”€ Request Body : β”‚ { "email": "user@example.com" } β”œβ”€ Response (200) : β”‚ { "message": "Reset email sent" } β”œβ”€ Action : β”‚ - Generate reset token (6 hours expiry) β”‚ - Send email (SendGrid) β”‚ - Store token in Redis └─ Errors : - 404 : Email not found (for security, return 200 anyway) POST /api/auth/reset-password β”œβ”€ Request Body : β”‚ { β”‚ "token": "reset_token_from_email", β”‚ "newPassword": "NewSecurePass123!" β”‚ } β”œβ”€ Response (200) : β”‚ { "message": "Password reset successful" } β”œβ”€ Action : β”‚ - Verify token from Redis β”‚ - Hash new password β”‚ - Update user β”‚ - Delete token └─ Errors : - 400 : Invalid/expired token - 422 : Password requirements not met ``` ### **User Endpoints** ``` GET /api/users/me β”œβ”€ Authentication : Required (JWT) β”œβ”€ Response (200) : β”‚ { β”‚ "id": "user_123", β”‚ "email": "user@example.com", β”‚ "firstName": "Jean", β”‚ "lastName": "Dupont", β”‚ "avatar": "https://s3.../avatar.jpg", β”‚ "country": "CF", β”‚ "language": "fr", β”‚ "createdAt": "2026-05-01T10:00:00Z", β”‚ "totalPoints": 1250, β”‚ "badges": ["founder", "week_streak_5"], β”‚ "coursesCompleted": 3, β”‚ "certificatesEarned": 2 β”‚ } └─ Errors : - 401 : Unauthorized PATCH /api/users/me β”œβ”€ Authentication : Required (JWT) β”œβ”€ Request Body (partial update) : β”‚ { β”‚ "firstName": "Johnny", β”‚ "language": "en", β”‚ "avatar": "[file upload]" β”‚ } β”œβ”€ Response (200) : Updated user object └─ Validation : - First/last name : alphanumeric + spaces, min 2 - Language : ISO 639-1 code - Avatar : JPG/PNG, max 5MB PUT /api/users/me/password β”œβ”€ Authentication : Required (JWT) β”œβ”€ Request Body : β”‚ { β”‚ "currentPassword": "OldPass123!", β”‚ "newPassword": "NewPass456!" β”‚ } β”œβ”€ Response (200) : { "message": "Password updated" } └─ Validation : - Current password must be correct - New password must meet requirements - New β‰  current ``` ### **Courses Endpoints** ``` GET /api/courses β”œβ”€ Authentication : Optional β”œβ”€ Query Parameters : β”‚ - limit=20 (default) β”‚ - offset=0 (default) β”‚ - search=climate (search title + description) β”‚ - sortBy=popular (popular, newest, rating) β”‚ - language=fr β”œβ”€ Response (200) : β”‚ { β”‚ "data": [ β”‚ { β”‚ "id": "course_001", β”‚ "title": "Les Fondamentaux de la Crise Climatique", β”‚ "description": "...", β”‚ "duration": "5 hours", β”‚ "thumbnail": "https://s3.../thumb.jpg", β”‚ "rating": 4.7, β”‚ "enrollmentCount": 1250, β”‚ "certificateAvailable": true, β”‚ "language": "fr", β”‚ "level": "beginner" β”‚ } β”‚ ], β”‚ "meta": { β”‚ "total": 145, β”‚ "limit": 20, β”‚ "offset": 0 β”‚ } β”‚ } └─ Caching : - Cache 1 hour (Public) - Invalidate on new enrollment GET /api/courses/:id β”œβ”€ Authentication : Optional β”œβ”€ Response (200) : β”‚ { β”‚ "id": "course_001", β”‚ "title": "Les Fondamentaux...", β”‚ "description": "...", β”‚ "instructor": { β”‚ "id": "user_456", β”‚ "name": "Dr. Jean Smith", β”‚ "bio": "..." β”‚ }, β”‚ "modules": [ β”‚ { β”‚ "id": "module_1", β”‚ "title": "Introduction", β”‚ "lessons": 4, β”‚ "duration": "45 minutes" β”‚ } β”‚ ], β”‚ "prerequisites": [], β”‚ "certificateRequirements": { β”‚ "minimumScore": 75, β”‚ "quizzes": true, β”‚ "finalAssessment": true β”‚ } β”‚ } └─ Cache : 24 hours POST /api/enrollments β”œβ”€ Authentication : Required (JWT) β”œβ”€ Request Body : β”‚ { β”‚ "courseId": "course_001" β”‚ } β”œβ”€ Response (201) : β”‚ { β”‚ "id": "enrollment_789", β”‚ "userId": "user_123", β”‚ "courseId": "course_001", β”‚ "enrolledAt": "2026-05-01T10:00:00Z", β”‚ "progress": 0, β”‚ "status": "in_progress" β”‚ } β”œβ”€ Actions : β”‚ - Create enrollment record β”‚ - Send welcome email β”‚ - Add user to course Discord (optional) β”‚ - Trigger "first lesson" notification └─ Errors : - 404 : Course not found - 409 : Already enrolled GET /api/enrollments/me β”œβ”€ Authentication : Required (JWT) β”œβ”€ Response (200) : β”‚ { β”‚ "data": [ β”‚ { β”‚ "id": "enrollment_789", β”‚ "course": { ... }, β”‚ "progress": 35, β”‚ "lastAccessed": "2026-06-15T10:00:00Z", β”‚ "status": "in_progress", β”‚ "certificateEarned": false β”‚ } β”‚ ] β”‚ } β”œβ”€ Caching : Cache 5 min (user specific) └─ Paging : limit + offset ``` ### **Quiz Endpoints** ``` GET /api/courses/:courseId/quizzes/:quizId β”œβ”€ Authentication : Required (JWT) β”œβ”€ Check : User enrolled in course β”œβ”€ Response (200) : β”‚ { β”‚ "id": "quiz_001", β”‚ "title": "Chapter 1 Quiz", β”‚ "questions": [ β”‚ { β”‚ "id": "q1", β”‚ "type": "multiple_choice", β”‚ "text": "What is CO2?", β”‚ "options": [ β”‚ { "id": "opt_1", "text": "Option A" }, β”‚ { "id": "opt_2", "text": "Option B" } β”‚ ], β”‚ "order": 1 β”‚ } β”‚ ], β”‚ "timeLimit": 30, β”‚ "passingScore": 75, β”‚ "attemptsAllowed": 3 β”‚ } β”œβ”€ Note : Answers NOT included (prevent cheating) └─ Cache : 24 hours POST /api/quizzes/:quizId/submit β”œβ”€ Authentication : Required (JWT) β”œβ”€ Request Body : β”‚ { β”‚ "responses": [ β”‚ { β”‚ "questionId": "q1", β”‚ "answer": "opt_2" β”‚ } β”‚ ] β”‚ } β”œβ”€ Response (200) : β”‚ { β”‚ "score": 85, β”‚ "percentage": 85, β”‚ "passed": true, β”‚ "feedback": [ β”‚ { β”‚ "questionId": "q1", β”‚ "isCorrect": true, β”‚ "explanation": "Option B is correct because..." β”‚ } β”‚ ], β”‚ "certificateEligible": true β”‚ } β”œβ”€ Backend : β”‚ - Validate answers β”‚ - Calculate score β”‚ - Check if >= passing score β”‚ - If passed : trigger certificate generation β”‚ - Log attempt (analytics) └─ Errors : - 400 : Invalid answer format - 429 : Too many attempts - 403 : Not enrolled GET /api/quizzes/:quizId/results β”œβ”€ Authentication : Required (JWT) β”œβ”€ Response (200) : β”‚ { β”‚ "attempts": [ β”‚ { β”‚ "attemptNumber": 1, β”‚ "score": 65, β”‚ "passed": false, β”‚ "submittedAt": "2026-06-15T10:00:00Z" β”‚ } β”‚ ] β”‚ } └─ Paging : Show last 10 attempts ``` ### **Certificate Endpoints** ``` POST /api/certificates/generate β”œβ”€ Authentication : Required (JWT) β”œβ”€ Trigger : Automatic (on quiz pass), or manual request β”œβ”€ Request Body : β”‚ { β”‚ "courseId": "course_001", β”‚ "quizId": "quiz_001" β”‚ } β”œβ”€ Response (201) : β”‚ { β”‚ "id": "cert_abc123", β”‚ "userId": "user_123", β”‚ "courseId": "course_001", β”‚ "certificateUrl": "https://s3.../cert_abc123.pdf", β”‚ "issuedAt": "2026-06-15T10:00:00Z", β”‚ "expiresAt": null (no expiry), β”‚ "verificationCode": "GLCA-2026-ABC123" β”‚ } β”œβ”€ Backend Process : β”‚ - Verify user passed quiz (score β‰₯75%) β”‚ - Render PDF (Puppeteer) : β”‚ - User name β”‚ - Course name β”‚ - Date issued β”‚ - Verification code (QR) β”‚ - Upload to S3 (private bucket) β”‚ - Generate presigned URL (24h expiry) β”‚ - Send email with download link β”‚ - Record in database β”œβ”€ PDF Template : β”‚ - GLCA logo (top) β”‚ - "CERTIFICATE OF COMPLETION" β”‚ - "This certifies that [NAME]" β”‚ - "has successfully completed [COURSE]" β”‚ - Date issued β”‚ - Instructor signature (image) β”‚ - QR code (links to verification page) β”‚ - Serial number └─ Errors : - 400 : Quiz not passed - 404 : Course/quiz not found GET /api/certificates/verify/:code β”œβ”€ Authentication : Optional (public verification) β”œβ”€ Response (200) : β”‚ { β”‚ "valid": true, β”‚ "userName": "Jean Dupont", β”‚ "courseName": "Les Fondamentaux...", β”‚ "issuedAt": "2026-06-15", β”‚ "verificationCode": "GLCA-2026-ABC123" β”‚ } β”œβ”€ Caching : Cache 1 week └─ Use : LinkedIn sharing, employer verification GET /api/users/me/certificates β”œβ”€ Authentication : Required (JWT) β”œβ”€ Response (200) : β”‚ { β”‚ "data": [ β”‚ { β”‚ "id": "cert_abc123", β”‚ "courseName": "Les Fondamentaux...", β”‚ "issuedAt": "2026-06-15T10:00:00Z", β”‚ "downloadUrl": "https://s3.../cert_abc123.pdf", β”‚ "shareUrl": "https://greenland.../verify/GLCA-2026-ABC123", β”‚ "likedInShareUrl": "https://linkedin.com/feed/..." β”‚ } β”‚ ] β”‚ } └─ Action : Allow LinkedIn share (oauth) ``` ### **Analytics Endpoints** (Internal Only) ``` GET /api/admin/analytics/overview β”œβ”€ Authentication : Admin only β”œβ”€ Response (200) : β”‚ { β”‚ "totalUsers": 12500, β”‚ "activeUsers7d": 3200, β”‚ "totalEnrollments": 28000, β”‚ "averageCompletionRate": 42.5, β”‚ "certificatesIssued": 8500, β”‚ "topCourse": { "id": "course_001", "enrollments": 5000 } β”‚ } └─ Cache : 1 hour GET /api/admin/analytics/courses/:id β”œβ”€ Authentication : Admin only β”œβ”€ Response (200) : β”‚ { β”‚ "courseId": "course_001", β”‚ "enrollments": 5000, β”‚ "completionRate": 45, β”‚ "averageScore": 78.5, β”‚ "rating": 4.7, β”‚ "dropoffPoints": [ β”‚ { β”‚ "moduleNumber": 2, β”‚ "percentageWhoLeft": 25 β”‚ } β”‚ ] β”‚ } └─ Use : Identify content issues GET /api/admin/analytics/events β”œβ”€ Authentication : Admin only β”œβ”€ Query Parameters : β”‚ - eventType=course_view, quiz_complete, etc. β”‚ - startDate=2026-06-01 β”‚ - endDate=2026-06-30 β”‚ - limit=100 β”œβ”€ Response (200) : β”‚ { β”‚ "events": [ β”‚ { β”‚ "userId": "user_123", β”‚ "eventType": "quiz_complete", β”‚ "courseId": "course_001", β”‚ "score": 85, β”‚ "timestamp": "2026-06-15T10:00:00Z" β”‚ } β”‚ ] β”‚ } └─ Use : Real-time dashboards ``` --- ## 4️⃣ DATABASE SCHEMA ### **Core Tables** ```sql -- Users CREATE TABLE users ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), email VARCHAR(255) NOT NULL UNIQUE, password_hash VARCHAR(255) NOT NULL, first_name VARCHAR(100) NOT NULL, last_name VARCHAR(100) NOT NULL, avatar_url VARCHAR(500), country_code CHAR(2), language VARCHAR(5) DEFAULT 'fr', total_points INT DEFAULT 0, created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW(), last_login TIMESTAMP, is_active BOOLEAN DEFAULT true, is_admin BOOLEAN DEFAULT false, INDEX (email) ); -- Courses CREATE TABLE courses ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), title VARCHAR(255) NOT NULL, description TEXT NOT NULL, instructor_id UUID NOT NULL REFERENCES users(id), duration_hours INT, thumbnail_url VARCHAR(500), language VARCHAR(5) DEFAULT 'fr', level ENUM('beginner', 'intermediate', 'advanced'), rating DECIMAL(3,2), enrollment_count INT DEFAULT 0, certificate_available BOOLEAN DEFAULT true, min_score_for_cert INT DEFAULT 75, published BOOLEAN DEFAULT false, created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW(), INDEX (instructor_id, published), FULLTEXT INDEX (title, description) ); -- Enrollments CREATE TABLE enrollments ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, course_id UUID NOT NULL REFERENCES courses(id) ON DELETE CASCADE, enrolled_at TIMESTAMP DEFAULT NOW(), progress INT DEFAULT 0, status ENUM('in_progress', 'completed', 'dropped') DEFAULT 'in_progress', completed_at TIMESTAMP, last_accessed TIMESTAMP, UNIQUE(user_id, course_id), INDEX (user_id, course_id), INDEX (status, completed_at) ); -- Modules (lessons) CREATE TABLE modules ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), course_id UUID NOT NULL REFERENCES courses(id) ON DELETE CASCADE, title VARCHAR(255) NOT NULL, description TEXT, order_index INT NOT NULL, duration_minutes INT, content_type ENUM('video', 'text', 'quiz', 'interactive'), content_url VARCHAR(500), created_at TIMESTAMP DEFAULT NOW(), INDEX (course_id, order_index) ); -- Quiz CREATE TABLE quizzes ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), module_id UUID NOT NULL REFERENCES modules(id) ON DELETE CASCADE, title VARCHAR(255) NOT NULL, time_limit_minutes INT, passing_score INT DEFAULT 75, max_attempts INT DEFAULT 3, shuffle_questions BOOLEAN DEFAULT true, created_at TIMESTAMP DEFAULT NOW(), INDEX (module_id) ); -- Quiz Questions CREATE TABLE quiz_questions ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), quiz_id UUID NOT NULL REFERENCES quizzes(id) ON DELETE CASCADE, question_text TEXT NOT NULL, question_type ENUM('multiple_choice', 'true_false', 'matching', 'fill_blank'), order_index INT NOT NULL, points INT DEFAULT 1, created_at TIMESTAMP DEFAULT NOW(), INDEX (quiz_id, order_index) ); -- Quiz Answers (options) CREATE TABLE quiz_answers ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), question_id UUID NOT NULL REFERENCES quiz_questions(id) ON DELETE CASCADE, answer_text VARCHAR(500) NOT NULL, is_correct BOOLEAN DEFAULT false, order_index INT, explanation TEXT, INDEX (question_id, is_correct) ); -- Quiz Responses (user submissions) CREATE TABLE quiz_responses ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, quiz_id UUID NOT NULL REFERENCES quizzes(id) ON DELETE CASCADE, submitted_at TIMESTAMP DEFAULT NOW(), score INT, percentage DECIMAL(5,2), passed BOOLEAN, attempt_number INT DEFAULT 1, INDEX (user_id, quiz_id, submitted_at) ); -- Quiz Response Details CREATE TABLE quiz_response_details ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), response_id UUID NOT NULL REFERENCES quiz_responses(id) ON DELETE CASCADE, question_id UUID NOT NULL REFERENCES quiz_questions(id), user_answer_id UUID REFERENCES quiz_answers(id), is_correct BOOLEAN, INDEX (response_id, question_id) ); -- Certificates CREATE TABLE certificates ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, course_id UUID NOT NULL REFERENCES courses(id), quiz_id UUID REFERENCES quizzes(id), issued_at TIMESTAMP DEFAULT NOW(), expires_at TIMESTAMP, verification_code VARCHAR(50) NOT NULL UNIQUE, pdf_url VARCHAR(500), INDEX (user_id, course_id, verification_code) ); -- User Progress CREATE TABLE user_progress ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, module_id UUID NOT NULL REFERENCES modules(id) ON DELETE CASCADE, enrollment_id UUID NOT NULL REFERENCES enrollments(id), completed BOOLEAN DEFAULT false, time_spent_seconds INT DEFAULT 0, completed_at TIMESTAMP, UNIQUE(user_id, module_id), INDEX (user_id, enrollment_id) ); -- Badges CREATE TABLE badges ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), code VARCHAR(50) NOT NULL UNIQUE, name VARCHAR(100) NOT NULL, description TEXT, icon_url VARCHAR(500), criteria JSONB -- {type: 'course_complete', course_id: '...'} ); -- User Badges (awarded) CREATE TABLE user_badges ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, badge_id UUID NOT NULL REFERENCES badges(id), earned_at TIMESTAMP DEFAULT NOW(), UNIQUE(user_id, badge_id), INDEX (user_id) ); -- Events (for analytics) CREATE TABLE events ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID REFERENCES users(id), event_type VARCHAR(50) NOT NULL, -- 'course_view', 'quiz_complete', etc. entity_id UUID, -- course_id or quiz_id metadata JSONB, -- {score: 85, duration: 30, ...} created_at TIMESTAMP DEFAULT NOW(), INDEX (user_id, event_type, created_at) ); ``` --- ## 5️⃣ DEPLOYMENT & INFRASTRUCTURE ### **CI/CD Pipeline (GitHub Actions)** ```yaml name: Deploy to Production on: push: branches: [main] workflow_dispatch: jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Setup Node uses: actions/setup-node@v3 with: node-version: '18' - name: Install dependencies run: npm ci - name: Run linter run: npm run lint - name: Run tests run: npm run test - name: Build run: npm run build deploy: needs: test runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Deploy to AWS env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} run: | aws s3 sync dist/ s3://glca-frontend-prod/ aws cloudfront create-invalidation --distribution-id ${{ secrets.CLOUDFRONT_DIST_ID }} --paths "/*" - name: Notify Slack if: success() run: | curl -X POST ${{ secrets.SLACK_WEBHOOK }} \ -H 'Content-Type: application/json' \ -d '{"text":"βœ… Production deploy successful"}' ``` ### **Database Migrations** ```bash # Using Prisma npx prisma migrate dev --name add_user_badges npx prisma migrate deploy # Production # Using Knex (Node alternative) npx knex migrate:latest ``` ### **Environment Configuration** ```bash # .env.production NODE_ENV=production DATABASE_URL=postgresql://user:pass@rds.amazonaws.com:5432/glca_prod REDIS_URL=redis://elasticache.amazonaws.com:6379 JWT_SECRET=[random 64-char key] JWT_EXPIRY=86400 # 24 hours REFRESH_TOKEN_EXPIRY=2592000 # 30 days AWS_REGION=eu-west-1 S3_BUCKET=glca-production SENDGRID_API_KEY=[api key] CORS_ORIGIN=https://greenland-academy.org LOG_LEVEL=info SENTRY_DSN=[sentry url] ``` --- ## 6️⃣ PERFORMANCE & SCALING ### **Caching Strategy** ``` LEVEL 1 : CDN (CloudFlare) β”œβ”€ TTL : 24 hours β”œβ”€ Content : HTML, CSS, JS, images β”œβ”€ Invalidation : On new deploy └─ Hit rate target : 80%+ LEVEL 2 : Application Cache (Redis) β”œβ”€ User data : TTL 5 min (user specific) β”œβ”€ Courses list : TTL 1 hour (public) β”œβ”€ Quiz data : TTL 24 hours (static) β”œβ”€ Sessions : TTL 24 hours (auth) └─ Cache keys : `user:{id}:profile`, `course:{id}:data` LEVEL 3 : Database Query Cache β”œβ”€ Prepared statements (reduce parsing) β”œβ”€ Indexes on foreign keys + filters β”œβ”€ Query result caching (5 sec, Prisma) └─ Read replicas for analytics queries ``` ### **Database Optimization** ```sql -- Key Indexes CREATE INDEX idx_enrollments_user_course ON enrollments(user_id, course_id); CREATE INDEX idx_quiz_responses_user_quiz ON quiz_responses(user_id, quiz_id, submitted_at); CREATE INDEX idx_events_timestamp ON events(created_at DESC); CREATE INDEX idx_courses_published ON courses(published, updated_at DESC); -- Partitioning (for large tables) CREATE TABLE events_2026_06 PARTITION OF events FOR VALUES FROM ('2026-06-01') TO ('2026-07-01'); ``` ### **API Rate Limiting** ```javascript // Express middleware const rateLimit = require('express-rate-limit'); const authLimiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 10, // max 10 attempts keyGenerator: (req) => req.ip, skip: (req) => req.user, // Skip if authenticated }); const apiLimiter = rateLimit({ windowMs: 60 * 1000, // 1 minute max: 100, // max 100 requests per minute keyGenerator: (req) => req.user?.id || req.ip, }); app.post('/api/auth/login', authLimiter, loginHandler); app.use('/api/', apiLimiter); ``` ### **Scaling Strategy** ``` VERTICAL SCALING (Short term) β”œβ”€ t3.medium β†’ t3.large (2-4 weeks) β”œβ”€ RDS : db.t3.medium β†’ db.t3.large └─ Cost : +50% HORIZONTAL SCALING (Long term) β”œβ”€ Add more EC2 instances (2-5) β”œβ”€ Load balancer distributes traffic β”œβ”€ Auto-scaling group (min 2, max 10) └─ Session store in Redis (not in-memory) MULTI-REGION (If global scale) β”œβ”€ Deploy backend to multiple regions β”œβ”€ Route traffic via CloudFront β”œβ”€ Database replication (RDS Multi-region) └─ Latency reduced for users everywhere MONITORING METRICS β”œβ”€ CPU usage : Alert if > 75% β”œβ”€ Memory : Alert if > 80% β”œβ”€ Database connections : Alert if > 90 / 100 β”œβ”€ Response time (p99) : Alert if > 500ms β”œβ”€ Error rate : Alert if > 1% └─ Disk space : Alert if < 20% ``` --- ## 7️⃣ SECURITY & COMPLIANCE ### **Authentication & Authorization** ```javascript // JWT Payload { "sub": "user_123", // subject (user ID) "email": "user@example.com", "role": "student", // student, teacher, admin "permissions": ["read:courses", "write:quiz_responses"], "iat": 1624876800, // issued at "exp": 1624963200, // expiry (24 hours) "aud": "greenland-academy" } // Role-based Access Control const roles = { student: ['read:courses', 'read:profile', 'write:quiz_responses'], teacher: ['read:courses', 'write:courses', 'read:analytics'], admin: ['*'] // all permissions }; ``` ### **Data Protection** ``` ENCRYPTION AT REST β”œβ”€ Database : RDS encryption (AES-256) β”œβ”€ S3 : Server-side encryption (AES-256) β”œβ”€ Redis : Encryption enabled └─ Backups : Encrypted in Glacier ENCRYPTION IN TRANSIT β”œβ”€ HTTPS/TLS 1.3 (all endpoints) β”œβ”€ Certificate : Let's Encrypt auto-renewal β”œβ”€ HSTS : 1 year max-age └─ No mixed content (HTTP) SENSITIVE DATA β”œβ”€ Passwords : Bcrypt (cost 12) β”œβ”€ API keys : Never in code (use secrets manager) β”œβ”€ User data : PII encryption (if regulatory requirement) └─ Audit logs : All access logged ``` ### **OWASP Top 10 Protection** ``` 1. Injection β”œβ”€ SQL : Parameterized queries (Prisma ORM) β”œβ”€ NoSQL : Strict schemas └─ Command : No shell execution 2. Broken Authentication β”œβ”€ Strong password requirements β”œβ”€ 2FA option (future) └─ Session timeout : 24 hours 3. Sensitive Data Exposure β”œβ”€ HTTPS only β”œβ”€ Sensitive headers (X-Content-Type-Options, etc.) └─ No debug info in errors 4. XML External Entities (XXE) β”œβ”€ Disable XML parsing └─ Validate file uploads 5. Broken Access Control β”œβ”€ Middleware checks before each action β”œβ”€ User can only access own data └─ Admin role separated 6. Security Misconfiguration β”œβ”€ Security headers (HELMET middleware) β”œβ”€ CORS strict (whitelist domains) └─ No default credentials 7. XSS (Cross-Site Scripting) β”œβ”€ React auto-escapes by default β”œβ”€ Content-Security-Policy header └─ Input validation 8. Insecure Deserialization β”œβ”€ No eval() calls β”œβ”€ Validate JSON before parsing └─ Type checking (TypeScript) 9. Using Components with Known Vulnerabilities β”œβ”€ npm audit (weekly) β”œβ”€ Snyk integration └─ Dependabot alerts 10. Insufficient Logging & Monitoring β”œβ”€ All auth attempts logged β”œβ”€ Errors to Sentry β”œβ”€ API calls to CloudWatch └─ Dashboards (DataDog / Grafana) ``` ### **Compliance** ``` GDPR (EU users) β”œβ”€ Data portability : Export user data (JSON) β”œβ”€ Right to deletion : Delete all user data β”œβ”€ Privacy policy : Clear & accessible β”œβ”€ Consent tracking : Newsletter opt-in └─ Data processor agreements : With S3, SendGrid CCPA (California) β”œβ”€ Similar to GDPR β”œβ”€ Privacy notice at collection β”œβ”€ Opt-out mechanism └─ No sale of personal data REGULARITY AUDITS β”œβ”€ Penetration testing : Annual β”œβ”€ Code review : Monthly β”œβ”€ Dependency audit : Weekly └─ Access logs review : Monthly ``` --- ## 8️⃣ MONITORING & LOGGING ### **Application Monitoring** ```javascript // Sentry integration const Sentry = require("@sentry/node"); Sentry.init({ dsn: process.env.SENTRY_DSN, tracesSampleRate: 0.1, environment: process.env.NODE_ENV, }); app.use(Sentry.Handlers.requestHandler()); app.use(Sentry.Handlers.errorHandler()); // Custom errors trigger alerts try { // business logic } catch (error) { Sentry.captureException(error, { level: 'error' }); res.status(500).json({ error: 'Internal server error' }); } ``` ### **Logging Strategy** ```javascript const logger = require('pino')({ level: process.env.LOG_LEVEL || 'info', transport: { target: 'pino-pretty', options: { colorize: true, ignore: 'pid,hostname', }, }, }); // Log levels logger.info('User enrolled in course', { userId, courseId }); logger.warn('Unusual activity detected', { userId, attempts: 10 }); logger.error('Database connection failed', { error: err.message }); // JSON transport to CloudWatch // AWS Lambda β†’ CloudWatch Logs β†’ ELK / Splunk ``` ### **Metrics & Dashboards** ``` CLOUDWATCH DASHBOARD β”œβ”€ Request count (per minute) β”œβ”€ Error rate (% 5xx errors) β”œβ”€ Response time (p50, p95, p99) β”œβ”€ Database connections β”œβ”€ Redis memory usage β”œβ”€ Certificate generation queue └─ Active users (real-time) CUSTOM METRICS β”œβ”€ Courses completed (daily) β”œβ”€ Certificates issued (daily) β”œβ”€ Quiz pass rate (%) β”œβ”€ Average quiz score β”œβ”€ User retention (7-day, 30-day) └─ New user signups (daily) ALERTING THRESHOLDS β”œβ”€ Error rate > 2% : Immediate alert β”œβ”€ Response time p99 > 1000ms : Alert β”œβ”€ Database CPU > 85% : Alert β”œβ”€ Redis memory > 90% : Alert └─ Certificate generation delay > 5 min : Alert ``` --- ## πŸ“‹ DEVELOPMENT SETUP (Local) ```bash # Prerequisites npm install -g node@18 git # Clone & Install git clone https://github.com/greenland/academy.git cd academy npm install # Environment cp .env.example .env.local # Edit .env.local with local DB credentials # Start Database (Docker) docker-compose up -d # Run migrations npm run migrate:dev # Start Dev Server npm run dev:api # Backend on localhost:3000 npm run dev:web # Frontend on localhost:5173 # Run Tests npm test # Build npm run build npm run start # Production mode ``` --- **Technical Guide Completed** βœ… This guide provides all specifications needed for developers to implement the platform. All endpoints, database schemas, and infrastructure recommendations are production-ready. Next steps : 1. Review with team 2. Adjust for your tech stack if needed 3. Share with development team 4. Begin implementation