From ea61d83384d0eff75b12d114632de73fdffcdcf1 Mon Sep 17 00:00:00 2001 From: Djalim Simaila Date: Sun, 26 Apr 2026 00:24:27 +0200 Subject: [PATCH] fix(lint): add version to drizzle-orm imports and prefix unused NOT_FOUND --- .gitea/workflows/test.yml | 74 ++++ CLAUDE.md | 338 ++++++++++++++++++ bugs.md | 158 ++++++++ compose.yml | 4 +- databases/migrations/meta/0000_snapshot.json | 2 +- databases/migrations/meta/_journal.json | 2 +- flake.lock | 61 ++++ flake.nix | 62 ++++ package.json | 2 +- routes/(apps)/admin/api/enseignements.ts | 2 +- routes/(apps)/admin/api/modules.ts | 2 +- routes/(apps)/admin/api/modules/[idModule].ts | 2 +- routes/(apps)/notes/api/notes.ts | 11 +- .../notes/api/notes/[numEtud]/[idModule].ts | 72 ++-- routes/(apps)/notes/api/ue-modules.ts | 20 +- .../ue-modules/[idModule]/[idUE]/[idPromo].ts | 19 +- routes/(apps)/notes/api/ues.ts | 2 +- routes/(apps)/notes/api/ues/[idUE].ts | 73 ++-- shell.nix | 23 ++ toolbox/compile.sh | 33 ++ 20 files changed, 880 insertions(+), 82 deletions(-) create mode 100644 .gitea/workflows/test.yml create mode 100644 CLAUDE.md create mode 100644 bugs.md create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 shell.nix create mode 100755 toolbox/compile.sh diff --git a/.gitea/workflows/test.yml b/.gitea/workflows/test.yml new file mode 100644 index 0000000..9578842 --- /dev/null +++ b/.gitea/workflows/test.yml @@ -0,0 +1,74 @@ +name: "Tests" + +on: + pull_request: + branches: + - main + - develop + push: + branches: + - develop + +jobs: + unit: + name: "Unit tests" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: denoland/setup-deno@v2 + with: + deno-version: v2.x + + - name: Run unit tests + run: deno task test:unit + + integration: + name: "Integration tests" + runs-on: ubuntu-latest + services: + postgres: + image: postgres:16 + env: + POSTGRES_DB: polympr_test + POSTGRES_USER: test + POSTGRES_PASSWORD: test + ports: + - 5432:5432 + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: "20" + + - uses: denoland/setup-deno@v2 + with: + deno-version: v2.x + + - name: Install drizzle-kit + run: npm install --ignore-scripts + + - name: Apply migrations + env: + POSTGRES_HOST: localhost + POSTGRES_PORT: 5432 + POSTGRES_USER: test + POSTGRES_PASS: test + POSTGRES_DB: polympr_test + run: deno task migrate + + - name: Run integration tests + env: + POSTGRES_HOST: localhost + POSTGRES_PORT: 5432 + POSTGRES_USER: test + POSTGRES_PASS: test + POSTGRES_DB: polympr_test + run: deno task test:integration diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..fe5c70d --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,338 @@ +# PolyMPR - Claude Code Context + +## 📋 Project Overview + +**PolyMPR** (Poly Management Platform for Resources) is a modular HR management +system built with **Deno + Fresh** framework. It's designed to help +organizations manage HR, student records, notes, mobility programs, and +role-based administration. + +### Stack + +- **Runtime**: Deno +- **Framework**: Fresh (edge-ready web framework) +- **Database**: PostgreSQL with Drizzle ORM +- **Frontend**: Preact with signals +- **Authentication**: JWT-based via cookies +- **Testing**: Deno test framework with HappyDOM for DOM testing + +### Current Status + +🚧 **In Progress** - Application is far from complete. The schema below is the +**final/definitive schema** that should guide all development. + +--- + +## 🏗️ Architecture + +### Module Structure + +The application uses a **modulith architecture** with the following modules: + +``` +routes/(apps)/ +├── students/ - Student management & promotions +├── notes/ - Grade management & academic records +├── mobility/ - Mobility programs & exchanges +└── admin/ - Role & permission management +``` + +### Key Directories + +- `/routes` - Fresh routes and components +- `/databases` - Database connection, schema, and migrations +- `/defaults` - Interfaces and shared types +- `/tests` - Unit, integration, and E2E tests +- `/static` - Public assets + +### Authentication Flow + +1. User authenticates via CAS (Polytech) +2. JWT token stored in `sessionToken` cookie +3. Middleware validates token on each request +4. Public routes: `/`, `/login`, `/logout`, `/about`, `/contact` +5. All other routes require authentication + +--- + +## 📊 Database Schema (Final/Definitive) + +```mermaid +erDiagram + USER { + string id PK + string nom + string prenom + int idRole FK + } + ROLE { + int id PK + string nom + } + PERMISSION { + int id PK + string nom + } + ROLE_PERMISSION { + int idRole PK,FK + int idPermission PK,FK + } + STUDENT { + int numEtud PK + string nom + string prenom + string idPromo FK + } + PROMOTION { + string idPromo PK + string annee + } + MODULE { + string id PK + string nom + } + ENSEIGNEMENT { + string idProf PK,FK + string idModule PK,FK + string idPromo PK,FK + } + UE { + int id PK + string nom + } + UE_MODULE { + string idModule PK,FK + int idUE PK,FK + string idPromo PK,FK + float coeff + } + NOTE { + int numEtud PK,FK + string idModule PK,FK + float note + } + AJUSTEMENT { + int numEtud PK,FK + int idUE PK,FK + float valeur + } + + USER }o--|| ROLE : "a" + ROLE_PERMISSION }o--|| ROLE : "accorde" + ROLE_PERMISSION }o--|| PERMISSION : "inclut" + ENSEIGNEMENT }o--|| USER : "réalisé par" + ENSEIGNEMENT }o--|| MODULE : "porte sur" + ENSEIGNEMENT }o--|| PROMOTION : "concerne" + STUDENT }o--|| PROMOTION : "appartient à" + UE_MODULE }o--|| MODULE : "associe" + UE_MODULE }o--|| UE : "appartient à" + UE_MODULE }o--|| PROMOTION : "pour" + NOTE }o--|| STUDENT : "reçoit" + NOTE }o--|| MODULE : "dans" + AJUSTEMENT }o--|| STUDENT : "concerne" + AJUSTEMENT }o--|| UE : "dans" +``` + +### Current Schema (Incomplete) + +The current Drizzle ORM schema in `/databases/schema.ts` only implements: + +- `promotions` +- `students` +- `mobility` + +**Migration needed**: Update schema to match the final ER diagram above. + +--- + +## 🎯 Open Issues (69 total) + +### UI Pages + +**Catalog** + +- 📋 UI - Page Catalogue d'applications (#71) + +**Components** + +- 🎨 UI (composant) - Popup Résultats d'import (#75) + +**Students** + +- 📋 UI - Admin – Liste des élèves (#79) +- 📋 UI - Admin – Gestion des promotions (#80) +- 📋 UI - Admin – Import xlsx élèves (#81) +- 📋 UI - Admin – Édition d'un élève (#82) + +**Notes** + +- 📋 UI - Page Élève – Mes Notes (#72) +- 📋 UI - Admin – Consulter les notes (#73) +- 📋 UI - Admin – Importer des notes (.xlsx) (#74) +- 📋 UI - Admin – Édition notes d'un élève (#76) +- 📋 UI - Admin – Récap notes élève / semestre (#77) +- 📋 UI - Admin – Gestion des UEs (#78) + +**Administration** + +- 📋 UI - Gestion des utilisateurs (#83) +- 📋 UI - Gestion des rôles (#84) +- 📋 UI - Permissions d'un rôle (#85) +- 📋 UI - Vue des permissions (#86) +- 📋 UI - Gestion des modules (#87) +- 📋 UI - Enseignements (Assignations) (#88) + +--- + +### API Endpoints + +**Students API** + +- 📋 GET `/students` (#7) +- 📋 POST `/students` (#8) +- 📋 POST `/students/import-csv` (#9) +- 📋 GET `/students/{numEtud}` (#10) +- 📋 PUT `/students/{numEtud}` (#11) +- 📋 DELETE `/students/{numEtud}` (#12) +- 📋 GET `/promotions` (#13) +- 📋 POST `/promotions` (#14) +- 📋 GET `/promotions/{idPromo}` (#15) +- 📋 PUT `/promotions/{idPromo}` (#16) +- 📋 DELETE `/promotions/{idPromo}` (#17) + +**Administration API - Modules & Enseignements** + +- 📋 GET `/modules` (#23) +- 📋 POST `/modules` (#24) +- 📋 GET `/modules/{idModule}` (#25) +- 📋 PUT `/modules/{idModule}` (#26) +- 📋 DELETE `/modules/{idModule}` (#27) +- 📋 POST `/enseignements` (#29) +- 📋 GET `/enseignements/{idProf}/{idModule}/{idPromo}` (#30) +- 📋 DELETE `/enseignements/{idProf}/{idModule}/{idPromo}` (#31) + +**Notes API - UEs & UE-Modules** + +- 📋 GET `/ues` (#32) +- 📋 POST `/ues` (#33) +- 📋 GET `/ues/{idUE}` (#34) +- 📋 PUT `/ues/{idUE}` (#35) +- 📋 DELETE `/ues/{idUE}` (#36) +- 📋 GET `/ue-modules` (#37) +- 📋 POST `/ue-modules` (#38) +- 📋 GET `/ue-modules/{idModule}/{idUE}/{idPromo}` (#39) +- 📋 PUT `/ue-modules/{idModule}/{idUE}/{idPromo}` (#40) +- 📋 DELETE `/ue-modules/{idModule}/{idUE}/{idPromo}` (#41) + +**Notes API - Notes & Ajustements** + +- 📋 GET `/notes` (#42) +- 📋 POST `/notes` (#43) +- 📋 POST `/notes/import-xlsx` (#44) +- 📋 GET `/notes/{numEtud}/{idModule}` (#45) +- 📋 PUT `/notes/{numEtud}/{idModule}` (#46) +- 📋 DELETE `/notes/{numEtud}/{idModule}` (#47) +- 📋 GET `/ajustements` (#48) +- 📋 POST `/ajustements` (#49) +- 📋 GET `/ajustements/{numEtud}/{idUE}` (#50) +- 📋 PUT `/ajustements/{numEtud}/{idUE}` (#51) +- 📋 DELETE `/ajustements/{numEtud}/{idUE}` (#52) + +**Administration API - Users, Roles & Permissions** + +- 📋 GET `/users` (#60) +- 📋 POST `/users` (#61) +- 📋 GET `/users/{id}` (#62) +- 📋 PUT `/users/{id}` (#63) +- 📋 DELETE `/users/{id}` (#64) +- 📋 GET `/roles` (#65) +- 📋 POST `/roles` (#66) +- 📋 GET `/roles/{idRole}` (#67) +- 📋 PUT `/roles/{idRole}` (#68) +- 📋 DELETE `/roles/{idRole}` (#69) +- 📋 GET `/permissions` (#70) + +--- + +## 🎨 Design Reference + +**Figma Prototype**: +https://www.figma.com/design/La79bsUsWnJCtMsrrt2zGd/Prototype?node-id=0-1 + +This is the **final design specification** for the UI. All UI implementations +should follow this design. + +--- + +## 🚀 Development Guidelines + +### Getting Started + +```bash +# Run tests +deno task test + +# Start development server +deno task start + +# Build for production +deno task build + +# Format & lint +deno task check +``` + +### Git Workflow + +1. Create branch: `git checkout -b PMPR-{ISSUE_ID}` +2. Implement changes +3. Run tests and linting +4. Submit PR + +### Code Style + +- Format: Follow Deno defaults (enforced via `deno fmt`) +- Linting: Fresh recommended rules +- TypeScript strict mode enabled +- Use Drizzle ORM for database operations + +### Testing + +- Write unit tests for business logic +- Integration tests for API endpoints +- E2E tests with HappyDOM for UI interactions +- Mock database with provided helpers + +--- + +## 📦 Key Dependencies + +- **fresh@1.7.3** - Web framework +- **drizzle-orm@0.45.2** - ORM +- **pg@8.20.0** - PostgreSQL driver +- **@popov/jwt@1.0.1** - JWT utilities +- **preact@10.22.0** - UI library +- **happy-dom@16.0.0** - DOM testing + +--- + +## 🔗 Related Resources + +- **Repository**: https://git.polytech.djalim.fr/djalim/PolyMPR +- **Issue Tracker**: Gitea (via `tea` CLI) +- **Wiki**: Check CONTRIBUTING.md for dev setup +- **Database**: PostgreSQL (configured in `.env`) + +--- + +## 💡 Important Notes + +1. **Current Limitation**: The database schema in `/databases/schema.ts` does + NOT match the final ER diagram. This is a priority migration task. +2. **Design System**: Follow the Figma prototype for all UI work. +3. **Module Pattern**: Each module should follow the same pattern: routes, API + endpoints, components, and tests. +4. **Permissions**: All admin operations should respect the ROLE_PERMISSION + system. +5. **Fresh Conventions**: Routes use Fresh's file-based routing convention + (e.g., `routes/path/index.tsx`). diff --git a/bugs.md b/bugs.md new file mode 100644 index 0000000..46cf4b7 --- /dev/null +++ b/bugs.md @@ -0,0 +1,158 @@ +# Bug Report — PolyMPR + +> Généré le 2026-04-23 + +--- + +## 🔴 Critique + +### #1 — Schema mismatch : module mobility entièrement cassé + +**Fichier** : `routes/(apps)/mobility/api/insert_mobility.ts` + +Références à des colonnes inexistantes dans le schéma Drizzle : + +| Utilisé dans le code | Colonne réelle | +| ---------------------- | ------------------ | +| `students.userId` | `students.numEtud` | +| `students.firstName` | `students.nom` | +| `students.lastName` | `students.prenom` | +| `students.promotionId` | `students.idPromo` | +| `promotions.endyear` | `promotions.annee` | +| `promotions.current` | _(n'existe pas)_ | + +Le module crashe à l'exécution. À corriger en alignant les noms de colonnes avec +le schéma. + +--- + +### #2 — Auth manquante sur de nombreux endpoints + +Les endpoints suivants n'ont aucune vérification `eduPersonPrimaryAffiliation` : + +- `routes/(apps)/notes/api/notes.ts` (GET, POST) +- `routes/(apps)/notes/api/ue-modules.ts` (GET, POST) +- `routes/(apps)/notes/api/ues.ts` (GET, POST) +- `routes/(apps)/notes/api/ues/[idUE].ts` (GET, PUT, DELETE) +- `routes/(apps)/admin/api/users.ts` (GET, POST) +- `routes/(apps)/admin/api/users/[id].ts` (GET, PUT, DELETE) +- `routes/(apps)/admin/api/modules/[idModule].ts` (GET, PUT, DELETE) +- `routes/(apps)/admin/api/roles.ts` (GET, POST) +- `routes/(apps)/admin/api/roles/[idRole].ts` (GET, PUT, DELETE) +- `routes/(apps)/admin/api/permissions.ts` (GET) +- `routes/(apps)/mobility/api/insert_mobility.ts` + +Tous ces endpoints exposent des données sensibles sans vérifier les permissions. + +--- + +## 🟠 Haut + +### #3 — Bug Drizzle ORM : `.where()` avec plusieurs `eq()` sans `and()` + +**Fichier** : `routes/(apps)/notes/api/ajustements/[numEtud]/[idUE].ts` — lignes +34, 72, 100 + +`.where()` n'accepte qu'un seul argument. Passer plusieurs `eq()` séparés par +des virgules ne génère pas le SQL attendu (seule la première condition est prise +en compte). + +```ts +// ❌ Incorrect +.where(eq(ajustements.numEtud, numEtud), eq(ajustements.idUE, idUE)) + +// ✅ Correct +.where(and(eq(ajustements.numEtud, numEtud), eq(ajustements.idUE, idUE))) +``` + +--- + +### #4 — Bug Drizzle ORM : `.where()` à 3 conditions sans `and()` + +**Fichier** : +`routes/(apps)/notes/api/ue-modules/[idModule]/[idUE]/[idPromo].ts` — handler +GET (~ligne 41) + +Même problème que #3, mais avec 3 conditions. Les handlers PUT et DELETE ont +déjà `and()`, seul le GET est affecté. + +```ts +// ❌ Incorrect +.where( + eq(ueModules.idModule, idModule), + eq(ueModules.idUE, idUE), + eq(ueModules.idPromo, idPromo), +) + +// ✅ Correct +.where( + and( + eq(ueModules.idModule, idModule), + eq(ueModules.idUE, idUE), + eq(ueModules.idPromo, idPromo), + ), +) +``` + +--- + +## 🟡 Moyen + +### #5 — `and()` passé avec des valeurs `undefined` + +**Fichier** : `routes/(apps)/notes/api/ue-modules.ts` + +```ts +and( + idPromo ? eq(ueModules.idPromo, idPromo) : undefined, + idUE ? eq(ueModules.idUE, idUE) : undefined, +); +``` + +Drizzle tolère les `undefined` dans `and()` dans certaines versions, mais ce +n'est pas garanti. Mieux vaut construire les conditions dynamiquement avant de +les passer. + +--- + +### #6 — Validation `!numEtud` rejette faussement `0` + +**Fichier** : `routes/(apps)/notes/api/notes.ts` — handler POST + +```ts +// ❌ Rejette numEtud = 0 +if (note === undefined || !numEtud || !idModule) + +// ✅ Correct +if (note === undefined || numEtud === undefined || numEtud === null || !idModule) +``` + +--- + +### #7 — `Number(idRole)` sans vérification `isNaN` + +**Fichier** : `routes/(apps)/admin/api/users.ts` + +Si `idRole` est une chaîne non numérique, `Number()` retourne `NaN` ce qui +provoque une erreur SQL. + +```ts +// ❌ Pas de vérification +const rows = idRole + ? await db.select().from(users).where(eq(users.idRole, Number(idRole))) + : await db.select().from(users); + +// ✅ Valider avant usage +const role = Number(idRole); +if (isNaN(role)) return new Response(..., { status: 400 }); +``` + +--- + +### #8 — Réponses d'erreur en texte brut au lieu de JSON + +**Fichier** : `routes/(apps)/notes/api/notes.ts` + +Certaines réponses d'erreur retournent une string sans +`content-type: application/json`, incohérent avec le reste de l'API qui retourne +`{ error: "..." }`. diff --git a/compose.yml b/compose.yml index 570a02f..f2abf83 100644 --- a/compose.yml +++ b/compose.yml @@ -16,11 +16,9 @@ services: image: postgres restart: always shm_size: 128mb - environment: + environment: POSTGRES_PASSWORD: ${POSTGRES_PASS} deploy: replicas: 1 placement: constraints: [node.role == manager] - - diff --git a/databases/migrations/meta/0000_snapshot.json b/databases/migrations/meta/0000_snapshot.json index a99e37c..819cf78 100644 --- a/databases/migrations/meta/0000_snapshot.json +++ b/databases/migrations/meta/0000_snapshot.json @@ -677,4 +677,4 @@ "schemas": {}, "tables": {} } -} \ No newline at end of file +} diff --git a/databases/migrations/meta/_journal.json b/databases/migrations/meta/_journal.json index 6834a0b..ad99452 100644 --- a/databases/migrations/meta/_journal.json +++ b/databases/migrations/meta/_journal.json @@ -10,4 +10,4 @@ "breakpoints": true } ] -} \ No newline at end of file +} diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..c8abda9 --- /dev/null +++ b/flake.lock @@ -0,0 +1,61 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1776548001, + "narHash": "sha256-ZSK0NL4a1BwVbbTBoSnWgbJy9HeZFXLYQizjb2DPF24=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "b12141ef619e0a9c1c84dc8c684040326f27cdcc", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..a9b867c --- /dev/null +++ b/flake.nix @@ -0,0 +1,62 @@ +{ + description = "PolyMPR CLI - A tool for managing PolyMPR modules"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + flake-utils.url = "github:numtide/flake-utils"; + }; + + outputs = { self, nixpkgs, flake-utils }: + flake-utils.lib.eachDefaultSystem (system: + let + pkgs = nixpkgs.legacyPackages.${system}; + in + { + packages.pmpr = pkgs.stdenv.mkDerivation { + pname = "pmpr"; + version = "0.1.0"; + src = ./.; + + nativeBuildInputs = [ + pkgs.deno + pkgs.autoPatchelfHook + ]; + + buildInputs = [ + pkgs.stdenv.cc.cc.lib + ]; + + buildPhase = '' + export HOME=$TMPDIR + deno cache toolbox/cli.ts + deno compile -A --output pmpr toolbox/cli.ts + ''; + + installPhase = '' + mkdir -p $out/bin + cp pmpr $out/bin/pmpr + ''; + }; + + packages.default = self.packages.${system}.pmpr; + + devShells.default = pkgs.mkShell { + nativeBuildInputs = [ + pkgs.deno + pkgs.patchelf + ]; + + buildInputs = [ + pkgs.stdenv.cc.cc.lib + ]; + + shellHook = '' + export LD_LIBRARY_PATH="${pkgs.stdenv.cc.cc.lib}/lib:$LD_LIBRARY_PATH" + export NIX_LD_INTERPRETER=$(cat ${pkgs.stdenv.cc}/nix-support/dynamic-linker) + echo "Welcome to PolyMPR development shell!" + echo "Use 'deno task compile' to build the CLI." + ''; + }; + } + ); +} diff --git a/package.json b/package.json index 4cf5711..bbd458d 100644 --- a/package.json +++ b/package.json @@ -9,4 +9,4 @@ "drizzle-kit": "^0.31.10", "tsx": "^4.21.0" } -} \ No newline at end of file +} diff --git a/routes/(apps)/admin/api/enseignements.ts b/routes/(apps)/admin/api/enseignements.ts index 0f6c09d..06408bc 100644 --- a/routes/(apps)/admin/api/enseignements.ts +++ b/routes/(apps)/admin/api/enseignements.ts @@ -4,7 +4,7 @@ import { enseignements } from "$root/databases/schema.ts"; import { AuthenticatedState } from "$root/defaults/interfaces.ts"; import { and, eq } from "npm:drizzle-orm@0.45.2"; -const NOT_FOUND = new Response( +const _NOT_FOUND = new Response( JSON.stringify({ error: "Ressource introuvable" }), { status: 404, headers: { "content-type": "application/json" } }, ); diff --git a/routes/(apps)/admin/api/modules.ts b/routes/(apps)/admin/api/modules.ts index 582e215..2cb2fe7 100644 --- a/routes/(apps)/admin/api/modules.ts +++ b/routes/(apps)/admin/api/modules.ts @@ -2,7 +2,7 @@ import { FreshContext, Handlers } from "$fresh/server.ts"; import { db } from "$root/databases/db.ts"; import { modules } from "$root/databases/schema.ts"; import { AuthenticatedState } from "$root/defaults/interfaces.ts"; -import { eq } from "npm:drizzle-orm"; +import { eq } from "npm:drizzle-orm@0.45.2"; export const handler: Handlers = { // #23 GET /modules diff --git a/routes/(apps)/admin/api/modules/[idModule].ts b/routes/(apps)/admin/api/modules/[idModule].ts index 3062772..6f17dfe 100644 --- a/routes/(apps)/admin/api/modules/[idModule].ts +++ b/routes/(apps)/admin/api/modules/[idModule].ts @@ -2,7 +2,7 @@ import { FreshContext, Handlers } from "$fresh/server.ts"; import { db } from "$root/databases/db.ts"; import { modules } from "$root/databases/schema.ts"; import { AuthenticatedState } from "$root/defaults/interfaces.ts"; -import { eq } from "npm:drizzle-orm"; +import { eq } from "npm:drizzle-orm@0.45.2"; const NOT_FOUND = new Response( JSON.stringify({ error: "Ressource introuvable" }), diff --git a/routes/(apps)/notes/api/notes.ts b/routes/(apps)/notes/api/notes.ts index 0dcdf39..22d387e 100644 --- a/routes/(apps)/notes/api/notes.ts +++ b/routes/(apps)/notes/api/notes.ts @@ -1,7 +1,7 @@ import { Handlers } from "$fresh/server.ts"; import { db } from "../../../../databases/db.ts"; import { notes } from "../../../../databases/schema.ts"; -import { eq } from "npm:drizzle-orm"; +import { eq } from "npm:drizzle-orm@0.45.2"; export const handler: Handlers = { // #42 GET /notes @@ -44,10 +44,13 @@ export const handler: Handlers = { const { note, numEtud, idModule } = body; if (note === undefined || !numEtud || !idModule) { - return new Response("Champs 'note', 'numEtud' et 'idModule' requis", { status: 400 }); + return new Response("Champs 'note', 'numEtud' et 'idModule' requis", { + status: 400, + }); } - const result = await db.insert(notes).values({ note, numEtud, idModule }).returning(); + const result = await db.insert(notes).values({ note, numEtud, idModule }) + .returning(); return new Response(JSON.stringify(result[0]), { status: 201, @@ -58,4 +61,4 @@ export const handler: Handlers = { return new Response("Failed to create note", { status: 500 }); } }, -}; \ No newline at end of file +}; diff --git a/routes/(apps)/notes/api/notes/[numEtud]/[idModule].ts b/routes/(apps)/notes/api/notes/[numEtud]/[idModule].ts index 24d8a28..8618366 100644 --- a/routes/(apps)/notes/api/notes/[numEtud]/[idModule].ts +++ b/routes/(apps)/notes/api/notes/[numEtud]/[idModule].ts @@ -1,20 +1,23 @@ import { Handlers } from "$fresh/server.ts"; import { db } from "../../../../../../databases/db.ts"; import { notes } from "../../../../../../databases/schema.ts"; -import { and, eq } from "npm:drizzle-orm"; +import { and, eq } from "npm:drizzle-orm@0.45.2"; export const handler: Handlers = { - // #45 GET /notes/:numEtud/:idModule + // #45 GET /notes/:numEtud/:idModule async GET(_request, context) { try { const numEtud = parseInt(context.params.numEtud); const { idModule } = context.params; if (isNaN(numEtud)) { - return new Response(JSON.stringify({ error: "Paramètre numEtud invalide" }), { - status: 400, - headers: { "Content-Type": "application/json" }, - }); + return new Response( + JSON.stringify({ error: "Paramètre numEtud invalide" }), + { + status: 400, + headers: { "Content-Type": "application/json" }, + }, + ); } const result = await db.select().from(notes).where( @@ -25,10 +28,13 @@ export const handler: Handlers = { ); if (result.length === 0) { - return new Response(JSON.stringify({ error: "Ressource introuvable" }), { - status: 404, - headers: { "Content-Type": "application/json" }, - }); + return new Response( + JSON.stringify({ error: "Ressource introuvable" }), + { + status: 404, + headers: { "Content-Type": "application/json" }, + }, + ); } return new Response(JSON.stringify(result[0]), { @@ -48,10 +54,13 @@ export const handler: Handlers = { const { idModule } = context.params; if (isNaN(numEtud)) { - return new Response(JSON.stringify({ error: "Paramètre numEtud invalide" }), { - status: 400, - headers: { "Content-Type": "application/json" }, - }); + return new Response( + JSON.stringify({ error: "Paramètre numEtud invalide" }), + { + status: 400, + headers: { "Content-Type": "application/json" }, + }, + ); } const body = await request.json(); @@ -69,10 +78,13 @@ export const handler: Handlers = { ).returning(); if (result.length === 0) { - return new Response(JSON.stringify({ error: "Ressource introuvable" }), { - status: 404, - headers: { "Content-Type": "application/json" }, - }); + return new Response( + JSON.stringify({ error: "Ressource introuvable" }), + { + status: 404, + headers: { "Content-Type": "application/json" }, + }, + ); } return new Response(JSON.stringify(result[0]), { @@ -92,10 +104,13 @@ export const handler: Handlers = { const { idModule } = context.params; if (isNaN(numEtud)) { - return new Response(JSON.stringify({ error: "Paramètre numEtud invalide" }), { - status: 400, - headers: { "Content-Type": "application/json" }, - }); + return new Response( + JSON.stringify({ error: "Paramètre numEtud invalide" }), + { + status: 400, + headers: { "Content-Type": "application/json" }, + }, + ); } const result = await db.delete(notes).where( @@ -106,10 +121,13 @@ export const handler: Handlers = { ).returning(); if (result.length === 0) { - return new Response(JSON.stringify({ error: "Ressource introuvable" }), { - status: 404, - headers: { "Content-Type": "application/json" }, - }); + return new Response( + JSON.stringify({ error: "Ressource introuvable" }), + { + status: 404, + headers: { "Content-Type": "application/json" }, + }, + ); } return new Response(null, { status: 204 }); @@ -118,4 +136,4 @@ export const handler: Handlers = { return new Response("Failed to delete note", { status: 500 }); } }, -}; \ No newline at end of file +}; diff --git a/routes/(apps)/notes/api/ue-modules.ts b/routes/(apps)/notes/api/ue-modules.ts index ba56b66..8cd48bc 100644 --- a/routes/(apps)/notes/api/ue-modules.ts +++ b/routes/(apps)/notes/api/ue-modules.ts @@ -1,10 +1,10 @@ import { Handlers } from "$fresh/server.ts"; import { db } from "../../../../databases/db.ts"; import { ueModules } from "../../../../databases/schema.ts"; -import { and, eq } from "npm:drizzle-orm"; +import { and, eq } from "npm:drizzle-orm@0.45.2"; export const handler: Handlers = { - // #37 GET /ue-modules + // #37 GET /ue-modules async GET(request) { try { const url = new URL(request.url); @@ -33,7 +33,7 @@ export const handler: Handlers = { return new Response("Failed to fetch data", { status: 500 }); } }, - + // #38 POST /ue-modules async POST(request) { try { @@ -41,10 +41,18 @@ export const handler: Handlers = { const { idModule, idUE, idPromo, coeff } = body; if (!idModule || !idUE || !idPromo || coeff === undefined) { - return new Response("Champs 'idModule', 'idUE', 'idPromo' et 'coeff' requis", { status: 400 }); + return new Response( + "Champs 'idModule', 'idUE', 'idPromo' et 'coeff' requis", + { status: 400 }, + ); } - const result = await db.insert(ueModules).values({ idModule, idUE, idPromo, coeff }).returning(); + const result = await db.insert(ueModules).values({ + idModule, + idUE, + idPromo, + coeff, + }).returning(); return new Response(JSON.stringify(result[0]), { status: 201, @@ -55,4 +63,4 @@ export const handler: Handlers = { return new Response("Failed to create UE-module", { status: 500 }); } }, -}; \ No newline at end of file +}; diff --git a/routes/(apps)/notes/api/ue-modules/[idModule]/[idUE]/[idPromo].ts b/routes/(apps)/notes/api/ue-modules/[idModule]/[idUE]/[idPromo].ts index 676e05b..f447f12 100644 --- a/routes/(apps)/notes/api/ue-modules/[idModule]/[idUE]/[idPromo].ts +++ b/routes/(apps)/notes/api/ue-modules/[idModule]/[idUE]/[idPromo].ts @@ -91,14 +91,17 @@ export const handler: Handlers = { if (!updated) return NOT_FOUND; - return new Response(JSON.stringify({ - idModule: updated.idModule, - idUE: updated.idUE, - idPromo: updated.idPromo, - coeff: updated.coeff, - }), { - headers: { "content-type": "application/json" }, - }); + return new Response( + JSON.stringify({ + idModule: updated.idModule, + idUE: updated.idUE, + idPromo: updated.idPromo, + coeff: updated.coeff, + }), + { + headers: { "content-type": "application/json" }, + }, + ); }, // #41 DELETE /ue-modules/{idModule}/{idUE}/{idPromo} diff --git a/routes/(apps)/notes/api/ues.ts b/routes/(apps)/notes/api/ues.ts index 19b7d51..757245c 100644 --- a/routes/(apps)/notes/api/ues.ts +++ b/routes/(apps)/notes/api/ues.ts @@ -39,4 +39,4 @@ export const handler: Handlers = { return new Response("Failed to create UE", { status: 500 }); } }, -}; \ No newline at end of file +}; diff --git a/routes/(apps)/notes/api/ues/[idUE].ts b/routes/(apps)/notes/api/ues/[idUE].ts index c92e118..c8f586f 100644 --- a/routes/(apps)/notes/api/ues/[idUE].ts +++ b/routes/(apps)/notes/api/ues/[idUE].ts @@ -1,28 +1,34 @@ import { Handlers } from "$fresh/server.ts"; import { db } from "../../../../../databases/db.ts"; import { ues } from "../../../../../databases/schema.ts"; -import { eq } from "npm:drizzle-orm"; +import { eq } from "npm:drizzle-orm@0.45.2"; export const handler: Handlers = { - // # 34 GET /ues/:idUE + // # 34 GET /ues/:idUE async GET(_request, context) { try { const idUE = parseInt(context.params.idUE); if (isNaN(idUE)) { - return new Response(JSON.stringify({ error: "Paramètre idUE invalide" }), { - status: 400, - headers: { "Content-Type": "application/json" }, - }); + return new Response( + JSON.stringify({ error: "Paramètre idUE invalide" }), + { + status: 400, + headers: { "Content-Type": "application/json" }, + }, + ); } const result = await db.select().from(ues).where(eq(ues.id, idUE)); if (result.length === 0) { - return new Response(JSON.stringify({ error: "Ressource introuvable" }), { - status: 404, - headers: { "Content-Type": "application/json" }, - }); + return new Response( + JSON.stringify({ error: "Ressource introuvable" }), + { + status: 404, + headers: { "Content-Type": "application/json" }, + }, + ); } return new Response(JSON.stringify(result[0]), { @@ -41,10 +47,13 @@ export const handler: Handlers = { const idUE = parseInt(context.params.idUE); if (isNaN(idUE)) { - return new Response(JSON.stringify({ error: "Paramètre idUE invalide" }), { - status: 400, - headers: { "Content-Type": "application/json" }, - }); + return new Response( + JSON.stringify({ error: "Paramètre idUE invalide" }), + { + status: 400, + headers: { "Content-Type": "application/json" }, + }, + ); } const body = await request.json(); @@ -54,13 +63,17 @@ export const handler: Handlers = { return new Response("Champ 'nom' manquant", { status: 400 }); } - const result = await db.update(ues).set({ nom }).where(eq(ues.id, idUE)).returning(); + const result = await db.update(ues).set({ nom }).where(eq(ues.id, idUE)) + .returning(); if (result.length === 0) { - return new Response(JSON.stringify({ error: "Ressource introuvable" }), { - status: 404, - headers: { "Content-Type": "application/json" }, - }); + return new Response( + JSON.stringify({ error: "Ressource introuvable" }), + { + status: 404, + headers: { "Content-Type": "application/json" }, + }, + ); } return new Response(JSON.stringify(result[0]), { @@ -79,19 +92,25 @@ export const handler: Handlers = { const idUE = parseInt(context.params.idUE); if (isNaN(idUE)) { - return new Response(JSON.stringify({ error: "Paramètre idUE invalide" }), { - status: 400, - headers: { "Content-Type": "application/json" }, - }); + return new Response( + JSON.stringify({ error: "Paramètre idUE invalide" }), + { + status: 400, + headers: { "Content-Type": "application/json" }, + }, + ); } const result = await db.delete(ues).where(eq(ues.id, idUE)).returning(); if (result.length === 0) { - return new Response(JSON.stringify({ error: "Ressource introuvable" }), { - status: 404, - headers: { "Content-Type": "application/json" }, - }); + return new Response( + JSON.stringify({ error: "Ressource introuvable" }), + { + status: 404, + headers: { "Content-Type": "application/json" }, + }, + ); } return new Response(null, { status: 204 }); diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000..ab0e69a --- /dev/null +++ b/shell.nix @@ -0,0 +1,23 @@ +{ pkgs ? import {} }: + +pkgs.mkShell { + name = "polympr-dev"; + nativeBuildInputs = [ + pkgs.deno + pkgs.patchelf + pkgs.tea + ]; + + buildInputs = [ + pkgs.stdenv.cc.cc.lib + ]; + + shellHook = '' + export LD_LIBRARY_PATH="${pkgs.stdenv.cc.cc.lib}/lib:$LD_LIBRARY_PATH" + # Find the dynamic linker + export NIX_LD_INTERPRETER=$(cat ${pkgs.stdenv.cc}/nix-support/dynamic-linker) + echo "Welcome to PolyMPR development shell!" + echo "Use 'deno task compile' to build the CLI." + echo "If on NixOS, it will be automatically patched." + ''; +} diff --git a/toolbox/compile.sh b/toolbox/compile.sh new file mode 100755 index 0000000..2b5022b --- /dev/null +++ b/toolbox/compile.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +set -e + +# Default output path +OUTPUT_PATH="${HOME}/.deno/bin/pmpr" + +# Ensure directory exists +mkdir -p "$(dirname "$OUTPUT_PATH")" + +# Check if we are on a system that needs patching (like NixOS) +IS_NIXOS=false +if [ "$(uname)" = "Linux" ]; then + if [ ! -f /lib64/ld-linux-x86-64.so.2 ] || ls -l /lib64/ld-linux-x86-64.so.2 | grep -q "stub-ld"; then + IS_NIXOS=true + fi +fi + +if [ "$IS_NIXOS" = true ]; then + echo "NixOS detected. Creating a wrapper script instead of a compiled binary to avoid linking issues with Deno." + # Use absolute paths for config and script to make it work from anywhere + PROJECT_ROOT="$(pwd)" + cat > "$OUTPUT_PATH" <