{"id":47633368,"url":"https://github.com/rohanfosse/cursus","last_synced_at":"2026-05-10T11:21:37.535Z","repository":{"id":345397290,"uuid":"1182277867","full_name":"rohanfosse/cursus","owner":"rohanfosse","description":"Cursus - Plateforme pedagogique tout-en-un : messagerie, devoirs, quiz live, documents. Desktop (Electron) + Web (PWA) + Mobile.","archived":false,"fork":false,"pushed_at":"2026-04-27T15:22:05.000Z","size":20435,"stargazers_count":0,"open_issues_count":44,"forks_count":1,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-27T16:29:22.720Z","etag":null,"topics":["docker","education","electron","lms","pwa","realtime","socket-io","sqlite","typescript","vue3"],"latest_commit_sha":null,"homepage":"https://cursus.school","language":"Vue","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rohanfosse.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":"rohanfosse"}},"created_at":"2026-03-15T09:46:36.000Z","updated_at":"2026-04-27T15:19:35.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/rohanfosse/cursus","commit_stats":null,"previous_names":["rohanfosse/slack-like-electron","rohanfosse/cursus"],"tags_count":500,"template":false,"template_full_name":null,"purl":"pkg:github/rohanfosse/cursus","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rohanfosse%2Fcursus","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rohanfosse%2Fcursus/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rohanfosse%2Fcursus/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rohanfosse%2Fcursus/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rohanfosse","download_url":"https://codeload.github.com/rohanfosse/cursus/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rohanfosse%2Fcursus/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32447321,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-29T22:27:22.272Z","status":"ssl_error","status_checked_at":"2026-04-29T22:10:49.234Z","response_time":110,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["docker","education","electron","lms","pwa","realtime","socket-io","sqlite","typescript","vue3"],"created_at":"2026-04-01T23:54:20.101Z","updated_at":"2026-05-10T11:21:37.527Z","avatar_url":"https://github.com/rohanfosse.png","language":"Vue","funding_links":["https://github.com/sponsors/rohanfosse"],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n\n\u003cimg src=\"src/renderer/src/assets/logo.png\" alt=\"Cursus\" width=\"96\" /\u003e\n\n# Cursus\n\n### L'app tout-en-un pour ta promo\n\n**Chat \u0026middot; Devoirs \u0026middot; Quiz \u0026middot; Cours \u0026middot; Rendez-vous**\n\u003cbr /\u003e\nUn seul endroit, plus de charge mentale.\n\n\u003cbr /\u003e\n\n[![Tests](https://img.shields.io/github/actions/workflow/status/rohanfosse/cursus/test.yml?style=for-the-badge\u0026label=tests\u0026logo=vitest\u0026logoColor=white)](https://github.com/rohanfosse/cursus/actions)\n[![Version](https://img.shields.io/github/v/release/rohanfosse/cursus?style=for-the-badge\u0026label=version\u0026color=22c55e)](https://github.com/rohanfosse/cursus/releases)\n[![License AGPL-3.0](https://img.shields.io/github/license/rohanfosse/cursus?style=for-the-badge\u0026color=blue\u0026label=license)](LICENSE)\n[![App Status](https://img.shields.io/website?url=https%3A%2F%2Fapp.cursus.school\u0026style=for-the-badge\u0026label=app\u0026logo=statuspage\u0026logoColor=white)](https://app.cursus.school)\n\n\u003cbr /\u003e\n\n### [▶ Tester la démo live](https://app.cursus.school/#/demo)\n\n**Sans inscription. 30 secondes.** Bascule entre profil prof et étudiant pour explorer toute l'app.\n\n\u003cbr /\u003e\n\n[**Application**](https://app.cursus.school) \u0026nbsp;\u0026nbsp;\u0026middot;\u0026nbsp;\u0026nbsp; [**Site web**](https://cursus.school) \u0026nbsp;\u0026nbsp;\u0026middot;\u0026nbsp;\u0026nbsp; [**Télécharger**](https://github.com/rohanfosse/cursus/releases) \u0026nbsp;\u0026nbsp;\u0026middot;\u0026nbsp;\u0026nbsp; [**Discussions**](https://github.com/rohanfosse/cursus/discussions)\n\n\u003cbr /\u003e\n\n\u003cimg src=\"https://img.shields.io/badge/Vue_3-4FC08D?style=flat-square\u0026logo=vuedotjs\u0026logoColor=white\" /\u003e\n\u003cimg src=\"https://img.shields.io/badge/TypeScript-3178C6?style=flat-square\u0026logo=typescript\u0026logoColor=white\" /\u003e\n\u003cimg src=\"https://img.shields.io/badge/Electron_38-47848F?style=flat-square\u0026logo=electron\u0026logoColor=white\" /\u003e\n\u003cimg src=\"https://img.shields.io/badge/Vite_6-646CFF?style=flat-square\u0026logo=vite\u0026logoColor=white\" /\u003e\n\u003cimg src=\"https://img.shields.io/badge/Socket.IO-010101?style=flat-square\u0026logo=socketdotio\u0026logoColor=white\" /\u003e\n\u003cimg src=\"https://img.shields.io/badge/SQLite-003B57?style=flat-square\u0026logo=sqlite\u0026logoColor=white\" /\u003e\n\u003cimg src=\"https://img.shields.io/badge/Pinia-FFD43B?style=flat-square\u0026logo=pinia\u0026logoColor=black\" /\u003e\n\u003cimg src=\"https://img.shields.io/badge/Docker-2496ED?style=flat-square\u0026logo=docker\u0026logoColor=white\" /\u003e\n\n\u003c/div\u003e\n\n\u003cbr /\u003e\n\n\u003e Les étudiants et enseignants jonglent entre 5 à 8 outils chaque jour : Moodle,\n\u003e Teams, WhatsApp, Drive, mails. Les annonces se perdent, les deadlines aussi,\n\u003e la frustration monte. **Cursus supprime cette charge mentale.** On ouvre\n\u003e l'app le matin et on n'a jamais à se demander « c'est où ? ».\n\n\u003cbr /\u003e\n\n## Pourquoi\n\n\u003ctable\u003e\n\u003ctr\u003e\n\u003ctd width=\"33%\" valign=\"top\"\u003e\n\n### Un seul endroit\n\nChat, devoirs, documents, dashboard, quiz live, RDV. Plus besoin de jongler\nentre 5 onglets ouverts.\n\n\u003c/td\u003e\n\u003ctd width=\"33%\" valign=\"top\"\u003e\n\n### Sur-mesure éducatif\n\nTypes de devoirs spécifiques, notation par rubriques, suivi de promo,\ncampagnes de visites tripartites. Pas un outil générique adapté.\n\n\u003c/td\u003e\n\u003ctd width=\"33%\" valign=\"top\"\u003e\n\n### Moins de logistique\n\nGrilles d'évaluation, deadlines auto, notifications instantanées, sync\nOutlook. Plus de temps pour la pédagogie.\n\n\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/table\u003e\n\n\u003cbr /\u003e\n\n## Fonctionnalités\n\n\u003ctable\u003e\n\u003ctr\u003e\n\u003cth\u003eModule\u003c/th\u003e\n\u003cth\u003eDescription\u003c/th\u003e\n\u003c/tr\u003e\n\n\u003ctr\u003e\n\u003ctd valign=\"top\"\u003e\n\n**Chat temps réel**\n\n\u003c/td\u003e\n\u003ctd\u003e\n\nCanaux par promotion (archivables), annonces lecture seule, DMs étudiants,\nréactions, mentions \u003ccode\u003e@nom\u003c/code\u003e / \u003ccode\u003e@tous\u003c/code\u003e, slash commands\n\u003ccode\u003e/devoir\u003c/code\u003e \u003ccode\u003e/doc\u003c/code\u003e \u003ccode\u003e/annonce\u003c/code\u003e, recherche\nplein texte, indicateur de frappe, notifications desktop, **offline queue\navec retry**.\n\n\u003c/td\u003e\n\u003c/tr\u003e\n\n\u003ctr\u003e\n\u003ctd valign=\"top\"\u003e\n\n**Devoirs et évaluation**\n\n\u003c/td\u003e\n\u003ctd\u003e\n\n5 types (livrable, soutenance, CCTL, étude de cas, mémoire). Brouillon,\nverrouillage post-deadline, **grilles multicritères**, notation A-D,\nfeedback individuel, export CSV.\n\n\u003c/td\u003e\n\u003c/tr\u003e\n\n\u003ctr\u003e\n\u003ctd valign=\"top\"\u003e\n\n**Documents et ressources**\n\n\u003c/td\u003e\n\u003ctd\u003e\n\nUpload avec validation (taille max 50 Mo, extensions bloquées), liens,\ncatégorisation, viewers intégrés (PDF, Word, Excel), drag-and-drop,\nrecherche, liaison aux devoirs.\n\n\u003c/td\u003e\n\u003c/tr\u003e\n\n\u003ctr\u003e\n\u003ctd valign=\"top\"\u003e\n\n**Dashboard personnalisable**\n\n\u003c/td\u003e\n\u003ctd\u003e\n\nWidgets réorganisables par drag-and-drop, deadlines proches, dernières\nnotes, progression, calendrier. Vues dédiées enseignant / étudiant.\n\n\u003c/td\u003e\n\u003c/tr\u003e\n\n\u003ctr\u003e\n\u003ctd valign=\"top\"\u003e\n\n**Lumen**\u003cbr /\u003e\u003csub\u003eLiseuse de cours\u003c/sub\u003e\n\n\u003c/td\u003e\n\u003ctd\u003e\n\nCours markdown adossés à GitHub (1 promo = 1 organisation, 1 cours = 1 repo).\nDétection auto des chapitres, scaffold « Nouveau cours » en 1 clic,\n**recherche FTS5**, KaTeX + Mermaid + admonitions, édition inline, notes\nprivées, tracking de lecture, **PDF intégré** (pdf.js), **runner notebooks\n.ipynb** (Pyodide).\n\n\u003c/td\u003e\n\u003c/tr\u003e\n\n\u003ctr\u003e\n\u003ctd valign=\"top\"\u003e\n\n**Live**\u003cbr /\u003e\u003csub\u003e4 modes interactifs\u003c/sub\u003e\n\n\u003c/td\u003e\n\u003ctd\u003e\n\nModule unifié avec 4 catégories :\u003cbr /\u003e\n\u0026middot; **Spark** \u0026mdash; quiz QCM, vrai/faux, association, estimation,\nréponse courte, scoring + podium\u003cbr /\u003e\n\u0026middot; **Pulse** \u0026mdash; feedback anonyme : nuage de mots, échelle,\nhumeur, sondage, matrice, priorité\u003cbr /\u003e\n\u0026middot; **Code** \u0026mdash; éditeur live avec coloration syntaxique\u003cbr /\u003e\n\u0026middot; **Board** \u0026mdash; brainstorming collaboratif, post-its, votes,\ndrag \u0026amp; drop, export Markdown\n\n\u003c/td\u003e\n\u003c/tr\u003e\n\n\u003ctr\u003e\n\u003ctd valign=\"top\"\u003e\n\n**Rendez-vous**\u003cbr /\u003e\u003csub\u003emini-Calendly\u003c/sub\u003e\n\n\u003c/td\u003e\n\u003ctd\u003e\n\nPage dédiée \u003ccode\u003e/booking\u003c/code\u003e. Types d'événements, grille de\ndisponibilités hebdomadaire, lien de réservation par étudiant, sync\n**Microsoft Outlook + Teams** ou **Jitsi Meet** (alternative libre),\ne-mails de confirmation, rate limiting sur les routes publiques.\n\n\u003c/td\u003e\n\u003c/tr\u003e\n\n\u003ctr\u003e\n\u003ctd valign=\"top\"\u003e\n\n**Campagnes RDV**\u003cbr /\u003e\u003csub\u003evisites tripartites\u003c/sub\u003e\n\n\u003c/td\u003e\n\u003ctd\u003e\n\nPlanification automatique de visites prof + étudiant + tuteur entreprise\nsur une période donnée. Génération des créneaux à partir de règles\nhebdomadaires, **invitations en 1 clic**, lien personnel par étudiant,\nsuivi en temps réel, relances automatiques.\n\n\u003c/td\u003e\n\u003c/tr\u003e\n\n\u003ctr\u003e\n\u003ctd valign=\"top\"\u003e\n\n**Kanban projet**\n\n\u003c/td\u003e\n\u003ctd\u003e\n\nSuivi par groupe avec drag-and-drop (à faire, en cours, terminé).\nSynchronisation temps réel.\n\n\u003c/td\u003e\n\u003c/tr\u003e\n\n\u003ctr\u003e\n\u003ctd valign=\"top\"\u003e\n\n**Frise chronologique**\n\n\u003c/td\u003e\n\u003ctd\u003e\n\nTimeline interactive des devoirs, zoom semaine / mois / trimestre / année.\n\n\u003c/td\u003e\n\u003c/tr\u003e\n\n\u003ctr\u003e\n\u003ctd valign=\"top\"\u003e\n\n**Signature PDF**\n\n\u003c/td\u003e\n\u003ctd\u003e\n\nCircuit de signature en DM avec tampon, référence unique, sauvegarde locale.\n\n\u003c/td\u003e\n\u003c/tr\u003e\n\n\u003ctr\u003e\n\u003ctd valign=\"top\"\u003e\n\n**DMs**\u003cbr /\u003e\u003csub\u003echiffrés AES-256-GCM au repos en base\u003c/sub\u003e\n\n\u003c/td\u003e\n\u003ctd\u003e\n\nConversations privées chiffrées **AES-256-GCM**, indicateur en ligne,\nenvoi de fichiers, brouillons par conversation.\n\n\u003c/td\u003e\n\u003c/tr\u003e\n\n\u003ctr\u003e\n\u003ctd valign=\"top\"\u003e\n\n**Agenda et calendrier**\n\n\u003c/td\u003e\n\u003ctd\u003e\n\nReminders, deadlines, export ICS, sync Outlook bidirectionnelle.\n\n\u003c/td\u003e\n\u003c/tr\u003e\n\n\u003ctr\u003e\n\u003ctd valign=\"top\"\u003e\n\n**Mini-jeux**\n\n\u003c/td\u003e\n\u003ctd\u003e\n\nTypeRace (vitesse de frappe), Snake, Space Invaders. Leaderboard par promo,\nscopes \u003ccode\u003eday\u003c/code\u003e / \u003ccode\u003eweek\u003c/code\u003e / \u003ccode\u003eall\u003c/code\u003e. Activable\npar module dans l'admin.\n\n\u003c/td\u003e\n\u003c/tr\u003e\n\n\u003ctr\u003e\n\u003ctd valign=\"top\"\u003e\n\n**Mobile PWA**\n\n\u003c/td\u003e\n\u003ctd\u003e\n\nNavigation tactile, barre inférieure, optimisé pour petits écrans, service\nworker pour le mode hors-ligne.\n\n\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/table\u003e\n\n\u003cbr /\u003e\n\n## Démarrage rapide\n\n\u003e **Prérequis** : [Node.js](https://nodejs.org/) 20+ (CI sur 22) et npm.\n\n```bash\ngit clone https://github.com/rohanfosse/cursus.git\ncd cursus\nnpm install\nnpm run dev\n```\n\nLa base SQLite est créée automatiquement au premier lancement. Pour charger\ndes données de démonstration, ouvrir l'admin et cliquer sur **Réinitialiser\net peupler**.\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003eTous les scripts npm\u003c/b\u003e\u003c/summary\u003e\n\n| Commande                  | Description                                  |\n|---------------------------|----------------------------------------------|\n| `npm run dev`             | Electron + Vite HMR                          |\n| `npm run dev:web`         | PWA web seulement (Vite, port 5174)          |\n| `npm run server:dev`      | Serveur Express + Socket.IO en watch         |\n| `npm run build`           | Build complet (main + preload + renderer)   |\n| `npm run build:web`       | SPA web (PWA) dans `dist-web/`               |\n| `npm run build:win`       | Packaging Windows (.exe NSIS)                |\n| `npm run build:mac`       | Packaging macOS (.dmg)                       |\n| `npm run server`          | Serveur Express en production                |\n| `npm test`                | Tests Vitest (frontend + backend)            |\n| `npm run test:e2e`        | Tests E2E Playwright                         |\n| `npm run test:coverage`   | Tests + couverture (objectif 80%+)           |\n| `npm run typecheck`       | Vérification TypeScript stricte (vue-tsc)    |\n\n\u003c/details\u003e\n\n\u003cbr /\u003e\n\n## Architecture\n\n```mermaid\nflowchart LR\n    subgraph Clients\n        D[Electron Desktop\u003cbr/\u003eWin / macOS]\n        W[PWA Web\u003cbr/\u003eapp.cursus.school]\n        M[Mobile PWA]\n    end\n\n    subgraph Server[Server \u0026mdash; Node.js + Express]\n        API[REST API\u003cbr/\u003e~20 domaines]\n        WS[Socket.IO\u003cbr/\u003epresence, typing,\u003cbr/\u003epush notifications]\n        AUTH[Auth JWT\u003cbr/\u003e+ rate limit]\n    end\n\n    subgraph Storage\n        SQ[(SQLite\u003cbr/\u003eBetter-SQLite3)]\n        FS[Uploads\u003cbr/\u003elocal filesystem]\n    end\n\n    subgraph External[Services externes]\n        MS[Microsoft Graph\u003cbr/\u003eOutlook + Teams]\n        SMTP[SMTP\u003cbr/\u003ee-mails RDV]\n        GH[GitHub API\u003cbr/\u003eLumen courses]\n    end\n\n    D --\u003e|IPC + HTTP| Server\n    W --\u003e|HTTP + WSS| Server\n    M --\u003e|HTTP + WSS| Server\n\n    API --\u003e SQ\n    API --\u003e FS\n    WS --\u003e SQ\n\n    Server --\u003e|OAuth2| MS\n    Server --\u003e SMTP\n    Server --\u003e|REST| GH\n```\n\n\u003cbr /\u003e\n\n### Architecture \u0026 Scaling\n\n**Single-tenant assume.** Une instance Cursus = une école. Un seul process Node.js + un seul fichier SQLite. Pas de multi-tenant logique au niveau des tables (toutes les rows sont scopées par `promo_id`, mais la base est partagée). Si tu veux héberger plusieurs écoles, tu lances plusieurs instances avec leurs propres ports, dossiers et fichiers SQLite — chaque école a son monde isolé physiquement.\n\n**Pourquoi pas multi-tenant ?** Une école = ~50-300 utilisateurs simultanés au pic, ~50k messages, ~10k devoirs. SQLite gère ça en O(log n) avec une empreinte mémoire de 30-50 Mo. Le multi-tenant logique aurait coûté en complexité (préfixes partout, audits cross-tenant, risques de fuite) sans gain visible. Le déploiement Docker rend trivial le fait de lancer N instances.\n\n**Concurrence SQLite : mode WAL.** SQLite est ouvert en `PRAGMA journal_mode = WAL` (Write-Ahead Logging) dès la création. Un writer parallèle aux lecteurs sans verrou global, ce qui suffit pour les ~30 r/w/s qu'une promo génère. La WAL fait que les lectures ne bloquent jamais les écritures et inversement, jusqu'à l'unique writer simultané que SQLite tolère. Si jamais une école atteint plusieurs centaines de writes/s en pic, on bascule sur PostgreSQL avec une migration linéaire (le code utilise `better-sqlite3` derrière une couche de queries paramétrées).\n\n\u003e [!WARNING]\n\u003e **`better-sqlite3` se compile via `node-gyp` à l'install.** C'est un binding natif C++. Pour que `npm install` réussisse, tu dois avoir sur ta machine :\n\u003e\n\u003e | OS | Prérequis |\n\u003e |---|---|\n\u003e | **Windows** | Python 3 + Visual Studio Build Tools (workload \"C++ build tools\") OU `npm install --global windows-build-tools` |\n\u003e | **macOS** | Xcode Command Line Tools (`xcode-select --install`) |\n\u003e | **Linux** | `build-essential` + `python3` (`apt install build-essential python3`) |\n\u003e | **Docker** | L'image base inclut déjà ces deps. Si tu modifies le `Dockerfile`, ne pas oublier `apk add --no-cache python3 make g++` (Alpine) ou `apt-get install -y build-essential python3` (Debian/Ubuntu). |\n\u003e\n\u003e Pour Electron en particulier, `npm rebuild better-sqlite3` est nécessaire après chaque mise à jour d'Electron, car les ABI Node.js et Electron diffèrent. Le script `postinstall` du `package.json` s'en charge automatiquement.\n\n\u003cbr /\u003e\n\n## Stack technique\n\n| Couche       | Technologies                                                                                                     |\n|--------------|------------------------------------------------------------------------------------------------------------------|\n| **Desktop**  | Electron 38, context isolation, sandbox, auto-update (electron-updater, NSIS)                                    |\n| **Frontend** | Vue 3.5 Composition API, TypeScript strict, Pinia, Vue Router                                                    |\n| **Backend**  | Express 4, Socket.IO 4, SQLite (Better-SQLite3), JWT, Zod, bcrypt, MSAL Node, nodemailer                          |\n| **Build**    | electron-vite 3, Vite 6, electron-builder                                                                        |\n| **Mobile**   | PWA, service worker, Web App Manifest                                                                            |\n| **CI/CD**    | GitHub Actions (Vitest, Playwright, deploy Docker, release Win/macOS, Lighthouse, CodeQL, Dependabot)            |\n| **Qualité**  | Vitest, Supertest, Playwright, vue-tsc strict                                                                    |\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003eStructure du repo\u003c/b\u003e\u003c/summary\u003e\n\n```\ncursus/\n  src/\n    main/              Processus principal Electron (IPC, DB, fenêtre)\n    preload/           Bridge IPC type-safe (contextBridge)\n    renderer/          Frontend Vue 3 + TypeScript + Pinia\n    web/               Shim PWA (remplace IPC par fetch + socket.io)\n    landing/           Page vitrine cursus.school\n  server/\n    db/                SQLite : connexion, schéma, migrations, models\n    routes/            36 fichiers, ~20 domaines métier\n    services/          E-mail (nodemailer), Microsoft Graph (MSAL), unfurl\n    middleware/        Auth JWT, validation Zod, rate limit, rôle + promo\n    public/            Console d'administration\n  tests/\n    frontend/          Tests unitaires utils + stores\n    backend/           Tests models + routes + middleware + sécurité\n    e2e/               Playwright (auth, isolation cross-promo)\n```\n\n\u003c/details\u003e\n\n\u003cbr /\u003e\n\n## Sécurité\n\n| Couche                  | Mécanisme                                                                                                |\n|-------------------------|----------------------------------------------------------------------------------------------------------|\n| **Isolation promo**     | Middleware `requirePromo` + rooms Socket.IO par promotion                                                |\n| **Contrôle par rôle**   | 4 rôles hiérarchiques (admin \u003e enseignant \u003e intervenant \u003e étudiant), permissions centralisées            |\n| **DMs confidentiels**   | `requireDmParticipant` + chiffrement AES-256-GCM au repos                                                |\n| **Auth + chiffrement**  | JWT 7j, bcrypt 10 rounds, validation Zod, CSRF (OAuth state HMAC), CSP stricte, tokens MS chiffrés       |\n| **IPC sécurisé**        | `contextIsolation`, `sandbox`, `nodeIntegration: false`, vérifications rôle + promo dans les handlers   |\n| **RGPD**                | Export des données personnelles (Art. 20), suppression de compte                                         |\n\nPour signaler une vulnérabilité : [SECURITY.md](SECURITY.md).\n\n\u003cbr /\u003e\n\n## Déploiement\n\n### Docker (recommandé)\n\n```bash\ndocker compose build\ndocker compose up -d\ndocker logs -f cursus-server\n```\n\n### Manuel\n\n```bash\nnpm run build:web\nNODE_ENV=production PORT=3001 JWT_SECRET=\u003csecret-32-chars\u003e node server/index.js\n```\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003eVariables d'environnement\u003c/b\u003e\u003c/summary\u003e\n\n| Variable               | Description                                | Défaut                  |\n|------------------------|--------------------------------------------|-------------------------|\n| `PORT`                 | Port HTTP                                  | `3001`                  |\n| `JWT_SECRET`           | Clé JWT (min 32 chars en prod)             | `changeme-dev-secret`   |\n| `CORS_ORIGIN`          | Origine CORS                               | `*`                     |\n| `DB_PATH`              | Chemin SQLite                              | Auto                    |\n| `UPLOAD_DIR`           | Répertoire uploads                         | `uploads/`              |\n| `VITE_SERVER_URL`      | URL serveur pour le frontend               | `http://localhost:3001` |\n| `AZURE_TENANT_ID`      | Azure AD tenant ID (booking)               | -                       |\n| `AZURE_CLIENT_ID`      | Azure AD client ID (booking)               | -                       |\n| `AZURE_CLIENT_SECRET`  | Azure AD client secret (booking)           | -                       |\n| `SMTP_HOST`            | Serveur SMTP (e-mails RDV)                 | -                       |\n| `SMTP_USER`            | Utilisateur SMTP                           | -                       |\n| `SMTP_PASS`            | Mot de passe SMTP                          | -                       |\n\n\u003c/details\u003e\n\n### Infrastructure live\n\n| Service        | Domaine                                                              |\n|----------------|----------------------------------------------------------------------|\n| Application    | [app.cursus.school](https://app.cursus.school)                       |\n| Page vitrine   | [cursus.school](https://cursus.school)                               |\n| Administration | [admin.cursus.school](https://admin.cursus.school)                   |\n\nDocker + Nginx + Let's Encrypt sur VPS. Déploiement automatique via GitHub\nActions sur chaque push `main`.\n\n\u003cbr /\u003e\n\n## Contribuer\n\nLes contributions sont les bienvenues. Voir [CONTRIBUTING.md](CONTRIBUTING.md)\npour le workflow détaillé.\n\n```bash\ngit checkout -b feat/ma-feature   # branche descriptive\nnpm run dev                       # dev avec HMR\nnpm test                          # tests\nnpx vue-tsc --noEmit              # types\ngit push origin feat/ma-feature   # ouvrir une PR vers main\n```\n\n**Conventions** : commits préfixés (`feat:`, `fix:`, `docs:`, `chore:`,\n`refactor:`, `test:`), TypeScript strict, Composition API, variables CSS\n(pas de couleurs hardcodées), pas d'emojis dans l'UI ni les commits.\n\n\u003cbr /\u003e\n\n## Licence\n\nDistribué sous licence [GNU AGPL v3](LICENSE) \u0026middot; \u0026copy; 2025-2026\n[Rohan Fossé](https://github.com/rohanfosse).\n\nL'AGPL est une licence copyleft forte\u0026nbsp;: tu peux utiliser, modifier et\nredistribuer Cursus librement, mais si tu héberges une **version modifiée**\ncomme service réseau, tu dois publier les modifications sous la même licence.\nVoir [LICENSE](LICENSE) pour les termes complets, ou la\n[FAQ AGPL](https://www.gnu.org/licenses/agpl-3.0.faq.html) pour une explication\nplus accessible.\n\n\u003cbr /\u003e\n\n\u003cdiv align=\"center\"\u003e\n\n\u003csub\u003e\nConçu et développé avec soin par \u003ca href=\"https://github.com/rohanfosse\"\u003e@rohanfosse\u003c/a\u003e.\n\u003cbr /\u003e\nSi Cursus te plaît, mets une \u003ca href=\"https://github.com/rohanfosse/cursus/stargazers\"\u003eétoile\u003c/a\u003e sur GitHub \u0026mdash; ça aide énormément.\n\u003c/sub\u003e\n\n\u003c/div\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frohanfosse%2Fcursus","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frohanfosse%2Fcursus","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frohanfosse%2Fcursus/lists"}