{"id":51976621,"url":"https://github.com/sctg-development/ecole-directe-exclusions","last_synced_at":"2026-07-30T06:02:00.853Z","repository":{"id":370990816,"uuid":"1298280712","full_name":"sctg-development/ecole-directe-exclusions","owner":"sctg-development","description":"⚡ Real-time classroom exclusion tracking. Teachers report exclusions in \u003c10s, student life office gets instant push notifications. GDPR-compliant, free-tier friendly. TypeScript + Cloudflare Workers + React PWA + Capacitor.","archived":false,"fork":false,"pushed_at":"2026-07-12T14:13:49.000Z","size":405,"stargazers_count":0,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-07-12T15:11:23.539Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sctg-development.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE.md","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"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}},"created_at":"2026-07-12T12:54:49.000Z","updated_at":"2026-07-12T13:30:49.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/sctg-development/ecole-directe-exclusions","commit_stats":null,"previous_names":["sctg-development/ecole-directe-exclusions"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/sctg-development/ecole-directe-exclusions","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sctg-development%2Fecole-directe-exclusions","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sctg-development%2Fecole-directe-exclusions/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sctg-development%2Fecole-directe-exclusions/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sctg-development%2Fecole-directe-exclusions/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sctg-development","download_url":"https://codeload.github.com/sctg-development/ecole-directe-exclusions/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sctg-development%2Fecole-directe-exclusions/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":36064055,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-07-20T02:08:10.276Z","status":"online","status_checked_at":"2026-07-30T02:00:05.956Z","response_time":106,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"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":[],"created_at":"2026-07-30T06:02:00.031Z","updated_at":"2026-07-30T06:02:00.832Z","avatar_url":"https://github.com/sctg-development.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# École Directe Exclusions\n\n[![CI](https://github.com/sctg-development/ecole-directe-exclusions/actions/workflows/ci.yml/badge.svg)](https://github.com/sctg-development/ecole-directe-exclusions/actions/workflows/ci.yml)\n[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](./LICENSE)\n[![Node](https://img.shields.io/badge/node-%E2%89%A522-brightgreen.svg)](./package.json)\n![Code](https://tokeisrv.sctg.eu.org/b1/github.com/sctg-development/ecole-directe-exclusions?type=TypeScript,TSX\u0026category=code)\n![Comments](https://tokeisrv.sctg.eu.org/b1/github.com/sctg-development/ecole-directe-exclusions?type=TSX,TypeScript\u0026category=comments\u0026color=abdbe3)\n![Documentation](https://tokeisrv.sctg.eu.org/b1/github.com/sctg-development/ecole-directe-exclusions?type=Markdown\u0026label=doc\u0026color=e28743)\n\nReal-time classroom **exclusion tracking** for schools. When a teacher excludes a disruptive\nstudent from a lesson, the student today walks the school unsupervised — nobody at the student-life\noffice (_vie scolaire_) knows an exclusion happened. This app closes that gap: the teacher reports\nthe exclusion from their tablet in **under 10 seconds**, the vie scolaire receives a **push\nnotification immediately**, acknowledges it, marks the student as arrived — or escalates a search\nif the student never shows up. Every state change is kept in an immutable audit trail.\n\n## In brief\n\nA student excluded from class must never wander unsupervised around the school. With this application, the teacher reports the exclusion in under 10 seconds from their tablet; the student life office (_vie scolaire_) is notified immediately, takes charge of the student upon arrival, and an automatic alert is triggered if the student does not appear within the configured timeframe (10 minutes by default). Each school has a completely independent deployment (GDPR-compliant), and the full history of incidents is retained for educational follow-up.\n\n## How it works\n\n```mermaid\nflowchart LR\n  subgraph Devices[\"Teacher / vie scolaire\"]\n    A[\"Android app (Capacitor)\"]\n    I[\"iOS app (Capacitor)\"]\n    W[\"Web app (PWA)\"]\n  end\n  subgraph CF[\"Cloudflare — one independent stack per school\"]\n    WK[\"Worker (Hono, TypeScript)\u003cbr/\u003eREST API /api/v1 + static assets\u003cbr/\u003e+ cron escalation\"]\n    D1[(\"D1 (SQLite)\u003cbr/\u003eper-school database\")]\n  end\n  A -- HTTPS --\u003e WK\n  I -- HTTPS --\u003e WK\n  W -- HTTPS --\u003e WK\n  WK --- D1\n  WK -. \"FCM (Android/iOS)\u003cbr/\u003eWeb Push VAPID (web)\" .-\u003e Devices\n```\n\n- **One fully independent deployment per school** (separate Worker, D1 database and secrets) — no\n  student data ever crosses school boundaries, a GDPR requirement.\n- **Free-tier friendly**: fewer than 10 exclusions/day/school fits the Cloudflare free plans with\n  orders of magnitude of headroom.\n- The exclusion lifecycle (`pending → acknowledged → arrived/missing → resolved`, plus\n  `cancelled`) is a state machine defined once in `@exclusions/shared` and enforced everywhere.\n\n## Monorepo layout\n\n| Path              | Package              | Purpose                                                                            |\n| ----------------- | -------------------- | ---------------------------------------------------------------------------------- |\n| `packages/shared` | `@exclusions/shared` | Domain types, Zod API schemas, exclusion lifecycle state machine, shared constants |\n| `apps/server`     | `@exclusions/server` | Cloudflare Worker: REST API, auth, D1, push dispatch, SIS adapters, cron           |\n| `apps/client`     | `@exclusions/client` | React PWA + Capacitor shells (Android/iOS)                                         |\n| `docs/`           | —                    | Architecture, API contract, data model, deployment, GDPR, mobile builds            |\n| `scripts/`        | —                    | Operational helpers (VAPID key generation)                                         |\n| `.github/`        | —                    | CI and per-school deployment workflows                                             |\n| `.claude/`        | —                    | Claude Code skills for contributors                                                |\n\n## Quick start\n\nPrerequisites: Node.js \u003e= 22 and npm.\n\n```bash\ngit clone https://github.com/sctg-development/ecole-directe-exclusions.git\ncd ecole-directe-exclusions\nnpm install\n\n# Create the local D1 database (SQLite via wrangler)\nnpm run db:migrate:local -w @exclusions/server\n```\n\nLocal secrets: create `apps/server/.dev.vars` (gitignored) with at least `JWT_SECRET` and\n`BOOTSTRAP_SECRET` (any random strings for development). Web-push testing also needs the VAPID\nkeys — generate them with `node scripts/generate-vapid-keys.mjs`.\n\nRun the two dev servers in two terminals:\n\n```bash\nnpm run dev -w @exclusions/server   # API on http://localhost:8787\nnpm run dev -w @exclusions/client   # Web app on http://localhost:5173\n```\n\nBootstrap the first admin account (one-time, guarded by the bootstrap secret):\n\n```bash\ncurl -X POST http://localhost:8787/api/v1/auth/bootstrap \\\n  -H \"Content-Type: application/json\" \\\n  -H \"X-Bootstrap-Secret: \u003cyour BOOTSTRAP_SECRET\u003e\" \\\n  -d '{\"email\":\"admin@example.org\",\"displayName\":\"Admin\",\"password\":\"a-strong-password-protecting-super-admin\"}'\n```\n\nThen log in on http://localhost:5173 and create teacher / vie-scolaire accounts from the admin\nscreen. Useful checks while developing:\n\n```bash\nnpm run lint          # ESLint over the whole repo\nnpm run format:check  # Prettier check\nnpm run typecheck     # tsc --noEmit in every workspace\nnpm test              # Vitest in every workspace (server tests run inside workerd)\nnpm run build -w @exclusions/client   # production build of the web app\nnpm run build -w @exclusions/server   # wrangler dry-run build (needs the client build first)\n```\n\n## Documentation\n\n- [Architecture](./docs/ARCHITECTURE.md) — system overview, lifecycle, roles, auth, push\n- [API contract](./docs/API.md) — REST endpoints (`/api/v1`)\n- [Data model](./docs/DATA-MODEL.md) — D1 schema and retention\n- [Deployment](./docs/DEPLOYMENT.md) — per-school runbook (Cloudflare, secrets, bootstrap)\n- [GDPR](./docs/GDPR.md) — lawful basis, minimization, retention, data subject rights\n- [Mobile builds](./docs/MOBILE.md) — Capacitor Android/iOS, FCM/APNs, store distribution\n- [Contributing](./CONTRIBUTING.md) · [Security policy](./SECURITY.md) ·\n  [Code of conduct](./CODE_OF_CONDUCT.md)\n\n## Tech stack\n\n- **TypeScript** (strict) everywhere — one language for server, clients and shared domain logic\n- **Hono** on **Cloudflare Workers**, **D1** (SQLite) per school, cron triggers for escalation\n- **React 19 + Vite + Tailwind CSS 4** web client, wrapped by **Capacitor 8** for Android/iOS\n- **Web Push (VAPID)** and **FCM HTTP v1** push, both implemented with WebCrypto (no Node deps)\n- **Zod** schemas shared between server validation and client types\n- **Vitest** everywhere; server tests run inside workerd via `@cloudflare/vitest-pool-workers`\n\nUI language is **French** (users are French school staff); code, comments and docs are English.\n\n## License\n\n[MIT](./LICENSE) © 2026 Ronan Le Meillat — SCTG Development\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsctg-development%2Fecole-directe-exclusions","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsctg-development%2Fecole-directe-exclusions","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsctg-development%2Fecole-directe-exclusions/lists"}