From e94218bf07f2e22a7c4a93fee475c16ffe485fdf Mon Sep 17 00:00:00 2001 From: Eylexander Date: Sun, 5 Jul 2026 02:22:07 +0200 Subject: [PATCH 1/4] refactor(orm): add new foundation for orm change --- .gitignore | 8 + README.md | 10 +- backend/package-lock.json | 1155 ++++++++++++++++++++++- backend/package.json | 15 +- backend/prisma.config.ts | 16 + backend/prisma/models/challenges.prisma | 28 + backend/prisma/models/event.prisma | 9 + backend/prisma/models/faction.prisma | 7 + backend/prisma/models/news.prisma | 10 + backend/prisma/models/permanence.prisma | 33 + backend/prisma/models/role.prisma | 14 + backend/prisma/models/team.prisma | 26 + backend/prisma/models/user.prisma | 80 ++ backend/prisma/schema.prisma | 8 + backend/prisma/seed.ts | 137 +++ backend/src/prisma/db.ts | 20 + docker-compose.yml | 2 +- integration.utt.fr-key.pem | 52 +- integration.utt.fr.pem | 47 +- 19 files changed, 1594 insertions(+), 83 deletions(-) create mode 100644 backend/prisma.config.ts create mode 100644 backend/prisma/models/challenges.prisma create mode 100644 backend/prisma/models/event.prisma create mode 100644 backend/prisma/models/faction.prisma create mode 100644 backend/prisma/models/news.prisma create mode 100644 backend/prisma/models/permanence.prisma create mode 100644 backend/prisma/models/role.prisma create mode 100644 backend/prisma/models/team.prisma create mode 100644 backend/prisma/models/user.prisma create mode 100644 backend/prisma/schema.prisma create mode 100644 backend/prisma/seed.ts create mode 100644 backend/src/prisma/db.ts diff --git a/.gitignore b/.gitignore index 47046e0..598b48b 100644 --- a/.gitignore +++ b/.gitignore @@ -8,8 +8,16 @@ backend/uploads/ backend/exports/ backend/dist +# Prisma generated files +backend/src/prisma/build/**/*.ts + # Autres fichiers à ignorer .DS_Store *.log *.env google_credentials.json + + +# Ignorer les clés NGINX pour simuler la prod +integration.utt.fr-key.pem +integration.utt.fr.pem \ No newline at end of file diff --git a/README.md b/README.md index 2211397..298687e 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Ce projet est une plateforme pour le site d'intégration de l'Université de Tec - **Backend** : - Node.js - Express.js - - Drizzle ORM + - Prisma ORM - PostgreSQL client - Docker @@ -57,7 +57,7 @@ docker-compose up --build Cette commande : - Lance PostgreSQL -- Démarre le backend avec migration automatique via Drizzle ORM +- Démarre le backend avec synchronisation du schéma Prisma et seed automatique - Compile et sert le frontend ### 4. Accéder aux services @@ -68,9 +68,9 @@ Cette commande : --- -## 🧪 Gestion des migrations +## 🧪 Gestion du schéma et des seeds -Le backend utilise **Drizzle ORM** pour la gestion des migrations. +Le backend utilise **Prisma ORM** pour synchroniser le schéma et injecter les données de base. La commande suivante est exécutée automatiquement au démarrage : @@ -78,6 +78,8 @@ La commande suivante est exécutée automatiquement au démarrage : npm run migrate ``` +Elle exécute `prisma generate`, `prisma db push`, puis `prisma db seed`. + --- ## 📁 Structure du projet diff --git a/backend/package-lock.json b/backend/package-lock.json index 51d98ab..28d431b 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -10,6 +10,8 @@ "license": "ISC", "dependencies": { "@electric-sql/pglite": "^0.2.17", + "@prisma/adapter-pg": "^7.8.0", + "@prisma/client": "^7.8.0", "@types/multer": "^1.4.12", "axios": "^1.8.4", "bcryptjs": "^3.0.2", @@ -31,7 +33,7 @@ "multer": "^2.1.1", "nodemailer": "^9.0.1", "papaparse": "^5.5.2", - "pg": "^8.14.1", + "pg": "^8.22.0", "pg-promise": "^12.6.2", "postgres": "^3.4.5", "randombytes": "^2.1.0", @@ -44,12 +46,13 @@ "@types/express": "^5.0.1", "@types/node": "^22.14.1", "@types/papaparse": "^5.3.15", - "@types/pg": "^8.11.12", + "@types/pg": "^8.20.0", "eslint": "^9.39.4", "eslint-plugin-import": "^2.32.0", "eslint-plugin-unused-imports": "^4.4.1", "globals": "^15.15.0", "nodemon": "^3.1.9", + "prisma": "^7.8.0", "ts-node": "^10.9.2", "tsx": "^4.19.3", "typescript": "^5.8.3", @@ -809,6 +812,19 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@hono/node-server": { + "version": "1.19.11", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.11.tgz", + "integrity": "sha512-dr8/3zEaB+p0D2n/IUrlPF1HZm586qgJNXK1a9fhg/PzdtkK7Ksd5l312tJX2yBuALqDYBlG20QEbayqPyxn+g==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=18.14.1" + }, + "peerDependencies": { + "hono": "^4" + } + }, "node_modules/@humanfs/core": { "version": "0.19.2", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", @@ -920,6 +936,13 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@kurkle/color": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz", + "integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==", + "devOptional": true, + "license": "MIT" + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -930,6 +953,426 @@ "node": ">=14" } }, + "node_modules/@prisma/adapter-pg": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@prisma/adapter-pg/-/adapter-pg-7.8.0.tgz", + "integrity": "sha512-ygb3UkerK3v8MDpXVgCISdRNDozpxh6+JVJgiIGbSr5KBgz10LLf5ejUskPGoXlsIjxsOu6nuy1JVQr2EKGSlg==", + "license": "Apache-2.0", + "dependencies": { + "@prisma/driver-adapter-utils": "7.8.0", + "@types/pg": "^8.16.0", + "pg": "^8.16.3", + "postgres-array": "3.0.4" + } + }, + "node_modules/@prisma/adapter-pg/node_modules/postgres-array": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-3.0.4.tgz", + "integrity": "sha512-nAUSGfSDGOaOAEGwqsRY27GPOea7CNipJPOA7lPbdEpx5Kg3qzdP0AaWC5MlhTWV9s4hFX39nomVZ+C4tnGOJQ==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/@prisma/client": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-7.8.0.tgz", + "integrity": "sha512-HFp3Dawv/3sU3JtlPha90IB+48lS7zHiH4LKZPjmcE8YH5P9DOXGPvo8dqOtO7MqLDd1p2hOWMcFlRT1DMblHw==", + "license": "Apache-2.0", + "dependencies": { + "@prisma/client-runtime-utils": "7.8.0" + }, + "engines": { + "node": "^20.19 || ^22.12 || >=24.0" + }, + "peerDependencies": { + "prisma": "*", + "typescript": ">=5.4.0" + }, + "peerDependenciesMeta": { + "prisma": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/@prisma/client-runtime-utils": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@prisma/client-runtime-utils/-/client-runtime-utils-7.8.0.tgz", + "integrity": "sha512-5NQZztQ0oY/ADFkmd9gPuweH5A1/CCY8YQPorLLO0Mu6a87mY5gsnDkzmFmIHs9NFaLnZojzgddFVN4RpKYrdw==", + "license": "Apache-2.0" + }, + "node_modules/@prisma/config": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@prisma/config/-/config-7.8.0.tgz", + "integrity": "sha512-HFESzd9rx2ZQxlK+TL7tu1HPvCqrHiL6LCxYykI2c34mvaUuIVVl3lYuicJD/MNnzgPnyeBEMlK4WTomJCV5jw==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "c12": "3.3.4", + "deepmerge-ts": "7.1.5", + "effect": "3.20.0", + "empathic": "2.0.0" + } + }, + "node_modules/@prisma/debug": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-7.8.0.tgz", + "integrity": "sha512-p+QZReysDUqXC+mk17q9a+Y/qzh4c2KYliDK30buYUyfrGeTGSyfmc0AIrJRhZJrLHhRiJa9Au/J72h3C+szvA==", + "license": "Apache-2.0" + }, + "node_modules/@prisma/dev": { + "version": "0.24.3", + "resolved": "https://registry.npmjs.org/@prisma/dev/-/dev-0.24.3.tgz", + "integrity": "sha512-ffHlQuKXZiaDt9Go0OnCTdJZrHxK0k7omJKNV86/VjpsXu5EIHZLK0T7JSWgvNlJwh56kW9JFu9v0qJciFzepg==", + "devOptional": true, + "license": "ISC", + "dependencies": { + "@electric-sql/pglite": "0.4.1", + "@electric-sql/pglite-socket": "0.1.1", + "@electric-sql/pglite-tools": "0.3.1", + "@hono/node-server": "1.19.11", + "@prisma/get-platform": "7.2.0", + "@prisma/query-plan-executor": "7.2.0", + "@prisma/streams-local": "0.1.2", + "foreground-child": "3.3.1", + "get-port-please": "3.2.0", + "hono": "^4.12.8", + "http-status-codes": "2.3.0", + "pathe": "2.0.3", + "proper-lockfile": "4.1.2", + "remeda": "2.33.4", + "std-env": "3.10.0", + "valibot": "1.2.0", + "zeptomatch": "2.1.0" + } + }, + "node_modules/@prisma/dev/node_modules/@electric-sql/pglite": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@electric-sql/pglite/-/pglite-0.4.1.tgz", + "integrity": "sha512-mZ9NzzUSYPOCnxHH1oAHPRzoMFJHY472raDKwXl/+6oPbpdJ7g8LsCN4FSaIIfkiCKHhb3iF/Zqo3NYxaIhU7Q==", + "devOptional": true, + "license": "Apache-2.0" + }, + "node_modules/@prisma/dev/node_modules/@electric-sql/pglite-socket": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@electric-sql/pglite-socket/-/pglite-socket-0.1.1.tgz", + "integrity": "sha512-p2hoXw3Z3LQHwTeikdZNsFBOvXGqKY2hk51BBw+8NKND8eoH+8LFOtW9Z8CQKmTJ2qqGYu82ipqiyFZOTTXNfw==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "pglite-server": "dist/scripts/server.js" + }, + "peerDependencies": { + "@electric-sql/pglite": "0.4.1" + } + }, + "node_modules/@prisma/dev/node_modules/@electric-sql/pglite-tools": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@electric-sql/pglite-tools/-/pglite-tools-0.3.1.tgz", + "integrity": "sha512-C+T3oivmy9bpQvSxVqXA1UDY8cB9Eb9vZHL9zxWwEUfDixbXv4G3r2LjoTdR33LD8aomR3O9ZXEO3XEwr/cUCA==", + "devOptional": true, + "license": "Apache-2.0", + "peerDependencies": { + "@electric-sql/pglite": "0.4.1" + } + }, + "node_modules/@prisma/driver-adapter-utils": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@prisma/driver-adapter-utils/-/driver-adapter-utils-7.8.0.tgz", + "integrity": "sha512-/Q13o0ZT0rjc1Xk0Q9KhZYwuq2EW/vSbWUBKfgEKkaCuB/Sg6bqnjmTZqC5cD4d6y1vfFAEwBRzfzoSMIVJ55A==", + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "7.8.0" + } + }, + "node_modules/@prisma/engines": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-7.8.0.tgz", + "integrity": "sha512-jx3rCnNNrt5uzbkKlegtQ2GZHxSlihMCzutgT/BP6UIDF1r9tDI39hV/0T/cHZgzJ3ELbuQPXlVZy+Y1n0pcgw==", + "devOptional": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "7.8.0", + "@prisma/engines-version": "7.8.0-6.3c6e192761c0362d496ed980de936e2f3cebcd3a", + "@prisma/fetch-engine": "7.8.0", + "@prisma/get-platform": "7.8.0" + } + }, + "node_modules/@prisma/engines-version": { + "version": "7.8.0-6.3c6e192761c0362d496ed980de936e2f3cebcd3a", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-7.8.0-6.3c6e192761c0362d496ed980de936e2f3cebcd3a.tgz", + "integrity": "sha512-fJPQxCkLgA5EayWaW8eArgCvjJ+N+Kz3VyeNKMEeYiQC4alNkxRKFVAGxv/ZUzuJISKqdw+zGeDbS6mn6RCPOA==", + "devOptional": true, + "license": "Apache-2.0" + }, + "node_modules/@prisma/engines/node_modules/@prisma/get-platform": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.8.0.tgz", + "integrity": "sha512-WlxgRGnolL8VH2EmkH1R/DkKNr/mVdS3G2h42IZFFZ3eUrH9OT6t73kIOSlkkrv50wG123Iq8d96ufv5LlZktw==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "7.8.0" + } + }, + "node_modules/@prisma/fetch-engine": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-7.8.0.tgz", + "integrity": "sha512-gwB0Euiz/DDRyxFRpLXYlK3RfaZUj1c5dAYMuhZYfApg7arknJlcb9bIsOHDppJmbqYaVA+yBIiFMDBfprsNPQ==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "7.8.0", + "@prisma/engines-version": "7.8.0-6.3c6e192761c0362d496ed980de936e2f3cebcd3a", + "@prisma/get-platform": "7.8.0" + } + }, + "node_modules/@prisma/fetch-engine/node_modules/@prisma/get-platform": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.8.0.tgz", + "integrity": "sha512-WlxgRGnolL8VH2EmkH1R/DkKNr/mVdS3G2h42IZFFZ3eUrH9OT6t73kIOSlkkrv50wG123Iq8d96ufv5LlZktw==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "7.8.0" + } + }, + "node_modules/@prisma/get-platform": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.2.0.tgz", + "integrity": "sha512-k1V0l0Td1732EHpAfi2eySTezyllok9dXb6UQanajkJQzPUGi3vO2z7jdkz67SypFTdmbnyGYxvEvYZdZsMAVA==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "7.2.0" + } + }, + "node_modules/@prisma/get-platform/node_modules/@prisma/debug": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-7.2.0.tgz", + "integrity": "sha512-YSGTiSlBAVJPzX4ONZmMotL+ozJwQjRmZweQNIq/ER0tQJKJynNkRB3kyvt37eOfsbMCXk3gnLF6J9OJ4QWftw==", + "devOptional": true, + "license": "Apache-2.0" + }, + "node_modules/@prisma/query-plan-executor": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@prisma/query-plan-executor/-/query-plan-executor-7.2.0.tgz", + "integrity": "sha512-EOZmNzcV8uJ0mae3DhTsiHgoNCuu1J9mULQpGCh62zN3PxPTd+qI9tJvk5jOst8WHKQNwJWR3b39t0XvfBB0WQ==", + "devOptional": true, + "license": "Apache-2.0" + }, + "node_modules/@prisma/streams-local": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@prisma/streams-local/-/streams-local-0.1.2.tgz", + "integrity": "sha512-l49yTxKKF2odFxaAXTmwmkBKL3+bVQ1tFOooGifu4xkdb9NMNLxHj27XAhTylWZod8I+ISGM5erU1xcl/oBCtg==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "ajv": "^8.12.0", + "better-result": "^2.7.0", + "env-paths": "^3.0.0", + "proper-lockfile": "^4.1.2" + }, + "engines": { + "bun": ">=1.3.6", + "node": ">=22.0.0" + } + }, + "node_modules/@prisma/streams-local/node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@prisma/streams-local/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@prisma/studio-core": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@prisma/studio-core/-/studio-core-0.27.3.tgz", + "integrity": "sha512-AADjNFPdsrglxHQVTmHFqv6DuKQZ5WY4p5/gVFY017twvNrSwpLJ9lqUbYYxEu2W7nbvVxTZA8deJ8LseNALsw==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@radix-ui/react-toggle": "1.1.10", + "chart.js": "4.5.1" + }, + "engines": { + "node": "^20.19 || ^22.12 || >=24.0", + "pnpm": "8" + }, + "peerDependencies": { + "@types/react": "^18.0.0 || ^19.0.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@radix-ui/primitive": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz", + "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", + "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", + "devOptional": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toggle": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-1.1.10.tgz", + "integrity": "sha512-lS1odchhFTeZv3xwHH31YPObmJn8gOg7Lq12inrr0+BH/l3Tsq32VfjqH1oh80ARM3mlkfMic15n0kg4sD1poQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz", + "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-effect-event": "0.0.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-effect-event": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz", + "integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", + "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==", + "devOptional": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@rtsao/scc": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", @@ -937,6 +1380,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "devOptional": true, + "license": "MIT" + }, "node_modules/@streamparser/json": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/@streamparser/json/-/json-0.0.6.tgz", @@ -1108,10 +1558,9 @@ } }, "node_modules/@types/pg": { - "version": "8.15.4", - "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.15.4.tgz", - "integrity": "sha512-I6UNVBAoYbvuWkkU3oosC8yxqH21f4/Jc4DK71JLG3dT2mdlGe1z+ep/LQGXaKaOgcvUrsQoPRqfgtMcvZiJhg==", - "devOptional": true, + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.20.0.tgz", + "integrity": "sha512-bEPFOaMAHTEP1EzpvHTbmwR8UsFyHSKsRisLIHVMXnpNefSbGA1bD6CVy+qKjGSqmZqNqBDV2azOBo8TgkcVow==", "license": "MIT", "dependencies": { "@types/node": "*", @@ -1131,6 +1580,17 @@ "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", "license": "MIT" }, + "node_modules/@types/react": { + "version": "19.2.17", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz", + "integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==", + "devOptional": true, + "license": "MIT", + "peer": true, + "dependencies": { + "csstype": "^3.2.2" + } + }, "node_modules/@types/send": { "version": "0.17.5", "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.5.tgz", @@ -1733,6 +2193,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/aws-ssl-profiles": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz", + "integrity": "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/axios": { "version": "1.18.1", "resolved": "https://registry.npmjs.org/axios/-/axios-1.18.1.tgz", @@ -1805,6 +2275,13 @@ "bcrypt": "bin/bcrypt" } }, + "node_modules/better-result": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/better-result/-/better-result-2.9.2.tgz", + "integrity": "sha512-WIFoBPCdnTOdk9inkE1ZRvCZ4P0CpSkAiLlchC65N7n9DcjZ3NhqkBOlafzpOVnO8ixyi37kicmSJ3ENhPZl7Q==", + "devOptional": true, + "license": "MIT" + }, "node_modules/big-integer": { "version": "1.6.52", "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz", @@ -1932,6 +2409,78 @@ "node": ">= 0.8" } }, + "node_modules/c12": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/c12/-/c12-3.3.4.tgz", + "integrity": "sha512-cM0ApFQSBXuourJejzwv/AuPRvAxordTyParRVcHjjtXirtkzM0uK2L9TTn9s0cXZbG7E55jCivRQzoxYmRAlA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "chokidar": "^5.0.0", + "confbox": "^0.2.4", + "defu": "^6.1.6", + "dotenv": "^17.3.1", + "exsolve": "^1.0.8", + "giget": "^3.2.0", + "jiti": "^2.6.1", + "ohash": "^2.0.11", + "pathe": "^2.0.3", + "perfect-debounce": "^2.1.0", + "pkg-types": "^2.3.0", + "rc9": "^3.0.1" + }, + "peerDependencies": { + "magicast": "*" + }, + "peerDependenciesMeta": { + "magicast": { + "optional": true + } + } + }, + "node_modules/c12/node_modules/chokidar": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", + "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "readdirp": "^5.0.0" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/c12/node_modules/dotenv": { + "version": "17.4.2", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz", + "integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==", + "devOptional": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/c12/node_modules/readdirp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz", + "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/call-bind": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", @@ -2030,6 +2579,19 @@ "node": ">=8" } }, + "node_modules/chart.js": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.1.tgz", + "integrity": "sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@kurkle/color": "^0.3.0" + }, + "engines": { + "pnpm": ">=8" + } + }, "node_modules/chokidar": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", @@ -2116,6 +2678,13 @@ "typedarray": "^0.0.6" } }, + "node_modules/confbox": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.4.tgz", + "integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==", + "devOptional": true, + "license": "MIT" + }, "node_modules/content-disposition": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", @@ -2202,6 +2771,14 @@ "node": ">=18" } }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "devOptional": true, + "license": "MIT", + "peer": true + }, "node_modules/data-uri-to-buffer": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", @@ -2317,6 +2894,16 @@ "node": ">=0.10.0" } }, + "node_modules/deepmerge-ts": { + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.5.tgz", + "integrity": "sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw==", + "devOptional": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/define-data-property": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", @@ -2353,6 +2940,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/defu": { + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.7.tgz", + "integrity": "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==", + "devOptional": true, + "license": "MIT" + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -2362,6 +2956,16 @@ "node": ">=0.4.0" } }, + "node_modules/denque": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", + "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", + "devOptional": true, + "license": "Apache-2.0", + "engines": { + "node": ">=0.10" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -2371,6 +2975,13 @@ "node": ">= 0.8" } }, + "node_modules/destr": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz", + "integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==", + "devOptional": true, + "license": "MIT" + }, "node_modules/diff": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz", @@ -2648,12 +3259,33 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", "license": "MIT" }, + "node_modules/effect": { + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/effect/-/effect-3.20.0.tgz", + "integrity": "sha512-qMLfDJscrNG8p/aw+IkT9W7fgj50Z4wG5bLBy0Txsxz8iUHjDIkOgO3SV0WZfnQbNG2VJYb0b+rDLMrhM4+Krw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "fast-check": "^3.23.1" + } + }, "node_modules/emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "license": "MIT" }, + "node_modules/empathic": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/empathic/-/empathic-2.0.0.tgz", + "integrity": "sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, "node_modules/encodeurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", @@ -2675,6 +3307,19 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/env-paths": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-3.0.0.tgz", + "integrity": "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/es-abstract": { "version": "1.24.2", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.2.tgz", @@ -3229,17 +3874,47 @@ "url": "https://opencollective.com/express" } }, + "node_modules/exsolve": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.1.0.tgz", + "integrity": "sha512-D+42+T12DdIlJM3uepa55qGiL3sYdLBOxIl2ifQCzCHz4c7eiolaHsi3BIqEr7JxBzxv2pYZQX9kw16ziMcEmw==", + "devOptional": true, + "license": "MIT" + }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", "license": "MIT" }, + "node_modules/fast-check": { + "version": "3.23.2", + "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-3.23.2.tgz", + "integrity": "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==", + "devOptional": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT", + "dependencies": { + "pure-rand": "^6.1.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/fast-json-stable-stringify": { @@ -3256,6 +3931,23 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-uri": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.3.tgz", + "integrity": "sha512-i70LwGWUduXqzicKXWshooq+sWL1K3WUU5rKZNG/0i3a1OSoX3HqhH5WbWwTmqWfor4urUakGPiRQcleRZTwOg==", + "devOptional": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/fetch-blob": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", @@ -3579,6 +4271,16 @@ "node": ">=18" } }, + "node_modules/generate-function": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", + "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "is-property": "^1.0.2" + } + }, "node_modules/generator-function": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", @@ -3613,6 +4315,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-port-please": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/get-port-please/-/get-port-please-3.2.0.tgz", + "integrity": "sha512-I9QVvBw5U/hw3RmWpYKRumUeaDgxTPd401x364rLmWBJcOQ753eov1eTgzDqRG9bqFIfDc7gfzcQEWrUri3o1A==", + "devOptional": true, + "license": "MIT" + }, "node_modules/get-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", @@ -3656,6 +4365,16 @@ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, + "node_modules/giget": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/giget/-/giget-3.3.0.tgz", + "integrity": "sha512-gzi2D96p+AMfDcmJHGDj3KJ9NRiwvlFAU5yfa3ROwWZmFUjX4P43x3BcyRaOMMLto1vUo7C+86+MFhYTl6Ryiw==", + "devOptional": true, + "license": "MIT", + "bin": { + "giget": "dist/cli.mjs" + } + }, "node_modules/glob": { "version": "10.5.0", "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", @@ -3845,6 +4564,27 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "devOptional": true, + "license": "ISC" + }, + "node_modules/grammex": { + "version": "3.1.12", + "resolved": "https://registry.npmjs.org/grammex/-/grammex-3.1.12.tgz", + "integrity": "sha512-6ufJOsSA7LcQehIJNCO7HIBykfM7DXQual0Ny780/DEcJIpBlHRvcqEBWGPYd7hrXL2GJ3oJI1MIhaXjWmLQOQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/graphmatch": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/graphmatch/-/graphmatch-1.1.1.tgz", + "integrity": "sha512-5ykVn/EXM1hF0XCaWh05VbYvEiOL2lY1kBxZtaYsyvjp7cmWOU1XsAdfQBwClraEofXDT197lFbXOEVMHpvQOg==", + "devOptional": true, + "license": "MIT" + }, "node_modules/gtoken": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-8.0.0.tgz", @@ -3970,6 +4710,16 @@ "node": ">= 0.4" } }, + "node_modules/hono": { + "version": "4.12.27", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.27.tgz", + "integrity": "sha512-1yrb/+w6HWQJrUCLkJ2IF5jNIPvvFkblV5RNOYl6bV+OA6p9GLcMpHFFGTosSvHvcAUibuUukRqhlYI4z32C7Q==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=16.9.0" + } + }, "node_modules/html-encoding-sniffer": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", @@ -4046,6 +4796,13 @@ "node": ">= 14" } }, + "node_modules/http-status-codes": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/http-status-codes/-/http-status-codes-2.3.0.tgz", + "integrity": "sha512-RJ8XvFvpPM/Dmc5SV+dC4y5PCeOhT3x1Hq0NU3rjGeg5a/CqlhZ7uudknPwZFz4aeAXDcbAyaeP7GAo9lvngtA==", + "devOptional": true, + "license": "MIT" + }, "node_modules/https-proxy-agent": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", @@ -4455,6 +5212,13 @@ "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", "license": "MIT" }, + "node_modules/is-property": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", + "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==", + "devOptional": true, + "license": "MIT" + }, "node_modules/is-regex": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", @@ -4628,6 +5392,16 @@ "@pkgjs/parseargs": "^0.11.0" } }, + "node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "devOptional": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, "node_modules/js-yaml": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.2.0.tgz", @@ -4911,12 +5685,35 @@ "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", "license": "MIT" }, + "node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "devOptional": true, + "license": "Apache-2.0" + }, "node_modules/lru-cache": { "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "license": "ISC" }, + "node_modules/lru.min": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.4.tgz", + "integrity": "sha512-DqC6n3QQ77zdFpCMASA1a3Jlb64Hv2N2DciFGkO/4L9+q/IpIAuRlKOvCXabtRW6cQf8usbmM6BE/TOPysCdIA==", + "devOptional": true, + "license": "MIT", + "engines": { + "bun": ">=1.0.0", + "deno": ">=1.30.0", + "node": ">=8.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wellwelwel" + } + }, "node_modules/make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", @@ -5074,6 +5871,57 @@ "node": ">= 0.6" } }, + "node_modules/mysql2": { + "version": "3.15.3", + "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.15.3.tgz", + "integrity": "sha512-FBrGau0IXmuqg4haEZRBfHNWB5mUARw6hNwPDXXGg0XzVJ50mr/9hb267lvpVMnhZ1FON3qNd4Xfcez1rbFwSg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "aws-ssl-profiles": "^1.1.1", + "denque": "^2.1.0", + "generate-function": "^2.3.1", + "iconv-lite": "^0.7.0", + "long": "^5.2.1", + "lru.min": "^1.0.0", + "named-placeholders": "^1.1.3", + "seq-queue": "^0.0.5", + "sqlstring": "^2.3.2" + }, + "engines": { + "node": ">= 8.0" + } + }, + "node_modules/mysql2/node_modules/iconv-lite": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", + "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/named-placeholders": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.6.tgz", + "integrity": "sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "lru.min": "^1.1.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/nanoid": { "version": "3.3.15", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.15.tgz", @@ -5356,6 +6204,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/ohash": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz", + "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==", + "devOptional": true, + "license": "MIT" + }, "node_modules/on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", @@ -5549,15 +6404,29 @@ "url": "https://opencollective.com/express" } }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/perfect-debounce": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-2.1.0.tgz", + "integrity": "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==", + "devOptional": true, + "license": "MIT" + }, "node_modules/pg": { - "version": "8.20.0", - "resolved": "https://registry.npmjs.org/pg/-/pg-8.20.0.tgz", - "integrity": "sha512-ldhMxz2r8fl/6QkXnBD3CR9/xg694oT6DZQ2s6c/RI28OjtSOpxnPrUCGOBJ46RCUxcWdx3p6kw/xnDHjKvaRA==", + "version": "8.22.0", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.22.0.tgz", + "integrity": "sha512-8wih1vVIBMxoUM2oB4soJsD9tDnDpLv4OXBJ+EJzFsvycD+lfyIreC2gGHq78f8jbLLt+bvlPTFdFZfJkOuzAA==", "license": "MIT", "dependencies": { - "pg-connection-string": "^2.12.0", - "pg-pool": "^3.13.0", - "pg-protocol": "^1.13.0", + "pg-connection-string": "^2.14.0", + "pg-pool": "^3.14.0", + "pg-protocol": "^1.15.0", "pg-types": "2.2.0", "pgpass": "1.0.5" }, @@ -5565,7 +6434,7 @@ "node": ">= 16.0.0" }, "optionalDependencies": { - "pg-cloudflare": "^1.3.0" + "pg-cloudflare": "^1.4.0" }, "peerDependencies": { "pg-native": ">=3.0.1" @@ -5577,16 +6446,16 @@ } }, "node_modules/pg-cloudflare": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.3.0.tgz", - "integrity": "sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.4.0.tgz", + "integrity": "sha512-Vo7z/6rrQYxpNRylp4Tlob2elzbh+N/MOQbxFVWCxS7oEx6jF53GTJFxK2WWpKuBRkmiin4Mt+xofFDjx09R0A==", "license": "MIT", "optional": true }, "node_modules/pg-connection-string": { - "version": "2.12.0", - "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.12.0.tgz", - "integrity": "sha512-U7qg+bpswf3Cs5xLzRqbXbQl85ng0mfSV/J0nnA31MCLgvEaAo7CIhmeyrmJpOr7o+zm0rXK+hNnT5l9RHkCkQ==", + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.14.0.tgz", + "integrity": "sha512-XwWDGcLRGCXAR8F/AM5bG7Q+A3Wm2s6QeEjlOKZLlH3UYcguiqCWKyWXVag5TLTIjR7oOJUY8kcADaZgWPyLeg==", "license": "MIT" }, "node_modules/pg-cursor": { @@ -5618,9 +6487,9 @@ } }, "node_modules/pg-pool": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.13.0.tgz", - "integrity": "sha512-gB+R+Xud1gLFuRD/QgOIgGOBE2KCQPaPwkzBBGC9oG69pHTkhQeIuejVIk3/cnDyX39av2AxomQiyPT13WKHQA==", + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.14.0.tgz", + "integrity": "sha512-gKtPkFdQPU3DksooVLi9LsjZxrsBUZIpa+7aVx+LV5pNh0KzP4Zleud2po+ConrxbuXGBJ6Hfer6hdgpIBpBaw==", "license": "MIT", "peerDependencies": { "pg": ">=8.0" @@ -5644,10 +6513,37 @@ "pg-query-stream": "4.14.0" } }, + "node_modules/pg-promise/node_modules/pg": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.20.0.tgz", + "integrity": "sha512-ldhMxz2r8fl/6QkXnBD3CR9/xg694oT6DZQ2s6c/RI28OjtSOpxnPrUCGOBJ46RCUxcWdx3p6kw/xnDHjKvaRA==", + "license": "MIT", + "dependencies": { + "pg-connection-string": "^2.12.0", + "pg-pool": "^3.13.0", + "pg-protocol": "^1.13.0", + "pg-types": "2.2.0", + "pgpass": "1.0.5" + }, + "engines": { + "node": ">= 16.0.0" + }, + "optionalDependencies": { + "pg-cloudflare": "^1.3.0" + }, + "peerDependencies": { + "pg-native": ">=3.0.1" + }, + "peerDependenciesMeta": { + "pg-native": { + "optional": true + } + } + }, "node_modules/pg-protocol": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.13.0.tgz", - "integrity": "sha512-zzdvXfS6v89r6v7OcFCHfHlyG/wvry1ALxZo4LqgUoy7W9xhBDMaqOuMiF3qEV45VqsN6rdlcehHrfDtlCPc8w==", + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.15.0.tgz", + "integrity": "sha512-cq9sECI5s0+uPUXjbz8ioyPJni6RzsRib0US67i5IoTZKw8fNeYlVE7u8F4dG7vEJJtc5wdD1K189lCCUwqWTQ==", "license": "MIT" }, "node_modules/pg-query-stream": { @@ -5707,6 +6603,18 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pkg-types": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.1.tgz", + "integrity": "sha512-y+ichcgc2LrADuhLNAx8DFjVfgz91pRxfZdI3UDhxHvcVEZsenLO+7XaU5vOp0u/7V/wZ+plyuQxtrDlZJ+yeg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.2.4", + "exsolve": "^1.0.8", + "pathe": "^2.0.3" + } + }, "node_modules/possible-typed-array-names": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", @@ -5807,6 +6715,59 @@ "node": ">= 0.8.0" } }, + "node_modules/prisma": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-7.8.0.tgz", + "integrity": "sha512-yfN4yrw7HV9kEJhoy1+jgah0jafEIQsf7uWouSsM8MvJtlubsk+kM7AIBWZ8+GJl74Yj3c+nbYqBkMOxtsZ3Lw==", + "devOptional": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/config": "7.8.0", + "@prisma/dev": "0.24.3", + "@prisma/engines": "7.8.0", + "@prisma/studio-core": "0.27.3", + "mysql2": "3.15.3", + "postgres": "3.4.7" + }, + "bin": { + "prisma": "build/index.js" + }, + "engines": { + "node": "^20.19 || ^22.12 || >=24.0" + }, + "peerDependencies": { + "better-sqlite3": ">=9.0.0", + "typescript": ">=5.4.0" + }, + "peerDependenciesMeta": { + "better-sqlite3": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/proper-lockfile": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", + "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "retry": "^0.12.0", + "signal-exit": "^3.0.2" + } + }, + "node_modules/proper-lockfile/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "devOptional": true, + "license": "ISC" + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -5845,6 +6806,23 @@ "node": ">=6" } }, + "node_modules/pure-rand": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "devOptional": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" + }, "node_modules/qs": { "version": "6.15.2", "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz", @@ -5924,6 +6902,42 @@ "url": "https://opencollective.com/express" } }, + "node_modules/rc9": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/rc9/-/rc9-3.0.1.tgz", + "integrity": "sha512-gMDyleLWVE+i6Sgtc0QbbY6pEKqYs97NGi6isHQPqYlLemPoO8dxQ3uGi0f4NiP98c+jMW6cG1Kx9dDwfvqARQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "defu": "^6.1.6", + "destr": "^2.0.5" + } + }, + "node_modules/react": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz", + "integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==", + "devOptional": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.7.tgz", + "integrity": "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==", + "devOptional": true, + "license": "MIT", + "peer": true, + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.7" + } + }, "node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", @@ -5995,6 +7009,26 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/remeda": { + "version": "2.33.4", + "resolved": "https://registry.npmjs.org/remeda/-/remeda-2.33.4.tgz", + "integrity": "sha512-ygHswjlc/opg2VrtiYvUOPLjxjtdKvjGz1/plDhkG66hjNjFr1xmfrs2ClNFo/E6TyUFiwYNh53bKV26oBoMGQ==", + "devOptional": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/remeda" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/resolve": { "version": "2.0.0-next.6", "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.6.tgz", @@ -6038,6 +7072,16 @@ "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" } }, + "node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, "node_modules/rimraf": { "version": "5.0.10", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz", @@ -6182,6 +7226,14 @@ "node": ">=v12.22.7" } }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "devOptional": true, + "license": "MIT", + "peer": true + }, "node_modules/semver": { "version": "7.7.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", @@ -6216,6 +7268,12 @@ "node": ">= 18" } }, + "node_modules/seq-queue": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz", + "integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==", + "devOptional": true + }, "node_modules/serve-static": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", @@ -6450,6 +7508,16 @@ "node": ">= 10.x" } }, + "node_modules/sqlstring": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz", + "integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/statuses": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", @@ -6459,6 +7527,13 @@ "node": ">= 0.8" } }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "devOptional": true, + "license": "MIT" + }, "node_modules/stop-iteration-iterator": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", @@ -7517,7 +8592,7 @@ "version": "5.8.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -7646,6 +8721,21 @@ "dev": true, "license": "MIT" }, + "node_modules/valibot": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/valibot/-/valibot-1.2.0.tgz", + "integrity": "sha512-mm1rxUsmOxzrwnX5arGS+U4T25RdvpPjPN4yR0u9pUBov9+zGVtO84tif1eY4r6zWxVxu3KzIyknJy3rxfRZZg==", + "devOptional": true, + "license": "MIT", + "peerDependencies": { + "typescript": ">=5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -8000,6 +9090,17 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zeptomatch": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/zeptomatch/-/zeptomatch-2.1.0.tgz", + "integrity": "sha512-KiGErG2J0G82LSpniV0CtIzjlJ10E04j02VOudJsPyPwNZgGnRKQy7I1R7GMyg/QswnE4l7ohSGrQbQbjXPPDA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "grammex": "^3.1.11", + "graphmatch": "^1.1.0" + } } } } diff --git a/backend/package.json b/backend/package.json index f03b302..341ebf7 100644 --- a/backend/package.json +++ b/backend/package.json @@ -4,13 +4,19 @@ "main": "index.js", "scripts": { "dev": "nodemon --watch src --ext ts --exec ts-node ./server.ts", - "migrate": "npx drizzle-kit generate && npx drizzle-kit migrate", + "prisma:generate": "prisma generate", + "prisma:push": "prisma db push", + "prisma:seed": "prisma db seed", + "migrate": "npm run prisma:generate && npm run prisma:push && npm run prisma:seed", "start": "nodemon --watch src --ext && ts-node server.ts --verbose", "build": "ts-node server.ts --verbose", "build-test": "tsc --noemit", "lint": "eslint .", "lint:fix": "eslint . --fix" }, + "prisma": { + "seed": "tsx prisma/seed.ts" + }, "browser": { "fs": false, "path": false, @@ -22,6 +28,8 @@ "description": "", "dependencies": { "@electric-sql/pglite": "^0.2.17", + "@prisma/adapter-pg": "^7.8.0", + "@prisma/client": "^7.8.0", "@types/multer": "^1.4.12", "axios": "^1.8.4", "bcryptjs": "^3.0.2", @@ -43,7 +51,7 @@ "multer": "^2.1.1", "nodemailer": "^9.0.1", "papaparse": "^5.5.2", - "pg": "^8.14.1", + "pg": "^8.22.0", "pg-promise": "^12.6.2", "postgres": "^3.4.5", "randombytes": "^2.1.0", @@ -56,12 +64,13 @@ "@types/express": "^5.0.1", "@types/node": "^22.14.1", "@types/papaparse": "^5.3.15", - "@types/pg": "^8.11.12", + "@types/pg": "^8.20.0", "eslint": "^9.39.4", "eslint-plugin-import": "^2.32.0", "eslint-plugin-unused-imports": "^4.4.1", "globals": "^15.15.0", "nodemon": "^3.1.9", + "prisma": "^7.8.0", "ts-node": "^10.9.2", "tsx": "^4.19.3", "typescript": "^5.8.3", diff --git a/backend/prisma.config.ts b/backend/prisma.config.ts new file mode 100644 index 0000000..095d3fb --- /dev/null +++ b/backend/prisma.config.ts @@ -0,0 +1,16 @@ +import { defineConfig } from "prisma/config"; +import { join } from 'node:path'; +import * as dotenv from "dotenv"; + +dotenv.config(); + +export default defineConfig({ + schema: join('prisma'), + migrations: { + path: join('prisma', 'migrations'), + seed: "tsx prisma/seed.ts", + }, + datasource: { + url: process.env.OUTSIDE_DATABASE_URL ?? "", + }, +}); diff --git a/backend/prisma/models/challenges.prisma b/backend/prisma/models/challenges.prisma new file mode 100644 index 0000000..a95b07e --- /dev/null +++ b/backend/prisma/models/challenges.prisma @@ -0,0 +1,28 @@ +model challenges { + id Int @id @default(autoincrement()) + title String + description String + category String + points Int + created_by Int? // l'admin qui a créé le challenge + challenge_validation challenge_validation[] + users users? @relation(fields: [created_by], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "challenges_created_by_users_id_fk") +} + +model challenge_validation { + id Int @id @default(autoincrement()) + challenge_id Int + validated_by_admin_id Int + validated_at DateTime @default(now()) @db.Timestamptz(6) + target_user_id Int? + target_team_id Int? + target_faction_id Int? + points Int // Nombre de points validés + added_by_admin_id Int // Qui a validé les points + users_challenge_validation_added_by_admin_idTousers users @relation("challenge_validation_added_by_admin_idTousers", fields: [added_by_admin_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "challenge_validation_added_by_admin_id_users_id_fk") + challenges challenges @relation(fields: [challenge_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "challenge_validation_challenge_id_challenges_id_fk") + factions factions? @relation(fields: [target_faction_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "challenge_validation_target_faction_id_factions_id_fk") + teams teams? @relation(fields: [target_team_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "challenge_validation_target_team_id_teams_id_fk") + users_challenge_validation_target_user_idTousers users? @relation("challenge_validation_target_user_idTousers", fields: [target_user_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "challenge_validation_target_user_id_users_id_fk") + users_challenge_validation_validated_by_admin_idTousers users @relation("challenge_validation_validated_by_admin_idTousers", fields: [validated_by_admin_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "challenge_validation_validated_by_admin_id_users_id_fk") +} \ No newline at end of file diff --git a/backend/prisma/models/event.prisma b/backend/prisma/models/event.prisma new file mode 100644 index 0000000..5aa78f3 --- /dev/null +++ b/backend/prisma/models/event.prisma @@ -0,0 +1,9 @@ +model events { + id Int @id @default(autoincrement()) + pre_registration_open Boolean? @default(false) + shotgun_open Boolean? @default(false) + sdi_open Boolean? @default(false) + wei_open Boolean? @default(false) + food_open Boolean? @default(false) + chall_open Boolean? @default(false) +} \ No newline at end of file diff --git a/backend/prisma/models/faction.prisma b/backend/prisma/models/faction.prisma new file mode 100644 index 0000000..97d1c33 --- /dev/null +++ b/backend/prisma/models/faction.prisma @@ -0,0 +1,7 @@ +model factions { + id Int @id @default(autoincrement()) + name String? @unique(map: "factions_name_unique") + description String? + challenge_validation challenge_validation[] + team_faction team_faction[] +} \ No newline at end of file diff --git a/backend/prisma/models/news.prisma b/backend/prisma/models/news.prisma new file mode 100644 index 0000000..28e9b2c --- /dev/null +++ b/backend/prisma/models/news.prisma @@ -0,0 +1,10 @@ +model news { + id Int @id @default(autoincrement()) + title String? + description String? + type String? + published Boolean? @default(false) + target String? // "Tous", "Nouveau", "CE", "Orgas", "Admin", ... + created_at DateTime? @default(now()) @db.Timestamp(6) + image_url String? +} \ No newline at end of file diff --git a/backend/prisma/models/permanence.prisma b/backend/prisma/models/permanence.prisma new file mode 100644 index 0000000..430b341 --- /dev/null +++ b/backend/prisma/models/permanence.prisma @@ -0,0 +1,33 @@ +model permanences { + id Int @id @default(autoincrement()) + name String? + description String? + location String? + start_at DateTime? @db.Timestamp(6) + end_at DateTime? @db.Timestamp(6) + capacity Int? + is_open Boolean? @default(false) + difficulty Int? + respo_permanences respo_permanences[] + user_permanences user_permanences[] +} + +model respo_permanences { + user_id Int + permanence_id Int + permanences permanences @relation(fields: [permanence_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "respo_permanences_permanence_id_permanences_id_fk") + users users @relation(fields: [user_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "respo_permanences_user_id_users_id_fk") + + @@id([user_id, permanence_id], map: "respo_permanences_user_id_permanence_id_pk") +} + +model user_permanences { + user_id Int + permanence_id Int + registered_at DateTime? @default(now()) @db.Timestamp(6) + claimed Boolean? @default(false) + permanences permanences @relation(fields: [permanence_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "user_permanences_permanence_id_permanences_id_fk") + users users @relation(fields: [user_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "user_permanences_user_id_users_id_fk") + + @@id([user_id, permanence_id], map: "user_permanences_user_id_permanence_id_pk") +} \ No newline at end of file diff --git a/backend/prisma/models/role.prisma b/backend/prisma/models/role.prisma new file mode 100644 index 0000000..3abbaa6 --- /dev/null +++ b/backend/prisma/models/role.prisma @@ -0,0 +1,14 @@ +model roles { + id Int @id @default(autoincrement()) + name String? @unique(map: "roles_name_unique") // Nom du rôle (ex: "Animation", "Bouffe", ...) + description String? + role_points role_points? + user_preferences user_preferences[] + user_roles user_roles[] +} + +model role_points { + role_points Int @id(map: "role_points_role_points_pk") + points Int? + roles roles @relation(fields: [role_points], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "role_points_role_points_roles_id_fk") +} \ No newline at end of file diff --git a/backend/prisma/models/team.prisma b/backend/prisma/models/team.prisma new file mode 100644 index 0000000..ec8ccc2 --- /dev/null +++ b/backend/prisma/models/team.prisma @@ -0,0 +1,26 @@ +model teams { + id Int @id @default(autoincrement()) + name String? @unique(map: "teams_name_unique") + description String? + type String? + challenge_validation challenge_validation[] + team_faction team_faction[] + team_shotgun team_shotgun[] + user_teams user_teams[] +} + +model team_faction { + faction_id Int + team_id Int + factions factions @relation(fields: [faction_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "team_faction_faction_id_factions_id_fk") + teams teams @relation(fields: [team_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "team_faction_team_id_teams_id_fk") + + @@id([faction_id, team_id], map: "team_faction_faction_id_team_id_pk") +} + +model team_shotgun { + id Int @id @default(autoincrement()) + team_id Int? + timestamp DateTime? @default(now()) @db.Timestamp(6) // Timestamp du shotgun + teams teams? @relation(fields: [team_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "team_shotgun_team_id_teams_id_fk") +} \ No newline at end of file diff --git a/backend/prisma/models/user.prisma b/backend/prisma/models/user.prisma new file mode 100644 index 0000000..92274b7 --- /dev/null +++ b/backend/prisma/models/user.prisma @@ -0,0 +1,80 @@ +model users { + id Int @id @default(autoincrement()) + first_name String? + last_name String? + email String? @unique(map: "users_email_unique") + majeur Boolean? + branch String? + contact String? + password String? + permission String? @default("Nouveau") // Par défaut, le rôle sera "Nouveau" + created_at DateTime? @default(now()) @db.Timestamp(6) + discord_id String? + bus_attribution bus_attribution? + challenge_validation_challenge_validation_added_by_admin_idTousers challenge_validation[] @relation("challenge_validation_added_by_admin_idTousers") + challenge_validation_challenge_validation_target_user_idTousers challenge_validation[] @relation("challenge_validation_target_user_idTousers") + challenge_validation_challenge_validation_validated_by_admin_idTousers challenge_validation[] @relation("challenge_validation_validated_by_admin_idTousers") + challenges challenges[] + registration_tokens registration_tokens[] + respo_permanences respo_permanences[] + user_permanences user_permanences[] + user_preferences user_preferences[] + user_roles user_roles[] + user_teams user_teams[] + user_tent_user_tent_user_id_1Tousers user_tent[] @relation("user_tent_user_id_1Tousers") + user_tent_user_tent_user_id_2Tousers user_tent[] @relation("user_tent_user_id_2Tousers") +} + +model user_preferences { + user_id Int + role_id Int + roles roles @relation(fields: [role_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "user_preferences_role_id_roles_id_fk") + users users @relation(fields: [user_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "user_preferences_user_id_users_id_fk") + + @@id([user_id, role_id], map: "user_preferences_user_id_role_id_pk") +} + +model registration_tokens { + id Int @id @default(autoincrement()) + user_id Int + token String @unique(map: "registration_tokens_token_unique") + expires_at DateTime @db.Timestamp(6) + created_at DateTime? @default(now()) @db.Timestamp(6) + users users @relation(fields: [user_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "registration_tokens_user_id_users_id_fk") +} + +model user_roles { + user_id Int + role_id Int + roles roles @relation(fields: [role_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "user_roles_role_id_roles_id_fk") + users users @relation(fields: [user_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "user_roles_user_id_users_id_fk") + + @@id([user_id, role_id], map: "user_roles_user_id_role_id_pk") +} + +model user_teams { + user_id Int + team_id Int + teams teams @relation(fields: [team_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "user_teams_team_id_teams_id_fk") + users users @relation(fields: [user_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "user_teams_user_id_users_id_fk") + + @@id([user_id, team_id], map: "user_teams_user_id_team_id_pk") +} + +model bus_attribution { + user_id Int @id + bus Int + departure_time String + users users @relation(fields: [user_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "bus_attribution_user_id_users_id_fk") +} + +model user_tent { + user_id_1 Int + user_id_2 Int + confirmed Boolean? @default(false) // optionnel : pour savoir si les deux ont validé + created_at DateTime? @default(now()) @db.Timestamp(6) + users_user_tent_user_id_1Tousers users @relation("user_tent_user_id_1Tousers", fields: [user_id_1], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "user_tent_user_id_1_users_id_fk") + users_user_tent_user_id_2Tousers users @relation("user_tent_user_id_2Tousers", fields: [user_id_2], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "user_tent_user_id_2_users_id_fk") + + @@id([user_id_1, user_id_2], map: "user_tent_user_id_1_user_id_2_pk") +} \ No newline at end of file diff --git a/backend/prisma/schema.prisma b/backend/prisma/schema.prisma new file mode 100644 index 0000000..e1008ff --- /dev/null +++ b/backend/prisma/schema.prisma @@ -0,0 +1,8 @@ +generator client { + provider = "prisma-client" + output = "../src/prisma/build" +} + +datasource db { + provider = "postgresql" +} diff --git a/backend/prisma/seed.ts b/backend/prisma/seed.ts new file mode 100644 index 0000000..61364ee --- /dev/null +++ b/backend/prisma/seed.ts @@ -0,0 +1,137 @@ +import bcrypt from "bcryptjs"; +import { PrismaPg } from "@prisma/adapter-pg"; +import { PrismaClient } from "../src/prisma/build/client"; +import { zimbra_password } from "../src/utils/secret"; + +const connectionString = process.env.DATABASE_URL; + +if (!connectionString) { + throw new Error("DATABASE_URL is required to run Prisma seeds."); +} + +const adapter = new PrismaPg({ connectionString }); +const prisma = new PrismaClient({ adapter }); + +const roles = [ + { name: "Animation", description: "Animer, divertir et motiver les CE et les nouveaux étudiants tout au long de la pré-inté et de l'inté." }, + { name: "Arbitre", description: "Arbitrer les différents défis pendant le semaine d'intégration." }, + { name: "Argentique", description: "Couvrir les événements de l'intégration, prendre des photos" }, + { name: "Bouffe", description: "Prévoir, organiser et coordonner tous les repas de l'inté. La bouffe c'est sacré !" }, + { name: "Bar", description: "Prévoir, organiser et coordonner toutes les boissons de l'inté !" }, + { name: "Bénévole", description: "Deviens bénévole et participe à différentes activités de l'inté !" }, + { name: "Cahier de vacances", description: "Élaborer le futur cahier de vacances des nouveaux avec des petits exercices et blagues." }, + { name: "Chasse au trésor", description: "Elaborer une chasse au trésor dans toute la capitale Troyenne." }, + { name: "Communication", description: "Préparer et gérer toute la communication de l'intégration" }, + { name: "Graphisme", description: "Créer une charte graphique incroyable pour les rendre l'inté encore plus belle !" }, + { name: "Déco", description: "Être créatif et fabriquer de quoi décorer l'UTT au thème de l'inté." }, + { name: "Défis TC", description: "Préparer un défi où les nouveaux TC doivent faire preuve d'ingéniosité pour fabriquer quelque chose." }, + { name: "Dev / Info", description: "Maintenir le site et l'application de l'inté et développer de nouveaux outils informatiques." }, + { name: "Faux amphi", description: "Créer un faux premier cours compliqué pour les TC avec des professeurs et des faux élèves." }, + { name: "Faux discours de rentrée", description: "Préparer et faire un discours de rentrée pour faire une petite frayeur aux nouveaux." }, + { name: "Logistique", description: "Préparer, organiser et mettre en place tout le matériel nécessaire pour l'intégration." }, + { name: "Lac d'Orient Express", description: "Emmener nos nouveaux voir le magnifique lac de Mesnil" }, + { name: "Média", description: "Couvrir les événements de l'intégration, prendre des photos et monter des films souvenirs." }, + { name: "Mascotte", description: "Etre juste le GOAT de l'intégration !" }, + { name: "Parrainage", description: "Attribuer des parrains/marraines aux nouveaux étudiants de manière personnalisée." }, + { name: "Partenariat", description: "Rechercher et établir des partenariats utiles pour l'intégration et les nouveaux étudiants." }, + { name: "Prévention", description: "Évaluer les risques et mettre en place des mesures préventives avant et pendant l'intégration." }, + { name: "Rallye", description: "Organiser une après-midi de jeux et d'activités sportives pour les nouveaux étudiants." }, + { name: "Respo CE", description: "Gérer le planning des CE et les guider pour qu'ils accueillent au mieux les nouveaux." }, + { name: "Respo Question", description: "Répondre au mieux aux questions des nouveaux !" }, + { name: "Sécu", description: "Gérer la sécurité des événements, notamment durant le WEI et la soirée d'intégration." }, + { name: "Soirée d'intégration", description: "Organiser une soirée sur le campus UTT durant la semaine d'inté." }, + { name: "Son et lumière", description: "Installer et gérer le son et la lumière durant les événements nécessitant une ambiance spéciale." }, + { name: "Soutenabilité", description: "Mettre en place des actions pour réduire l'impact environnemental de l'intégration." }, + { name: "SVE", description: "Mettre en place le Salon de la Vie Etudiante (SVE) lors de la deuxième semaine" }, + { name: "Traduction en anglais", description: "Traduire les contenus de l'intégration pour les étudiants étrangers." }, + { name: "Village Asso", description: "Organiser un événement pour présenter les associations UTTiennes aux nouveaux étudiants." }, + { name: "Visites", description: "Organiser les visites de l'UTT et de la ville pour les nouveaux étudiants." }, + { name: "WEI", description: "Organiser le Week-end d'intégration (transport, animation, logistique, soirée, etc.)." } +]; + +async function main() { + const hashedPassword = await bcrypt.hash(zimbra_password, 10); + + await prisma.$transaction(async (tx) => { + + // initRoles + for (const role of roles) { + const seededRole = await tx.roles.upsert({ + where: { name: role.name }, + update: { description: role.description }, + create: role, + }); + + await tx.role_points.upsert({ + where: { role_points: seededRole.id }, + update: { points: 0 }, + create: { role_points: seededRole.id, points: 0 }, + }); + } + + + // initUser + const adminUser = await tx.users.upsert({ + where: { email: "integration@utt.fr" }, + update: { + first_name: "Integration UTT", + last_name: "Integration UTT", + majeur: true, + password: hashedPassword, + permission: "Admin", + }, + create: { + first_name: "Integration UTT", + last_name: "Integration UTT", + email: "integration@utt.fr", + majeur: true, + password: hashedPassword, + permission: "Admin", + }, + }); + + + // initEvent + const existingEvent = await tx.events.findFirst({ select: { id: true } }); + + if (!existingEvent) { + await tx.events.create({ + data: { + pre_registration_open: false, + shotgun_open: false, + sdi_open: false, + wei_open: false, + food_open: false, + chall_open: false, + }, + }); + } + + + // initChallenge + const existingChallenge = await tx.challenges.findFirst({ select: { id: true } }); + + if (!existingChallenge) { + await tx.challenges.create({ + data: { + title: "Free", + description: "Chalenge de réfrence pour les points gratuits", + category: "Free", + points: 0, + created_by: adminUser.id, + }, + }); + } + }); + + console.log("Prisma seed completed."); +} + +main() + .catch((error) => { + console.error("Prisma seed failed:", error); + process.exitCode = 1; + }) + .finally(async () => { + await prisma.$disconnect(); + }); \ No newline at end of file diff --git a/backend/src/prisma/db.ts b/backend/src/prisma/db.ts new file mode 100644 index 0000000..84e6aac --- /dev/null +++ b/backend/src/prisma/db.ts @@ -0,0 +1,20 @@ +import { PrismaClient } from "./build/client"; +import { PrismaPg } from "@prisma/adapter-pg"; +import { postgres_db, postgres_host, postgres_password, postgres_port, postgres_user } from "../utils/secret"; + +const connectionString = `postgresql://${postgres_user}:${postgres_password}@${postgres_host}:${postgres_port}/${postgres_db}`; + +// Singleton pattern to counter hot-reloading issues in development +const globalForPrisma = global as unknown as { prisma?: PrismaClient }; + +export const db = + globalForPrisma.prisma ?? + new PrismaClient({ + adapter: new PrismaPg({ + connectionString, + }) + }); + +if (process.env.NODE_ENV !== "production") { + globalForPrisma.prisma = db; +} \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 764d957..c3426fd 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -41,7 +41,7 @@ services: "sh", "-c", "until pg_isready -h db -p 5432; do echo 'Waiting for DB...'; sleep 2; - done && npx drizzle-kit migrate && npm run dev", + done && npm run migrate && npm run dev", ] frontend: diff --git a/integration.utt.fr-key.pem b/integration.utt.fr-key.pem index 9715f79..a118b2f 100644 --- a/integration.utt.fr-key.pem +++ b/integration.utt.fr-key.pem @@ -1,28 +1,28 @@ -----BEGIN PRIVATE KEY----- -MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCtif9VpeK8oNba -IfHlIyElLPsCYDLdfcYMNQUyGIyZGNa053rJuf7O5zyClVZb2WzLUXXOGROM4pZi -qE5D5CN0hbGLW2oPB4tuLhdl5CTse1iOqT0QYT4Uh+0760gug97lvDGaZlWVclEc -YpXsaz0qEFOORkGzm0E/7Q83dTkBEjI4yT0RpgM444DOGhaqpCM0hItUPaaIgHj9 -jwi8Z4leOQWW5edmPL2WoJmCs8z89iKn156e8V3Rudvax5kB8JZNrfKy0t+Dxq8D -fR/aZwFh2Zwm5VMbvsyiPD9EAJe85dVcmgDJ2msqzA2KXrCpuybXaBQNDvL1F7JQ -sPnEFj5FAgMBAAECggEAdZRugCVpPszrRdiCAPKQXpPfbninfhLdwR+baZngiUqf -DutX8p4m2yEpioCMhqcGY6vJ2d57tJVBt465mJ5Wt3huFzHU5mICQqDQEaVGr0PT -eLWKPjLk0RxXzKVZvspyl82u/iIgEqXl9wmE8y6lvn5ZXqiTk5G54ApKqRSvjt0b -lpk5qDPcgDeEKHmy/5GY6Xw5fbnohyeU2pROd/5iANRxxhh3zZehV+BE8IvX6m+Y -rKgQnj+1c3gL9idOUXI4ouHGLQXpUmF7FzHHZxDXjZpIwgnCV5wQHY15Mjraucrl -10gs7f0BUgh3bPGVMJOLoZP2yy7swPZJmMPwZKhDAQKBgQDM/KhXlyZ5GfWEbWt5 -4z1E3JaAlkqrrnwh6pvJASU8usSSXHp9kdPg9/UvWIgXTox1LS0gIiRg7g3v3nmv -UXCM65vpkvZ6dSoLmxG7y1Y4A9bFPBC95BaMiRRCa5XwIuVmlIMmZ6ruqLS2eTnL -gGHUSmDMpsRNriQeWpjmi+9gVQKBgQDYudowoENqXYpEms7gtZJ/RLDAc4joz9LI -MuIKFwOEDv9JCPbCjngKxIbEHvVl/hF5eaWk2P8lsGAT2GcR1ZQ/xxBMytWlvdAl -5/b8sdQfAGtE5K6BysumjIVqObAEQcLQ3ta+B3PR7n2Vh/LRRT+bt4g72dccRcIB -UmvfPIeWMQKBgChDpKl31iXJdJFrkMXjXeCN174wR0CLyHQ9Chakc/UG1p/NLH5H -y6+P5QhEwo6ZbjuCATAjpLOpbvFj6NEIFSyJBxoNNP7+zqBy+DvECA5+qowZbUxv -ZgJ61pDpYw1FPXw1xcEgcdHpL338N98CO7UgWv038K01fIC92PTIHd/1AoGBALXZ -DBktACQppLD37IpkEC41ptF0n/YpG0XcXAn3UX3nT5EqslKBVHxEdoftKh+QVX1F -8xUk9sHbAmLke2ddfG0fTLACqc3OPO7xei6Bz+jLYzaFY1+Il+SBmBiPmv+XZi34 -LNt6SVZm9H0Ze0bZAgxYrTj7CiGw7p5JWDYSBfqhAoGAXInJFpLndIQPRylTya7w -fJRZQ6vuLCN/BHdCu7DNPMt7kGGym4pJ+88DXrUkBmvoJ/ivzpuHY8MJXhp7cE5L -tsU43N6mfqBgeYP/utdMNKw6istk0d+4xSLiyF8fuBVIinDHny1oxV9Ww5TScTPe -Dg+Kdon5jypMQzPFEpB9xdY= +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCnESPrJQ4+EN6e +x4J4Rf+q42tIxcmkgFaj0b+qAoSed5cfJSB+wPOtjBNbmcCzLTnI3SU3yOxwmkgx +fkCQIB1yAY96HUUKoxI6WGU38DdEQily0vFc2mOHYIpGCwaht7HOwaNFSpE9BV65 +/tmNKJjE62aCpVxMQvYjHV0NQwT0VYyFP2N8bgD6SOhTFpf6o4ggcmhUd/OPPInV +/27peKml2DkwgOAEs+OYcKsTHEO3/2SxNhjaRQpeJ1LVrqI+8OFRmi14mEvltl1I +t39TEnmJm78vyhgr3PkW1dSWGc+OIeqC+H3zR+CGYCUtcZBQFKJ6SksmEO232xhH +BnyG5diTAgMBAAECggEAahugzIBEg3cDbaUZYHpU35yBTz8PysMYRAO7z6a5xEwA +V2+lK9ww6FDHGq6UjTh0MvcB5GRgh0P2BjlBmEhNa1NCZTSh3RelRBlQcC7Jazj6 +SktfsLGj9R+Dsujd1WhuLfseT6wEgtUeTNNoKRdXr/oyqVeNP4JoAfVsXDfstaF4 +3gT4N+WQBVXB1D+fYK8lc7JPopb6ipMI8BVf+a5QBBec5llz+Ty+SCU9OwkS3lmL +qr1yuSnN2gLL1SkoZq8f2Hx0QrEj0wbn59AmfXacVG+wwRg4JuTDLR8aABorrV6p +iydOayZRbjltmzw8hsJYmcKzmhS/QZSedOvdHCZ0AQKBgQDcPVjm6J/PJix/x9xH +ZvSDbHxC+yKCZHXeTT634RYu5T94FeXwpX2dmQaPwAw0epAOvNB28XkZFLgIfk4n +LAiUW94xKPOJtoGCVZbzw0q2Hrthxs2jgjzqSMI15vvV8Ba5vciw6SZNeU68Cvrp +lvZIvdhGsPD0Vtdy71aNiRJ9wQKBgQDCMZNlB1imkWf9BjmN7i0Jgd/7/SMTkxma +iknXdoKZJQVjFzhq5w4tK23QJDuEo/IN2MmgluVi3ydSEPydawgkw5IVzZ4gFgwZ +AHXr9rrDTOMvy28jiIfvmV3rvWjJ5NTgtPHYcH3/XePTYhIh27b68nMEhslSDUwS +UPs/+oHTUwKBgDCp+YfkJNOv2qtNbRX14NddVEO0nBZ1vjmlldMpkC5Ch3iwVgOL +fasiEfoXdFrwLf6jp+hwSNLK6jMuPXSYXTjy57CzHMT0J7+1y8oG/+4n8j2ToQKd +F3qpIwioBkNqbosSLVMx4T3DLSuNvY15xueh0zUjicpjme2Shux1OjnBAoGAGXiP +MK0p/48K6LEsU2VuGtUiH9cwBp1Aunhw/wXYU2X7sueUytDLlzkfCjH2bItkcFSC +/cFNDQ7gBW+c8rjbNJdCTgHOP78kjxL0ZKLUFFG9asNwLGT4D4E6RHVvjSuqkF6r +agw3OouCGnO+lRpQgdHTLEDRnyWHWM+uZNHivWkCgYA9Pyn2UfSBz6+CCgi+QI3s +fT2sPzu3lmkZxiG5OpsraOyuFLWygsdORhWluofOBu9CRJSg3HcyZWiEckC1GpIR +9sQuGrO9UZMwXGy/QGx6BbKGqylURBHGjK6Sy1QAsjt8BoXa7hC8vvsFHVE6QTm7 +6WfnV5notvzy7mnMk6+6Eg== -----END PRIVATE KEY----- diff --git a/integration.utt.fr.pem b/integration.utt.fr.pem index 53c55da..3b18f7f 100644 --- a/integration.utt.fr.pem +++ b/integration.utt.fr.pem @@ -1,24 +1,27 @@ -----BEGIN CERTIFICATE----- -MIIEFjCCAn6gAwIBAgIRALShiUHfdwNDV0zIXo5dFUAwDQYJKoZIhvcNAQELBQAw -XzEeMBwGA1UEChMVbWtjZXJ0IGRldmVsb3BtZW50IENBMRowGAYDVQQLDBFkb2Rp -bkBBcnRodXJEb2RpbjEhMB8GA1UEAwwYbWtjZXJ0IGRvZGluQEFydGh1ckRvZGlu -MB4XDTI2MDQyOTEzMzkzNloXDTI4MDcyOTEzMzkzNlowRTEnMCUGA1UEChMebWtj -ZXJ0IGRldmVsb3BtZW50IGNlcnRpZmljYXRlMRowGAYDVQQLDBFkb2RpbkBBcnRo -dXJEb2RpbjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK2J/1Wl4ryg -1toh8eUjISUs+wJgMt19xgw1BTIYjJkY1rTnesm5/s7nPIKVVlvZbMtRdc4ZE4zi -lmKoTkPkI3SFsYtbag8Hi24uF2XkJOx7WI6pPRBhPhSH7TvrSC6D3uW8MZpmVZVy -URxilexrPSoQU45GQbObQT/tDzd1OQESMjjJPRGmAzjjgM4aFqqkIzSEi1Q9poiA -eP2PCLxniV45BZbl52Y8vZagmYKzzPz2IqfXnp7xXdG529rHmQHwlk2t8rLS34PG -rwN9H9pnAWHZnCblUxu+zKI8P0QAl7zl1VyaAMnaayrMDYpesKm7JtdoFA0O8vUX -slCw+cQWPkUCAwEAAaNnMGUwDgYDVR0PAQH/BAQDAgWgMBMGA1UdJQQMMAoGCCsG -AQUFBwMBMB8GA1UdIwQYMBaAFCiT+Oto6lg8pQlhwjiu0sL8L0xgMB0GA1UdEQQW -MBSCEmludGVncmF0aW9uLnV0dC5mcjANBgkqhkiG9w0BAQsFAAOCAYEAlip+Dy7G -UuHwqHE3MkkJZbhmc7Vv1zTa4Lr/HULKGF16egdqU3BDAYD8KrWjQwJgOd/gSATT -Z0jSRv7AZqKVcl99hTcvDyEWFoHZDPyyjbHr0pwfr3WBtEu5FOaE/ntTqVAX93tQ -Qy/pWFhJ9Grj6SC1dKc7DV8hgzYdxLt+QwM0t01niPdOsrC63DDcJ2Avxdw/SrCR -pjFDy2ilP1htmX96KnzO5LDGtl6T4mKEogOIz8Q5xenxWVosgrxVq6xa3tNmcBDh -if3AtNdWPNDtJdU4+LIVtlHps0t6MEqj+oqPSPpaMTS1iNmjtR5O63+NN8yE44/K -/oMX0tVx1X3kjRDq9t9vlY2Bq0TBqlfHD2Aw7rzFD9rURQdyBqY8hzfDvoaMUrSo -rcwubTgJky3d8s6HHSsK4ix0hTbTPO1FrHSfAl4FMg5M/LStfEvtgyUmK6MZDWyY -yjkxLaVseJsG8D0IFME3yOosaN7eVPMx71Qc+bPOQhgXjNsabC94dcjO +MIIEgzCCAuugAwIBAgIRAL5io3lmrTgG6VQyThHrRikwDQYJKoZIhvcNAQELBQAw +gacxHjAcBgNVBAoTFW1rY2VydCBkZXZlbG9wbWVudCBDQTE+MDwGA1UECww1ZXls +ZXhhbmRlckBFeWxleGFuZGVyU3R1ZGllcy1FbmRlYXZvdXJvcyAoRXlsZXhhbmRl +cikxRTBDBgNVBAMMPG1rY2VydCBleWxleGFuZGVyQEV5bGV4YW5kZXJTdHVkaWVz +LUVuZGVhdm91cm9zIChFeWxleGFuZGVyKTAeFw0yNjA3MDMxNzQxMThaFw0yODEw +MDMxNzQxMThaMGkxJzAlBgNVBAoTHm1rY2VydCBkZXZlbG9wbWVudCBjZXJ0aWZp +Y2F0ZTE+MDwGA1UECww1ZXlsZXhhbmRlckBFeWxleGFuZGVyU3R1ZGllcy1FbmRl +YXZvdXJvcyAoRXlsZXhhbmRlcikwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQCnESPrJQ4+EN6ex4J4Rf+q42tIxcmkgFaj0b+qAoSed5cfJSB+wPOtjBNb +mcCzLTnI3SU3yOxwmkgxfkCQIB1yAY96HUUKoxI6WGU38DdEQily0vFc2mOHYIpG +Cwaht7HOwaNFSpE9BV65/tmNKJjE62aCpVxMQvYjHV0NQwT0VYyFP2N8bgD6SOhT +Fpf6o4ggcmhUd/OPPInV/27peKml2DkwgOAEs+OYcKsTHEO3/2SxNhjaRQpeJ1LV +rqI+8OFRmi14mEvltl1It39TEnmJm78vyhgr3PkW1dSWGc+OIeqC+H3zR+CGYCUt +cZBQFKJ6SksmEO232xhHBnyG5diTAgMBAAGjZzBlMA4GA1UdDwEB/wQEAwIFoDAT +BgNVHSUEDDAKBggrBgEFBQcDATAfBgNVHSMEGDAWgBTlh5B1AmfK55V1pSHVxqyG +mlrhPjAdBgNVHREEFjAUghJpbnRlZ3JhdGlvbi51dHQuZnIwDQYJKoZIhvcNAQEL +BQADggGBAB6YeaJGOlB2bylPzhtaLsRgemC0KIZDGDYjuIwNwrrmDsTCjQPDDbi+ ++y+YFFIcVmqIrXGuy3ZhkycKiEDQknWnAIF76d63AwjizfJJpeYX9Y378+XfPEum +LpFBev86qcbxvpEhecKsuHIEHrZL78LSI4NglgrGjVsJg5Co6ArUoGpCKae7hAs3 +lREON2cydvmQuj0RddyMbWJoX0ZXA4b5guvYslaamAY4xOmbFGt2sD08lf/bsAG3 +Hrax3tK7I2v0bgTnbms3W7bvu+ACo3lEkosZrLL2AeSY+mAbuJ9yhwnlY2G72Oy1 +BQYgorqZB1ss906KEf0vN0sDS5Y5SxXZdV0VQWeh73YFTqDGOooSvMc0IEJMoq+c +07qcQPEmhX7sjzXYwOtssOZ6WnMZL6de1ZncccXUYwldHwpujvB1N9BBzHXzfGax +W5gp4s+v4yUxDXPqs40xPPB8aYXx925xLgH86Vr7pQo7m/LlkqnJ+7DGqhmLiVOO +3EjkNaG26Q== -----END CERTIFICATE----- From ab1f056cf578111e14098f86a8f4739aa8b8e72a Mon Sep 17 00:00:00 2001 From: Eylexander Date: Mon, 6 Jul 2026 13:52:16 +0200 Subject: [PATCH 2/4] refactor(orm): translation made, need debug --- backend/src/services/auth.service.ts | 77 ++-- backend/src/services/bus.service.ts | 73 ++-- backend/src/services/challenge.service.ts | 221 +++++----- backend/src/services/discord.service.ts | 15 +- backend/src/services/event.service.ts | 87 ++-- backend/src/services/faction.service.ts | 44 +- backend/src/services/news.service.ts | 68 ++- backend/src/services/permanence.service.ts | 415 +++++++------------ backend/src/services/registration.service.ts | 8 +- backend/src/services/role.service.ts | 218 ++++------ backend/src/services/team.service.ts | 307 ++++++-------- backend/src/services/tent.service.ts | 149 +++---- backend/src/services/user.service.ts | 246 ++++++----- 13 files changed, 798 insertions(+), 1130 deletions(-) diff --git a/backend/src/services/auth.service.ts b/backend/src/services/auth.service.ts index 3cd89d6..f0ab322 100644 --- a/backend/src/services/auth.service.ts +++ b/backend/src/services/auth.service.ts @@ -1,13 +1,9 @@ import bcrypt from 'bcryptjs'; import { randomBytes } from "crypto"; -import { eq } from "drizzle-orm"; import { JSDOM } from 'jsdom'; import jwt from 'jsonwebtoken'; -import { cas_validate_url } from '../../src/utils/secret'; -import { db } from '../database/db'; -import { type User, userSchema } from '../schemas/Basic/user.schema'; -import { registrationSchema } from '../schemas/Relational/registration.schema'; -import { jwtSecret } from '../utils/secret'; +import { db } from '../prisma/db'; +import { cas_validate_url, jwtSecret } from '../utils/secret'; import * as role_service from './role.service'; import * as user_service from './user.service'; @@ -22,14 +18,14 @@ export const comparePassword = async (password: string, hashedPassword: string): return bcrypt.compare(password, hashedPassword); }; -// Fonction pour générer un JWT -export const generateToken = (user: User & { roles: { roleId: number; roleName: string }[] }) => { +// Fonction pour générer un token JWT +export const generateToken = (user: { id: number; email: string | null; permission: string | null; roles: { roleId: number; roleName: string }[] }) => { return jwt.sign( { userId: user.id, userEmail: user.email, userPermission: user.permission, - userRoles: user.roles, // Ajout des rôles dans le token + userRoles: user.roles, }, jwtSecret, { expiresIn: "1h" } @@ -50,14 +46,9 @@ export const loginUser = async (email: string, password: string) => { throw new Error("Mot de passe incorrect"); } - // Récupérer les rôles de l'utilisateur - const userRoles = await role_service.getUserRoles(user.id); // [{ roleId, roleName }] + const userRoles = await role_service.getUserRoles(user.id); - // Ajouter les rôles à l'objet utilisateur - const enrichedUser = { - ...user, - roles: userRoles, - }; + const enrichedUser = { ...user, roles: userRoles }; // Générer un token JWT avec les rôles inclus const token = generateToken(enrichedUser); @@ -75,17 +66,17 @@ export const registerUser = async (firstName: string, lastName: string, email: s // Hacher le mot de passe avant de l'enregistrer const hashedPassword = await hashPassword(password); - // Créer un nouvel utilisateur dans la base de données - const result = await db.insert(userSchema).values({ - first_name: firstName, - last_name: lastName, - email: email, - password: hashedPassword, - permission: 'Nouveau', + // Créer l'utilisateur dans la base de données + const newUser = await db.users.create({ + data: { + first_name: firstName, + last_name: lastName, + email: email, + password: hashedPassword, + permission: 'Nouveau', + } }); - // Retourner le nouvel utilisateur - const newUser = result[0]; // `result` est un tableau avec l'utilisateur inséré return newUser; }; @@ -111,7 +102,7 @@ export const validateCASTicket = async (ticket: string) => { } catch { throw new Error("Failed to fetch CAS. Please try again later."); } -} +}; export const parseUsernameFromCASResponse = async (response: string) => { const dom = new JSDOM(response, { contentType: "application/xml" }); @@ -128,13 +119,10 @@ export const parseUsernameFromCASResponse = async (response: string) => { }; } } -} - -/*================================================================================================================*/ +}; export const completeRegistration = async (token: string, password: string) => { - - const [tokenRow] = await db.select().from(registrationSchema).where(eq(registrationSchema.token, token)); + const tokenRow = await db.registration_tokens.findFirst({ where: { token } }); if (!tokenRow || new Date(tokenRow.expires_at) < new Date()) { throw new Error("Token invalide ou expiré."); @@ -142,23 +130,20 @@ export const completeRegistration = async (token: string, password: string) => { const hashedPassword = await bcrypt.hash(password, 10); - await db.update(userSchema) - .set({ password: hashedPassword, permission: "Nouveau" }) - .where(eq(userSchema.id, tokenRow.user_id)); - - // Supprimer le token - await db.delete(registrationSchema).where(eq(registrationSchema.id, tokenRow.id)); + await db.users.update({ + where: { id: tokenRow.user_id }, + data: { password: hashedPassword, permission: "Nouveau" } + }); -} + await db.registration_tokens.delete({ where: { id: tokenRow.id } }); +}; export const createRegistrationToken = async (userId: number) => { - const token = randomBytes(32).toString("hex"); // Jeton bien sécurisé + const token = randomBytes(32).toString("hex"); const expiresAt = new Date(Date.now() + 1000 * 60 * 60 * 24 * 90); // 90 jours - await db.insert(registrationSchema).values({ - user_id: userId, - token, - expires_at: expiresAt, + await db.registration_tokens.create({ + data: { user_id: userId, token, expires_at: expiresAt } }); return token; @@ -166,10 +151,8 @@ export const createRegistrationToken = async (userId: number) => { export const deleteUserRegistrationToken = async (userId: number) => { try { - await db.delete(registrationSchema).where(eq(registrationSchema.user_id, userId)); - return; - } - catch (error) { + await db.registration_tokens.deleteMany({ where: { user_id: userId } }); + } catch (error) { throw new Error(error); } }; diff --git a/backend/src/services/bus.service.ts b/backend/src/services/bus.service.ts index 7b15e73..d1b1cda 100644 --- a/backend/src/services/bus.service.ts +++ b/backend/src/services/bus.service.ts @@ -1,15 +1,12 @@ -import { eq } from "drizzle-orm"; import fs from "fs"; import Papa from "papaparse"; -import { db } from "../database/db"; -import { userSchema } from "../schemas/Basic/user.schema"; -import { busAttributionSchema } from "../schemas/Relational/busattribution.schema"; +import { db } from "../prisma/db"; export interface BusAttribution { userId: number; firstName: string | null; lastName: string | null; - email: string; + email: string | null; bus: number; departure_time: string; } @@ -22,42 +19,41 @@ type CsvBus = { // Récupérer toutes les attributions bus + user export const getAllBusAttributions = async (): Promise => { - const results = await db - .select({ - userId: userSchema.id, - firstName: userSchema.first_name, - lastName: userSchema.last_name, - email: userSchema.email, - bus: busAttributionSchema.bus, - departure_time: busAttributionSchema.departure_time - }) - .from(busAttributionSchema) - .innerJoin(userSchema, eq(userSchema.id, busAttributionSchema.user_id)); - - return results; + const results = await db.bus_attribution.findMany({ + include: { + users: { select: { id: true, first_name: true, last_name: true, email: true } } + } + }); + return results.map(r => ({ + userId: r.users.id, + firstName: r.users.first_name, + lastName: r.users.last_name, + email: r.users.email, + bus: r.bus, + departure_time: r.departure_time, + })); }; // Récupérer une attribution précise par userId export const getBusAttributionByUserId = async (userId: number): Promise => { - const result = await db - .select({ - userId: userSchema.id, - firstName: userSchema.first_name, - lastName: userSchema.last_name, - email: userSchema.email, - bus: busAttributionSchema.bus, - departure_time: busAttributionSchema.departure_time, - }) - .from(busAttributionSchema) - .innerJoin(userSchema, eq(userSchema.id, busAttributionSchema.user_id)) - .where(eq(busAttributionSchema.user_id, userId)); - - return result.length > 0 ? result[0] : null; + const result = await db.bus_attribution.findUnique({ + where: { user_id: userId }, + include: { + users: { select: { id: true, first_name: true, last_name: true, email: true } } + } + }); + if (!result) return null; + return { + userId: result.users.id, + firstName: result.users.first_name, + lastName: result.users.last_name, + email: result.users.email, + bus: result.bus, + departure_time: result.departure_time, + }; }; -export const importBusFromCSV = async ( - filePath: string -): Promise => { +export const importBusFromCSV = async (filePath: string): Promise => { const fileContent = fs.readFileSync(filePath, "utf8"); const { data, errors } = Papa.parse(fileContent, { @@ -71,11 +67,10 @@ export const importBusFromCSV = async ( } const parsedData = data.map((r) => ({ - user_id: r.user_id, - bus: r.bus, + user_id: Number(r.user_id), + bus: Number(r.bus), departure_time: r.departure_time, - })); - await db.insert(busAttributionSchema).values(parsedData); + await db.bus_attribution.createMany({ data: parsedData, skipDuplicates: true }); }; diff --git a/backend/src/services/challenge.service.ts b/backend/src/services/challenge.service.ts index 44d7ece..2102caf 100644 --- a/backend/src/services/challenge.service.ts +++ b/backend/src/services/challenge.service.ts @@ -1,10 +1,4 @@ -import { and, desc, eq, isNotNull, not, sum } from "drizzle-orm"; -import { db } from "../database/db"; -import { challengeSchema } from "../schemas/Basic/challenge.schema"; -import { factionSchema } from "../schemas/Basic/faction.schema"; -import { teamSchema } from "../schemas/Basic/team.schema"; -import { userSchema } from "../schemas/Basic/user.schema"; -import { challengeValidationSchema } from "../schemas/Relational/challengevalidation.schema"; // Schéma combiné +import { db } from "../prisma/db"; import * as team_service from "./team.service"; // 1. Créer un challenge @@ -15,35 +9,28 @@ export const createChallenge = async ( points: number, created_by: number ) => { - const challengeValues = { - title, - description, - category, - points, - created_by - }; - const result = await db.insert(challengeSchema).values(challengeValues).returning(); - return result[0]; + return await db.challenges.create({ + data: { title, description, category, points, created_by } + }); }; // 2. Supprimer un challenge export const deleteChallenge = async (challengeId: number) => { - // Récupère le challenge depuis la base de données - const challenge = await db.select().from(challengeSchema).where(eq(challengeSchema.id, challengeId)); - if (!challenge[0]) throw new Error("Challenge introuvable"); + // Vérifier si le challenge existe + const challenge = await db.challenges.findUnique({ where: { id: challengeId } }); + if (!challenge) throw new Error("Challenge introuvable"); - // Supprime les validations et les points associés à ce challenge - await db.delete(challengeValidationSchema).where(eq(challengeValidationSchema.challenge_id, challengeId)); - - // Supprime le challenge de la base de données - await db.delete(challengeSchema).where(eq(challengeSchema.id, challengeId)); + // Supprimer les validations associées avant de supprimer le challenge + await db.challenge_validation.deleteMany({ where: { challenge_id: challengeId } }); + // Supprimer le challenge + await db.challenges.delete({ where: { id: challengeId } }); return { message: `Challenge avec ID ${challengeId} supprimé` }; }; // 3. Récupérer tous les challenges sauf le free export const getAllChallenges = async () => { - return await db.select().from(challengeSchema).where(not(eq(challengeSchema.id, 1))); + return await db.challenges.findMany({ where: { id: { not: 1 } } }); }; // 4. Valider un challenge et attribuer des points @@ -58,18 +45,18 @@ export const validateChallenge = async ({ targetId: number; validatedBy: number; }) => { - // 1. Récupérer le challenge - const challenge = await db.select().from(challengeSchema).where(eq(challengeSchema.id, challengeId)); - if (!challenge[0]) throw new Error("Challenge introuvable"); + // 1. Vérifier si le challenge existe + const challenge = await db.challenges.findUnique({ where: { id: challengeId } }); + if (!challenge) throw new Error("Challenge introuvable"); - // 2. Déterminer le team_id et faction_id en fonction du type - let target_team_id = null; - let target_faction_id = null; + // 2. Déterminer les IDs de l'équipe et de la faction en fonction du type + let target_team_id: number | null = null; + let target_faction_id: number | null = null; switch (type) { case "user": - target_team_id = await team_service.getUserTeam(targetId); - target_faction_id = await team_service.getTeamFaction(target_team_id); + target_team_id = await team_service.getUserTeam(targetId) ?? null; + target_faction_id = target_team_id ? await team_service.getTeamFaction(target_team_id) : null; break; case "team": target_faction_id = await team_service.getTeamFaction(targetId); @@ -82,22 +69,21 @@ export const validateChallenge = async ({ throw new Error("Type de challenge non valide"); } - // 3. Créer l'objet de validation du challenge - const newChallengeValidationPoints = { - challenge_id: challengeId, - validated_by_admin_id: validatedBy, - validated_at: new Date(), - points: challenge[0].points, - added_by_admin_id: validatedBy, - target_user_id: type === "user" ? targetId : null, - target_team_id: target_team_id ? target_team_id : (type === "team" ? targetId : null), - target_faction_id: target_faction_id, - }; - - // 4. Insérer la validation du challenge dans la base de données - const inserted = await db.insert(challengeValidationSchema).values(newChallengeValidationPoints).returning(); - - return inserted[0]; + // 3. Créer l'entrée dans challenge_validation + const inserted = await db.challenge_validation.create({ + data: { + challenge_id: challengeId, + validated_by_admin_id: validatedBy, + validated_at: new Date(), + points: challenge.points, + added_by_admin_id: validatedBy, + target_user_id: type === "user" ? targetId : null, + target_team_id: target_team_id ? target_team_id : (type === "team" ? targetId : null), + target_faction_id: target_faction_id ?? null, + } + }); + + return inserted; }; // 5. Ajouter ou retirer des points manuellement @@ -113,22 +99,19 @@ export const modifyFactionPoints = async ({ points: number; reason: string; adminId: number; - challengeId?: number; // Optional, lié au challenge pour ajouter des points à une faction spécifique }) => { - const newchall = await createChallenge(title, reason, "Free", points, adminId) - const newChallengeValidationPoints = { - challenge_id: newchall.id, - validated_by_admin_id: adminId, - validated_at: new Date(), - points: points, - added_by_admin_id: adminId, - target_faction_id: factionId, - }; - - // 4. Insérer la validation du challenge dans la base de données - const insert = await db.insert(challengeValidationSchema).values(newChallengeValidationPoints).returning(); - - return insert[0]; + const newchall = await createChallenge(title, reason, "Free", points, adminId); + const insert = await db.challenge_validation.create({ + data: { + challenge_id: newchall.id, + validated_by_admin_id: adminId, + validated_at: new Date(), + points: points, + added_by_admin_id: adminId, + target_faction_id: factionId, + } + }); + return insert; }; export const modifyChallenge = async ({ @@ -144,9 +127,9 @@ export const modifyChallenge = async ({ category?: string; points?: number; }) => { - // 1. Récupérer le challenge à modifier - const challenge = await db.select().from(challengeSchema).where(eq(challengeSchema.id, challengeId)); - if (!challenge[0]) throw new Error("Challenge introuvable"); + // 1. Vérifier si le challenge existe + const challenge = await db.challenges.findUnique({ where: { id: challengeId } }); + if (!challenge) throw new Error("Challenge introuvable"); // 2. Construire l'objet de mise à jour const updateValues: any = {}; @@ -155,15 +138,8 @@ export const modifyChallenge = async ({ if (category) updateValues.category = category; if (points !== undefined) updateValues.points = points; - // 3. Mettre à jour les données du challenge - const updatedChallenge = await db - .update(challengeSchema) - .set(updateValues) - .where(eq(challengeSchema.id, challengeId)) - .returning(); - - // 4. Retourner le challenge mis à jour - return updatedChallenge[0]; + // 3. Mettre à jour le challenge + return await db.challenges.update({ where: { id: challengeId }, data: updateValues }); }; export const unvalidateChallenge = async ({ @@ -177,50 +153,49 @@ export const unvalidateChallenge = async ({ teamId: number; userId: number; }) => { - const rowToDelete = await db.select().from(challengeValidationSchema) - .where(and( - eq(challengeValidationSchema.challenge_id, challengeId), - eq(challengeValidationSchema.target_faction_id, factionId), - // Vérification de la présence de teamId et userId avant de les ajouter à la requête - ...(teamId ? [eq(challengeValidationSchema.target_team_id, teamId)] : []), - ...(userId ? [eq(challengeValidationSchema.target_user_id, userId)] : []) - )) - .limit(1); // Limite à une seule ligne - if (rowToDelete.length > 0) { - await db.delete(challengeValidationSchema) - .where(eq(challengeValidationSchema.id, rowToDelete[0].id)); + const rowToDelete = await db.challenge_validation.findFirst({ + where: { + AND: [ + { challenge_id: challengeId }, + { target_faction_id: factionId }, + ...(teamId ? [{ target_team_id: teamId }] : []), + ...(userId ? [{ target_user_id: userId }] : []), + ] + } + }); + if (rowToDelete) { + await db.challenge_validation.delete({ where: { id: rowToDelete.id } }); } - ; }; export const getValidatedChallenges = async () => { try { - // Effectuer une jointure entre les tables pour récupérer toutes les informations pertinentes - const validatedChallenges = await db - .select({ - challenge_id: challengeValidationSchema.challenge_id, - challenge_name: challengeSchema.title, - challenge_categorie: challengeSchema.category, - challenge_description: challengeSchema.description, - points: challengeValidationSchema.points, - validated_at: challengeValidationSchema.validated_at, - target_user_id: challengeValidationSchema.target_user_id, - target_team_id: challengeValidationSchema.target_team_id, - target_faction_id: challengeValidationSchema.target_faction_id, - target_user_firstname: userSchema.first_name, // ou d'autres champs de user - target_user_lastname: userSchema.last_name, - target_team_name: teamSchema.name, // ou d'autres champs de team - target_faction_name: factionSchema.name, // ou d'autres champs de faction - }) - .from(challengeValidationSchema) - .leftJoin(challengeSchema, eq(challengeValidationSchema.challenge_id, challengeSchema.id)) - .leftJoin(userSchema, and(eq(challengeValidationSchema.target_user_id, userSchema.id), isNotNull(challengeValidationSchema.target_user_id))) - .leftJoin(teamSchema, and(eq(challengeValidationSchema.target_team_id, (teamSchema.id)), isNotNull(challengeValidationSchema.target_team_id))) - .leftJoin(factionSchema, and(eq(challengeValidationSchema.target_faction_id, factionSchema.id), isNotNull(challengeValidationSchema.target_faction_id))) - .orderBy(desc(challengeValidationSchema.validated_at)); // Trier par date de validation (si nécessaire) - - // Retourner les résultats - return validatedChallenges; + const rows = await db.challenge_validation.findMany({ + orderBy: { validated_at: 'desc' }, + include: { + challenges: { select: { title: true, category: true, description: true } }, + users_challenge_validation_target_user_idTousers: { + select: { first_name: true, last_name: true } + }, + teams: { select: { name: true } }, + factions: { select: { name: true } }, + } + }); + return rows.map(vc => ({ + challenge_id: vc.challenge_id, + challenge_name: vc.challenges?.title ?? null, + challenge_categorie: vc.challenges?.category ?? null, + challenge_description: vc.challenges?.description ?? null, + points: vc.points, + validated_at: vc.validated_at, + target_user_id: vc.target_user_id, + target_team_id: vc.target_team_id, + target_faction_id: vc.target_faction_id, + target_user_firstname: vc.users_challenge_validation_target_user_idTousers?.first_name ?? null, + target_user_lastname: vc.users_challenge_validation_target_user_idTousers?.last_name ?? null, + target_team_name: vc.teams?.name ?? null, + target_faction_name: vc.factions?.name ?? null, + })); } catch (error) { console.error("Error retrieving validated challenges:", error); throw new Error("Error retrieving validated challenges"); @@ -229,17 +204,11 @@ export const getValidatedChallenges = async () => { export const getTotalFactionPoints = async (factionId: number): Promise => { try { - const result = await db - .select({ - totalPoints: sum(challengeValidationSchema.points) // Somme des points - , // Filtre par l'ID de la faction - }) - .from(challengeValidationSchema).where(eq(challengeValidationSchema.target_faction_id, factionId)); - - // Récupérer le total des points - - const totalPoints = Number(result[0]?.totalPoints) || 0; - return totalPoints; + const result = await db.challenge_validation.aggregate({ + where: { target_faction_id: factionId }, + _sum: { points: true } + }); + return Number(result._sum.points) || 0; } catch (error) { console.error("Erreur lors de la récupération des points de la faction:", error); throw new Error("Impossible de récupérer les points de la faction"); diff --git a/backend/src/services/discord.service.ts b/backend/src/services/discord.service.ts index 07602c4..bb52b42 100644 --- a/backend/src/services/discord.service.ts +++ b/backend/src/services/discord.service.ts @@ -1,7 +1,5 @@ import axios from 'axios'; -import { eq } from 'drizzle-orm'; -import { db } from '../database/db'; // Import de la connexion PostgreSQL -import { userSchema } from '../schemas/Basic/user.schema'; +import { db } from '../prisma/db'; import { discord_client_id, discord_client_secret, discord_redirect_uri } from '../utils/secret'; export const syncDiscordUserId = async (code: string, userId: number) => { @@ -28,10 +26,11 @@ export const syncDiscordUserId = async (code: string, userId: number) => { } }); - //Etape 3 : Update le discord_id de l'user - await db.update(userSchema) - .set({ discord_id: userResponse.data.id, }) - .where(eq(userSchema.id, userId)); + // Étape 3 : met à jour l'utilisateur dans la base de données avec l'ID Discord + await db.users.update({ + where: { id: userId }, + data: { discord_id: userResponse.data.id }, + }); - return userResponse.data; // { id, username, discriminator, ... } + return userResponse.data; }; diff --git a/backend/src/services/event.service.ts b/backend/src/services/event.service.ts index 1457dec..79a3c6e 100644 --- a/backend/src/services/event.service.ts +++ b/backend/src/services/event.service.ts @@ -1,83 +1,64 @@ -import { asc, eq } from "drizzle-orm"; -import { db } from "../database/db"; -import { eventSchema } from "../schemas/Basic/event.schema"; -import { teamSchema } from "../schemas/Basic/team.schema"; -import { teamShotgunSchema } from "../schemas/Relational/teamshotgun.schema"; +import { db } from "../prisma/db"; export const getEventsStatus = async () => { - const events = await db.select().from(eventSchema); - if (events.length > 0) { - return events[0]; // Renvoie le premier événement s'il existe - } else { - return null; // ou une valeur par défaut - } + const event = await db.events.findFirst(); + return event ?? null; }; export const validateShotgun = async (teamId: number) => { - await db.transaction(async (tx) => { - await tx.insert(teamShotgunSchema).values({ team_id: teamId }); - }) + await db.$transaction(async (tx) => { + await tx.team_shotgun.create({ data: { team_id: teamId } }); + }); }; export const alreadyShotgun = async (teamId: number) => { - const shotgunTeam = await db.select({ shotgunId: teamShotgunSchema.id }) - .from(teamShotgunSchema) - .where(eq(teamShotgunSchema.team_id, teamId)); - - if (shotgunTeam[0]) { - return true - } - else { - return false - } + const shotgunTeam = await db.team_shotgun.findFirst({ + where: { team_id: teamId }, + select: { id: true } + }); + return !!shotgunTeam; }; export const updatepreRegistrationStatus = async (preRegistrationOpen: boolean) => { - return await db.update(eventSchema) - .set({ pre_registration_open: preRegistrationOpen }) - .returning(); + await db.events.updateMany({ data: { pre_registration_open: preRegistrationOpen } }); + return await db.events.findMany(); }; export const updateShotgunStatus = async (shotgunOpen: boolean) => { - return await db.update(eventSchema) - .set({ shotgun_open: shotgunOpen }) - .returning(); + await db.events.updateMany({ data: { shotgun_open: shotgunOpen } }); + return await db.events.findMany(); }; export const getAllTeamShotguns = async () => { - return await db - .select({ - id: teamShotgunSchema.id, - teamId: teamShotgunSchema.team_id, - timestamp: teamShotgunSchema.timestamp, - teamName: teamSchema.name, - teamType: teamSchema.type, - }) - .from(teamShotgunSchema) - .leftJoin(teamSchema, eq(teamShotgunSchema.team_id, teamSchema.id)) - .orderBy(asc(teamShotgunSchema.timestamp), asc(teamShotgunSchema.id)); + const results = await db.team_shotgun.findMany({ + orderBy: [{ timestamp: 'asc' }, { id: 'asc' }], + include: { teams: { select: { name: true, type: true } } } + }); + return results.map(r => ({ + id: r.id, + teamId: r.team_id, + timestamp: r.timestamp, + teamName: r.teams?.name ?? null, + teamType: r.teams?.type ?? null, + })); }; export const updateSDIStatus = async (sdiOpen: boolean) => { - return await db.update(eventSchema) - .set({ sdi_open: sdiOpen }) - .returning(); + await db.events.updateMany({ data: { sdi_open: sdiOpen } }); + return await db.events.findMany(); }; export const updateWEIStatus = async (weiOpen: boolean) => { - return await db.update(eventSchema) - .set({ wei_open: weiOpen }) - .returning(); + await db.events.updateMany({ data: { wei_open: weiOpen } }); + return await db.events.findMany(); }; export const updateFoodStatus = async (foodOpen: boolean) => { - return await db.update(eventSchema) - .set({ food_open: foodOpen }) - .returning(); + await db.events.updateMany({ data: { food_open: foodOpen } }); + return await db.events.findMany(); }; export const updateChallStatus = async (challOpen: boolean) => { - return await db.update(eventSchema) - .set({ chall_open: challOpen }) - .returning(); + await db.events.updateMany({ data: { chall_open: challOpen } }); + return await db.events.findMany(); }; diff --git a/backend/src/services/faction.service.ts b/backend/src/services/faction.service.ts index 7171252..b03185d 100644 --- a/backend/src/services/faction.service.ts +++ b/backend/src/services/faction.service.ts @@ -1,35 +1,25 @@ -import { eq } from "drizzle-orm"; -import { db } from "../database/db"; -import { factionSchema } from "../schemas/Basic/faction.schema"; +import { db } from '../prisma/db'; export const getFactions = async () => { - const factions = await db.select( - { - factionId: factionSchema.id, - name: factionSchema.name, - description: factionSchema.description - }).from(factionSchema); - - return factions -} + const factions = await db.factions.findMany({ + select: { id: true, name: true, description: true } + }); + return factions.map(f => ({ factionId: f.id, name: f.name, description: f.description })); +}; export const getFaction = async (factionId: any) => { - const faction = await db.select( - { - factionId: factionSchema.id, - name: factionSchema.name, - description: factionSchema.description - }).from(factionSchema).where(eq(factionSchema.id, factionId)); - - return faction[0] -} + const faction = await db.factions.findUnique({ + where: { id: factionId }, + select: { id: true, name: true, description: true } + }); + return faction ? { factionId: faction.id, name: faction.name, description: faction.description } : undefined; +}; export const createFaction = async (factionName: string) => { - const faction = await db.insert(factionSchema).values({ name: factionName }).returning(); - - return faction -} + const faction = await db.factions.create({ data: { name: factionName } }); + return [faction]; +}; export const deleteFaction = async (factionId: number) => { - await db.delete(factionSchema).where(eq(factionSchema.id, factionId)); -} + await db.factions.delete({ where: { id: factionId } }); +}; diff --git a/backend/src/services/news.service.ts b/backend/src/services/news.service.ts index c6fedf1..949a627 100644 --- a/backend/src/services/news.service.ts +++ b/backend/src/services/news.service.ts @@ -1,6 +1,13 @@ -import { and, desc, eq } from "drizzle-orm"; -import { db } from "../database/db"; -import { type News, newsSchema } from "../schemas/Basic/news.schema"; +import { db } from '../prisma/db'; + +type NewsUpdateFields = Partial<{ + title: string | null; + description: string | null; + type: string | null; + published: boolean | null; + target: string | null; + image_url: string | null; +}>; // Créer une actu export const createNews = async ( @@ -11,59 +18,50 @@ export const createNews = async ( target: string, image_url?: string ) => { - const newNews = { - title, - description, - type, - published, - target, - image_url - }; - - const result = await db.insert(newsSchema).values(newNews).returning(); - return result[0]; + return await db.news.create({ + data: { title, description, type, published, target, image_url } + }); }; // Lister les actus export const getAllNews = async () => { - return await db.select().from(newsSchema).orderBy(desc(newsSchema.created_at)); + return await db.news.findMany({ orderBy: { created_at: 'desc' } }); }; // Lister les actus publiées (pour l'onglet côté utilisateur) export const getPublishedNews = async () => { - return await db.select().from(newsSchema).where(eq(newsSchema.published, true)).orderBy(desc(newsSchema.created_at)); + return await db.news.findMany({ + where: { published: true }, + orderBy: { created_at: 'desc' } + }); }; // Filtrer par type export const getPublishedNewsByType = async (type: string) => { - return await db.select().from(newsSchema) - .where(and(eq(newsSchema.published, true), eq(newsSchema.type, type))) - .orderBy(desc(newsSchema.created_at)); + return await db.news.findMany({ + where: { published: true, type }, + orderBy: { created_at: 'desc' } + }); }; // Publier une actu export const publishNews = async (id: number) => { - await db.update(newsSchema).set({ published: true }).where(eq(newsSchema.id, id)).returning(); + await db.news.update({ where: { id }, data: { published: true } }); }; +// Dépublier une actu export const deleteNews = async (newsId: number) => { - await db.delete(newsSchema).where(eq(newsSchema.id, newsId)); -} - -export const updateNews = async ( - id: number, - updates: Partial -) => { - const result = await db - .update(newsSchema) - .set(updates) - .where(eq(newsSchema.id, id)) - .returning(); + await db.news.delete({ where: { id: newsId } }); +}; - return result[0] ?? null; +// Mettre à jour une actu +export const updateNews = async (id: number, updates: NewsUpdateFields) => { + const result = await db.news.update({ where: { id }, data: updates }); + return result ?? null; }; +// Récupérer une actu par son ID export const getNewsById = async (id: number) => { - const result = await db.select().from(newsSchema).where(eq(newsSchema.id, id)); - return result[0] ?? null; + const result = await db.news.findUnique({ where: { id } }); + return result ?? null; }; diff --git a/backend/src/services/permanence.service.ts b/backend/src/services/permanence.service.ts index b47e69a..3002e81 100644 --- a/backend/src/services/permanence.service.ts +++ b/backend/src/services/permanence.service.ts @@ -1,10 +1,6 @@ -import { and, eq, inArray, sql } from "drizzle-orm"; import fs from "fs"; import Papa from "papaparse"; -import { db } from "../database/db"; -import { permanenceSchema } from "../schemas/Basic/permanence.schema"; -import { userSchema } from "../schemas/Basic/user.schema"; -import { respoPermanenceSchema, userPermanenceSchema } from "../schemas/Relational/userpermanences.schema"; +import { db } from "../prisma/db"; type CsvPermanence = { name: string; @@ -27,27 +23,16 @@ class UnregisterDeadlineError extends Error { } class RegisterDeadlineError extends Error { } export const getPermanenceById = async (permId: number) => { - const permanence = await db.query.permanenceSchema.findFirst({ - where: eq(permanenceSchema.id, permId), - }); + const permanence = await db.permanences.findUnique({ where: { id: permId } }); if (!permanence) throw new PermanenceNotFoundError("Permanence introuvable"); return permanence; }; -// ➕ S'inscrire à une permanence -export const registerUserToPermanence = async ( - userId: number, - permId: number -) => { +// S'inscrire à une permanence +export const registerUserToPermanence = async (userId: number, permId: number) => { try { - // Vérifications légères (sans verrouillage) - const user = await db.query.userSchema.findFirst({ - where: eq(userSchema.id, userId), - }); - if ( - !user || - (user.permission !== "Student" && user.permission !== "Admin") - ) { + const user = await db.users.findFirst({ where: { id: userId } }); + if (!user || (user.permission !== "Student" && user.permission !== "Admin")) { throw new UnauthorizedError("Unauthorized"); } @@ -67,27 +52,25 @@ export const registerUserToPermanence = async ( throw new PermanenceFullError("Permanence full"); } - // Transaction avec verrouillage de table complet - await db.transaction(async (tx) => { - // 1. VERROUILLAGE COMPLET de la table permanences - await tx.execute(sql`LOCK TABLE permanences IN EXCLUSIVE MODE`); + // Transaction avec verrouillage complet pour gérer l'inscription et la mise à jour de la capacité + await db.$transaction(async (tx) => { + // 1. Verrouillage de la table + await tx.$executeRaw`LOCK TABLE permanences IN EXCLUSIVE MODE`; - // 2. UPDATE atomique avec condition - décrémente seulement si capacity > 0 - const updateResult = await tx - .update(permanenceSchema) - .set({ capacity: sql`capacity - 1` }) - .where(and(eq(permanenceSchema.id, permId), sql`capacity > 0`)) - .returning({ newCapacity: permanenceSchema.capacity }); + // 2. Update de la capacité avec vérification + const updateCount = await tx.$executeRaw` + UPDATE permanences SET capacity = capacity - 1 + WHERE id = ${permId} AND capacity > 0 + `; // 3. Si aucune ligne modifiée = pas de place disponible - if (updateResult.length === 0) { + if (updateCount === 0) { throw new PermanenceFullError("Permanence full"); } // 4. Insérer l'utilisateur (seulement si l'UPDATE a réussi) - await tx.insert(userPermanenceSchema).values({ - user_id: userId, - permanence_id: permId, + await tx.user_permanences.create({ + data: { user_id: userId, permanence_id: permId } }); }); } catch (error: any) { @@ -106,11 +89,8 @@ export const registerUserToPermanence = async ( } }; -// ❌ Se désinscrire d'une permanence -export const unregisterUserFromPermanence = async ( - userId: number, - permId: number -) => { +// Désinscrire un utilisateur d'une permanence +export const unregisterUserFromPermanence = async (userId: number, permId: number) => { const permanence = await getPermanenceById(permId); const now = new Date(); const limitDate = new Date(permanence.start_at); @@ -119,28 +99,22 @@ export const unregisterUserFromPermanence = async ( if (now > limitDate) throw new UnregisterDeadlineError("Too late to unregister"); - // Désinscrire l'utilisateur - await db - .delete(userPermanenceSchema) - .where( - and( - eq(userPermanenceSchema.user_id, userId), - eq(userPermanenceSchema.permanence_id, permId) - ) - ); + await db.user_permanences.deleteMany({ + where: { user_id: userId, permanence_id: permId } + }); await modifyPermCap(permId, 1); }; -// 🔎 Voir toutes les permanences ouvertes +// Récupérer toutes les permanences ouvertes export const listOpenPermanences = async () => { - return await db.query.permanenceSchema.findMany({ - where: eq(permanenceSchema.is_open, true), - orderBy: permanenceSchema.start_at, + return await db.permanences.findMany({ + where: { is_open: true }, + orderBy: { start_at: 'asc' } }); }; -// ➕ Créer une permanence +// Créer une permanence export const createPermanence = async ( name: string, description: string, @@ -151,10 +125,9 @@ export const createPermanence = async ( difficulty: number, respoId: number ) => { - // Étape 1 : Création de la permanence - const [newPermanence] = await db - .insert(permanenceSchema) - .values({ + // 1. Créer la permanence + const newPermanence = await db.permanences.create({ + data: { name, description, location, @@ -163,35 +136,29 @@ export const createPermanence = async ( capacity, is_open: false, difficulty, - }) - .returning({ id: permanenceSchema.id }); + }, + select: { id: true } + }); - // Étape 2 : Ajout du responsable + // 2. Associer le responsable à la permanence if (newPermanence?.id && respoId) { - await db.insert(respoPermanenceSchema).values({ - user_id: respoId, - permanence_id: newPermanence.id, + await db.respo_permanences.create({ + data: { user_id: respoId, permanence_id: newPermanence.id } }); } }; +// Supprimer une permanence export const deletePermanence = async (permId: number) => { - // Étape 1 : Supprimer les inscriptions des utilisateurs - await db - .delete(userPermanenceSchema) - .where(eq(userPermanenceSchema.permanence_id, permId)); - - // Étape 2 : Supprimer les responsables associés - await db - .delete(respoPermanenceSchema) - .where(eq(respoPermanenceSchema.permanence_id, permId)); - - // Étape 3 : Supprimer la permanence - await db - .delete(permanenceSchema) - .where(eq(permanenceSchema.id, permId)); + // Supprimer les inscriptions des utilisateurs + await db.user_permanences.deleteMany({ where: { permanence_id: permId } }); + // Supprimer le responsable de la permanence + await db.respo_permanences.deleteMany({ where: { permanence_id: permId } }); + // Supprimer la permanence elle-même + await db.permanences.delete({ where: { id: permId } }); }; +// Mettre à jour les détails d'une permanence export const updatePermanence = async ( permId: number, name: string, @@ -203,172 +170,128 @@ export const updatePermanence = async ( difficulty: number, respoId: number ) => { - // Étape 1 : Mise à jour de la permanence - await db - .update(permanenceSchema) - .set({ - name, - description, - location, - start_at, - end_at, - capacity, - is_open: false, - difficulty, - }) - .where(eq(permanenceSchema.id, permId)); + // Mettre à jour les détails de la permanence + await db.permanences.update({ + where: { id: permId }, + data: { name, description, location, start_at, end_at, capacity, is_open: false, difficulty } + }); - // Étape 2 : Suppression des anciens responsables (si nécessaire) - await db - .delete(respoPermanenceSchema) - .where(eq(respoPermanenceSchema.permanence_id, permId)); + // Supprimer l'ancien responsable et ajouter le nouveau si nécessaire + await db.respo_permanences.deleteMany({ where: { permanence_id: permId } }); - // Étape 3 : Ajout du nouveau responsable if (respoId) { - await db.insert(respoPermanenceSchema).values({ - user_id: respoId, - permanence_id: permId, - }); + await db.respo_permanences.create({ data: { user_id: respoId, permanence_id: permId } }); } }; // Ouvrir une permanence (Admin action) export const openPermanence = async (permId: number) => { - await db - .update(permanenceSchema) - .set({ is_open: true }) - .where(eq(permanenceSchema.id, permId)); + await db.permanences.update({ where: { id: permId }, data: { is_open: true } }); }; // Fermer une permanence (Admin action) export const closePermanence = async (permId: number) => { - await db - .update(permanenceSchema) - .set({ is_open: false }) - .where(eq(permanenceSchema.id, permId)); + await db.permanences.update({ where: { id: permId }, data: { is_open: false } }); }; // Modifier la capacité de la permanence export const modifyPermCap = async (permId: number, factor: number) => { const perm = await getPermanenceById(permId); const newPermCap = Number(perm.capacity) + factor; - if (newPermCap < 0) throw new Error("Invalid capacity"); - - await db - .update(permanenceSchema) - .set({ capacity: newPermCap }) - .where(eq(permanenceSchema.id, permId)); + await db.permanences.update({ where: { id: permId }, data: { capacity: newPermCap } }); }; // Voir ses permanences export const getMyPermanences = async (userId: number) => { - const userPerms = await db - .select({ - id: permanenceSchema.id, - name: permanenceSchema.name, - start_at: permanenceSchema.start_at, - end_at: permanenceSchema.end_at, - location: permanenceSchema.location, - }) - .from(userPermanenceSchema) - .innerJoin( - permanenceSchema, - eq(permanenceSchema.id, userPermanenceSchema.permanence_id) - ) - .where(eq(userPermanenceSchema.user_id, userId)); - - // Ajout du responsables - const results = await Promise.all( - userPerms.map(async (perm) => { - const [respo] = await db - .select({ - id: userSchema.id, - firstName: userSchema.first_name, - lastName: userSchema.last_name, - email: userSchema.email, - }) - .from(respoPermanenceSchema) - .innerJoin(userSchema, eq(userSchema.id, respoPermanenceSchema.user_id)) - .where(eq(respoPermanenceSchema.permanence_id, perm.id)); + const userPerms = await db.user_permanences.findMany({ + where: { user_id: userId }, + include: { + permanences: { + select: { id: true, name: true, start_at: true, end_at: true, location: true } + } + } + }); + const results = await Promise.all( + userPerms.map(async (up) => { + const respo = await db.respo_permanences.findFirst({ + where: { permanence_id: up.permanence_id }, + include: { + users: { select: { id: true, first_name: true, last_name: true, email: true } } + } + }); return { - ...perm, - respo: respo ?? null, + ...up.permanences, + respo: respo ? { + id: respo.users.id, + firstName: respo.users.first_name, + lastName: respo.users.last_name, + email: respo.users.email, + } : null }; }) ); - return results; }; +// Récupérer toutes les permanences avec leurs responsables et membres export const getAllPermanences = async () => { - const perms = await db.select().from(permanenceSchema); + const perms = await db.permanences.findMany(); const results = await Promise.all( perms.map(async (perm) => { - const [respo] = await db - .select({ - userId: userSchema.id, - firstName: userSchema.first_name, - lastName: userSchema.last_name, - email: userSchema.email, - }) - .from(respoPermanenceSchema) - .innerJoin(userSchema, eq(userSchema.id, respoPermanenceSchema.user_id)) - .where(eq(respoPermanenceSchema.permanence_id, perm.id)); - + const respo = await db.respo_permanences.findFirst({ + where: { permanence_id: perm.id }, + include: { + users: { select: { id: true, first_name: true, last_name: true, email: true } } + } + }); return { ...perm, - respo: respo ?? null, + respo: respo ? { + userId: respo.users.id, + firstName: respo.users.first_name, + lastName: respo.users.last_name, + email: respo.users.email, + } : null }; }) ); - return results; }; +// Récupérer les utilisateurs inscrits à une permanence export const getUsersInPermanence = async (permId: number) => { - return await db - .select({ - userId: userSchema.id, - firstName: userSchema.first_name, - lastName: userSchema.last_name, - claimed: userPermanenceSchema.claimed, - }) - .from(userPermanenceSchema) - .innerJoin(userSchema, eq(userSchema.id, userPermanenceSchema.user_id)) - .where(eq(userPermanenceSchema.permanence_id, permId)); + const rows = await db.user_permanences.findMany({ + where: { permanence_id: permId }, + include: { + users: { select: { id: true, first_name: true, last_name: true } } + } + }); + return rows.map(r => ({ + userId: r.users.id, + firstName: r.users.first_name, + lastName: r.users.last_name, + claimed: r.claimed, + })); }; +// Ajouter un utilisateur à une permanence (Admin action) export const addUserToPermanence = async (userId: number, permId: number) => { - // Désinscrire l'utilisateur - await db.insert(userPermanenceSchema).values({ - user_id: userId, - permanence_id: permId, - }); - + await db.user_permanences.create({ data: { user_id: userId, permanence_id: permId } }); await modifyPermCap(permId, -1); }; -export const removeUserToPermanence = async ( - userId: number, - permId: number -) => { - await db - .delete(userPermanenceSchema) - .where( - and( - eq(userPermanenceSchema.user_id, userId), - eq(userPermanenceSchema.permanence_id, permId) - ) - ); - +// Supprimer un utilisateur d'une permanence (Admin action) +export const removeUserToPermanence = async (userId: number, permId: number) => { + await db.user_permanences.deleteMany({ where: { user_id: userId, permanence_id: permId } }); await modifyPermCap(permId, 1); }; +// Récupérer toutes les permanences avec leurs utilisateurs export const getAllPermanencesWithUsers = async () => { // Récupère toutes les permanences const permanences = await getAllPermanences(); @@ -376,21 +299,21 @@ export const getAllPermanencesWithUsers = async () => { // Pour chaque permanence, on récupère les users associés avec leur statut claimed const results = await Promise.all( permanences.map(async (permanence) => { - const userRelations = await db - .select({ - id: userSchema.id, - first_name: userSchema.first_name, - last_name: userSchema.last_name, - email: userSchema.email, - claimed: userPermanenceSchema.claimed, - }) - .from(userPermanenceSchema) - .innerJoin(userSchema, eq(userSchema.id, userPermanenceSchema.user_id)) - .where(eq(userPermanenceSchema.permanence_id, permanence.id)); - + const userRelations = await db.user_permanences.findMany({ + where: { permanence_id: permanence.id }, + include: { + users: { select: { id: true, first_name: true, last_name: true, email: true } } + } + }); return { ...permanence, - users: userRelations, + users: userRelations.map(r => ({ + id: r.users.id, + first_name: r.users.first_name, + last_name: r.users.last_name, + email: r.users.email, + claimed: r.claimed, + })) }; }) ); @@ -398,9 +321,8 @@ export const getAllPermanencesWithUsers = async () => { return results; }; -export const importPermanencesFromCSV = async ( - filePath: string -): Promise => { +// Permet l'importation de permanences depuis un fichier CSV +export const importPermanencesFromCSV = async (filePath: string): Promise => { const fileContent = fs.readFileSync(filePath, "utf8"); const { data, errors } = Papa.parse(fileContent, { @@ -422,59 +344,44 @@ export const importPermanencesFromCSV = async ( capacity: parseInt(r.capacity, 10), difficulty: parseInt(r.difficulty, 10), is_open: false, - })); - await db.insert(permanenceSchema).values(parsedData); + await db.permanences.createMany({ data: parsedData }); }; -export const isUserRespoOfPermanence = async ( - userId: number, -): Promise => { - const respo = await db - .select() - .from(respoPermanenceSchema) - .where( - eq(respoPermanenceSchema.user_id, userId) - ); - - return respo.length > 0; +// Vérifie si un utilisateur est responsable d'une permanence +export const isUserRespoOfPermanence = async (userId: number): Promise => { + const respo = await db.respo_permanences.findFirst({ where: { user_id: userId } }); + return respo !== null; }; +// Récupère les détails des permanences pour un responsable spécifique export const getPermanenceDetailsForRespo = async (respoId: number) => { - // Étape 1 : Trouver les permanences dont il est respo - const respos = await db - .select() - .from(respoPermanenceSchema) - .where(eq(respoPermanenceSchema.user_id, respoId)); - - const permanenceIds = respos.map((r) => r.permanence_id); - if (permanenceIds.length === 0) throw new Error("Pas de permanences");; - - // Étape 2 : Récupérer les permanences - const permanences = await db - .select() - .from(permanenceSchema) - .where(inArray(permanenceSchema.id, permanenceIds)); - - // Étape 3 : Récupérer les membres avec infos utiles + // Récupérer toutes les permanences pour lesquelles l'utilisateur est responsable + const respos = await db.respo_permanences.findMany({ where: { user_id: respoId } }); + const permanenceIds = respos.map(r => r.permanence_id); + if (permanenceIds.length === 0) throw new Error("Pas de permanences"); + + // Récupérer les permanences et leurs membres + const permanences = await db.permanences.findMany({ where: { id: { in: permanenceIds } } }); + const results = await Promise.all( permanences.map(async (perm) => { - const members = await db - .select({ - id: userSchema.id, - first_name: userSchema.first_name, - last_name: userSchema.last_name, - email: userSchema.email, - claimed: userPermanenceSchema.claimed, - }) - .from(userPermanenceSchema) - .innerJoin(userSchema, eq(userSchema.id, userPermanenceSchema.user_id)) - .where(eq(userPermanenceSchema.permanence_id, perm.id)); - + const members = await db.user_permanences.findMany({ + where: { permanence_id: perm.id }, + include: { + users: { select: { id: true, first_name: true, last_name: true, email: true } } + } + }); return { permanence: perm, - members, + members: members.map(m => ({ + id: m.users.id, + first_name: m.users.first_name, + last_name: m.users.last_name, + email: m.users.email, + claimed: m.claimed, + })) }; }) ); @@ -482,14 +389,10 @@ export const getPermanenceDetailsForRespo = async (respoId: number) => { return results; }; +// Permet à un utilisateur de "claim" sa présence à une permanence export const claimMember = async (userId: number, permId: number, claimed: boolean) => { - await db - .update(userPermanenceSchema) - .set({ claimed }) - .where( - and( - eq(userPermanenceSchema.user_id, userId), - eq(userPermanenceSchema.permanence_id, permId) - ) - ); + await db.user_permanences.updateMany({ + where: { user_id: userId, permanence_id: permId }, + data: { claimed } + }); }; diff --git a/backend/src/services/registration.service.ts b/backend/src/services/registration.service.ts index 1ffac87..9c7733f 100644 --- a/backend/src/services/registration.service.ts +++ b/backend/src/services/registration.service.ts @@ -1,11 +1,9 @@ -import { eq } from 'drizzle-orm'; -import { db } from '../database/db'; // Import de la connexion PostgreSQL -import { registrationSchema } from '../schemas/Relational/registration.schema'; +import { db } from '../prisma/db'; export const getRegistrationByUserId = async (userId: number) => { try { - const token = await db.select().from(registrationSchema).where(eq(registrationSchema.user_id, userId)); - return token[0] ? token[0].token : null; + const token = await db.registration_tokens.findFirst({ where: { user_id: userId } }); + return token ? token.token : null; } catch (err) { console.error('Erreur lors de la récupération du token d\'enregistrement:', err); throw new Error('Erreur de base de données'); diff --git a/backend/src/services/role.service.ts b/backend/src/services/role.service.ts index f7eaaa9..2c87c4e 100644 --- a/backend/src/services/role.service.ts +++ b/backend/src/services/role.service.ts @@ -1,200 +1,138 @@ -import { and, eq } from "drizzle-orm"; -import { db } from "../database/db"; -import { roleSchema } from "../schemas/Basic/role.schema"; -import { userSchema } from "../schemas/Basic/user.schema"; -import { rolePoints } from "../schemas/Relational/rolepoints.schema"; -import { - userPreferencesSchema, - type UserRole, - userRolesSchema, -} from "../schemas/Relational/userroles.schema"; +import { db } from "../prisma/db"; // 1. Ajouter un rôle à un utilisateur export const assignRoleToUser = async (userId: number, roleId: number) => { - const existing = await db - .select() - .from(userRolesSchema) - .where(and(eq(userRolesSchema.user_id, userId), eq(userRolesSchema.role_id, roleId))); - - if (existing.length > 0) return; - - const newUserRole: UserRole = { user_id: userId, role_id: roleId }; - await db.insert(userRolesSchema).values(newUserRole); + const existing = await db.user_roles.findFirst({ where: { user_id: userId, role_id: roleId } }); + if (existing) return; + await db.user_roles.create({ data: { user_id: userId, role_id: roleId } }); }; // 2. Supprimer un rôle d'un utilisateur export const removeRoleFromUser = async (userId: number, roleId: number) => { - await db - .delete(userRolesSchema) - .where(and(eq(userRolesSchema.user_id, userId), eq(userRolesSchema.role_id, roleId))); + await db.user_roles.deleteMany({ where: { user_id: userId, role_id: roleId } }); }; // 3. Récupérer les rôles d'un utilisateur export const getUserRoles = async (userId: number) => { - return await db - .select({ - roleId: roleSchema.id, - roleName: roleSchema.name, - }) - .from(userRolesSchema) - .innerJoin(roleSchema, eq(userRolesSchema.role_id, roleSchema.id)) - .where(eq(userRolesSchema.user_id, userId)); + const rows = await db.user_roles.findMany({ + where: { user_id: userId }, + include: { roles: { select: { id: true, name: true } } } + }); + return rows.map(r => ({ roleId: r.roles.id, roleName: r.roles.name })); }; // 4. Récupérer tous les utilisateurs avec leurs rôles export const getUsersWithRoles = async () => { - return await db - .select({ - userId: userSchema.id, - firstName: userSchema.first_name, - lastName: userSchema.last_name, - roleName: roleSchema.name, - }) - .from(userRolesSchema) - .innerJoin(userSchema, eq(userRolesSchema.user_id, userSchema.id)) - .innerJoin(roleSchema, eq(userRolesSchema.role_id, roleSchema.id)); + const rows = await db.user_roles.findMany({ + include: { + users: { select: { id: true, first_name: true, last_name: true } }, + roles: { select: { name: true } } + } + }); + return rows.map(r => ({ + userId: r.users.id, + firstName: r.users.first_name, + lastName: r.users.last_name, + roleName: r.roles.name, + })); }; // 5. Récupérer les utilisateurs par nom de rôle export const getUsersByRoleName = async (roleName: string) => { - const role = await db - .select() - .from(roleSchema) - .where(eq(roleSchema.name, roleName)) - .limit(1); - - if (role.length === 0) throw new Error("Rôle non trouvé"); - - const roleId = role[0].id; - - return await db - .select({ - userId: userSchema.id, - firstName: userSchema.first_name, - lastName: userSchema.last_name, - email: userSchema.email, - contact: userSchema.contact, - roleName: roleSchema.name, - }) - .from(userPreferencesSchema) - .innerJoin(userSchema, eq(userPreferencesSchema.userId, userSchema.id)) - .innerJoin(roleSchema, eq(userPreferencesSchema.roleId, roleSchema.id)) - .where(eq(userPreferencesSchema.roleId, roleId)); + const role = await db.roles.findFirst({ where: { name: roleName } }); + if (!role) throw new Error("Rôle non trouvé"); + + const rows = await db.user_preferences.findMany({ + where: { role_id: role.id }, + include: { + users: { select: { id: true, first_name: true, last_name: true, email: true, contact: true } }, + roles: { select: { name: true } } + } + }); + return rows.map(r => ({ + userId: r.users.id, + firstName: r.users.first_name, + lastName: r.users.last_name, + email: r.users.email, + contact: r.users.contact, + roleName: r.roles.name, + })); }; // 6. Mettre à jour les préférences d'un utilisateur export const updateUserPreferences = async (userId: number, roleIds: number[]) => { - await db.delete(userPreferencesSchema).where(eq(userPreferencesSchema.userId, userId)); - - const newPreferences = roleIds.map((roleId) => ({ userId, roleId })); - await db.insert(userPreferencesSchema).values(newPreferences); + await db.user_preferences.deleteMany({ where: { user_id: userId } }); + if (roleIds.length > 0) { + await db.user_preferences.createMany({ + data: roleIds.map(roleId => ({ user_id: userId, role_id: roleId })) + }); + } }; // 7. Récupérer les préférences d'un utilisateur export const getUserPreferences = async (userId: number) => { - return await db - .select({ - roleId: roleSchema.id, - roleName: roleSchema.name, - }) - .from(userPreferencesSchema) - .innerJoin(roleSchema, eq(userPreferencesSchema.roleId, roleSchema.id)) - .where(eq(userPreferencesSchema.userId, userId)); + const rows = await db.user_preferences.findMany({ + where: { user_id: userId }, + include: { roles: { select: { id: true, name: true } } } + }); + return rows.map(r => ({ roleId: r.roles.id, roleName: r.roles.name })); }; // 8. Récupérer tous les rôles disponibles export const getAllRoles = async () => { - return await db - .select({ - roleId: roleSchema.id, - name: roleSchema.name, - description: roleSchema.description, - }) - .from(roleSchema); + const roles = await db.roles.findMany({ + select: { id: true, name: true, description: true } + }); + return roles.map(r => ({ roleId: r.id, name: r.name, description: r.description })); }; - // 9. Ajouter des points à un rôle export const addPointsToRole = async (roleId: number, pointsToAdd: number) => { - const existing = await db - .select() - .from(rolePoints) - .where(eq(rolePoints.role_id, roleId)) - .limit(1); - - if (existing.length === 0) { - await db.insert(rolePoints).values({ role_id: roleId, points: pointsToAdd }); - } else { - const newPoints = existing[0].points + pointsToAdd; - await db - .update(rolePoints) - .set({ points: newPoints }) - .where(eq(rolePoints.role_id, roleId)); - } + await db.role_points.upsert({ + where: { role_points: roleId }, + create: { role_points: roleId, points: pointsToAdd }, + update: { points: { increment: pointsToAdd } } + }); }; // 10. Retirer des points à un rôle export const removePointsFromRole = async (roleId: number, pointsToRemove: number) => { - const existing = await db - .select() - .from(rolePoints) - .where(eq(rolePoints.role_id, roleId)) - .limit(1); - - if (existing.length === 0) { - throw new Error("Rôle introuvable"); - } + const existing = await db.role_points.findUnique({ where: { role_points: roleId } }); + if (!existing) throw new Error("Rôle introuvable"); - const newPoints = Math.max(0, existing[0].points - pointsToRemove); - await db - .update(rolePoints) - .set({ points: newPoints }) - .where(eq(rolePoints.role_id, roleId)); + const newPoints = Math.max(0, (existing.points ?? 0) - pointsToRemove); + await db.role_points.update({ where: { role_points: roleId }, data: { points: newPoints } }); }; // 11. Récupérer les points de tous les rôles export const getAllRolePoints = async () => { - return await db.select().from(rolePoints); + return await db.role_points.findMany(); }; // 12. Récupérer les points d'un rôle spécifique export const getRolePoints = async (roleId: number) => { - const result = await db - .select() - .from(rolePoints) - .where(eq(rolePoints.role_id, roleId)) - .limit(1); - - return result[0] ?? null; + const result = await db.role_points.findUnique({ where: { role_points: roleId } }); + return result ?? null; }; // 13. Checker le role d'User export const checkRoleUser = async (userId: number, roleId: number) => { - const existingRole = await db - .select() - .from(userRolesSchema) - .where(and(eq(userRolesSchema.user_id, userId), eq(userRolesSchema.role_id, roleId))); - - return existingRole; + return await db.user_roles.findMany({ where: { user_id: userId, role_id: roleId } }); }; -// 13. Ajouter un role à l'User +// 14. Ajouter un role à l'User export const insertUserRole = async (userId: number, roleId: number) => { - const newUserRole: UserRole = { user_id: userId, role_id: roleId } - await db.insert(userRolesSchema).values(newUserRole); -} + await db.user_roles.create({ data: { user_id: userId, role_id: roleId } }); +}; -// 13. Recevoir tout les roles +// 15. Recevoir tout les roles export const getRoles = async () => { try { - const roles = await db.select({ - roleId: roleSchema.id, - name: roleSchema.name, - description: roleSchema.description - }).from(roleSchema); - return roles; - } - catch (error) { + const roles = await db.roles.findMany({ + select: { id: true, name: true, description: true } + }); + return roles.map(r => ({ roleId: r.id, name: r.name, description: r.description })); + } catch (error) { throw new Error("Erreur lors de la récupération des rôles : " + error.message); } -} +}; diff --git a/backend/src/services/team.service.ts b/backend/src/services/team.service.ts index 1083479..6102e53 100644 --- a/backend/src/services/team.service.ts +++ b/backend/src/services/team.service.ts @@ -1,267 +1,194 @@ -import { eq } from "drizzle-orm"; -import { db } from "../database/db"; -import { factionSchema } from "../schemas/Basic/faction.schema"; -import { teamSchema } from "../schemas/Basic/team.schema"; -import { userSchema } from "../schemas/Basic/user.schema"; -import { teamFactionSchema } from "../schemas/Relational/teamfaction.schema"; -import { teamShotgunSchema } from "../schemas/Relational/teamshotgun.schema"; -import { userTeamsSchema } from "../schemas/Relational/userteams.schema"; +import { db } from "../prisma/db"; import { getFaction } from "./faction.service"; export const createTeam = async (teamName: string, members: number[]) => { - const newTeam = await db.insert(teamSchema).values({ name: teamName }).returning(); - const teamId = newTeam[0].id; + const newTeam = await db.teams.create({ data: { name: teamName } }); + const teamId = newTeam.id; - await Promise.all(members.map((userId) => - db.insert(userTeamsSchema).values({ team_id: teamId, user_id: userId }) - )); + await db.user_teams.createMany({ + data: members.map(userId => ({ user_id: userId, team_id: teamId })), + skipDuplicates: true, + }); - return newTeam; + return [newTeam]; }; export const createTeamLight = async (teamName: string, factionId: number) => { - const newTeam = await db.insert(teamSchema).values({ name: teamName }).returning({ teamId: teamSchema.id }); + const newTeam = await db.teams.create({ data: { name: teamName } }); if (factionId) { - await db.insert(teamFactionSchema).values({ faction_id: factionId, team_id: newTeam[0].teamId }); + await db.team_faction.create({ data: { faction_id: factionId, team_id: newTeam.id } }); } - return newTeam; + return [{ teamId: newTeam.id }]; }; export const getUserTeam = async (userId: number) => { - const userTeam = await db.select({ userTeamId: userTeamsSchema.team_id }).from(userTeamsSchema).where(eq(userTeamsSchema.user_id, userId)); - - return userTeam[0]?.userTeamId -} + const userTeam = await db.user_teams.findFirst({ + where: { user_id: userId }, + select: { team_id: true } + }); + return userTeam?.team_id; +}; export const getTeams = async () => { - const teams = await db.select( - { - teamId: teamSchema.id, - name: teamSchema.name, - description: teamSchema.description, - type: teamSchema.type - }).from(teamSchema); - - return teams -} + const teams = await db.teams.findMany({ + select: { id: true, name: true, description: true, type: true } + }); + return teams.map(t => ({ + teamId: t.id, + name: t.name, + description: t.description, + type: t.type, + })); +}; export const getTeamsAll = async () => { - const teams = await db.select().from(teamSchema); + const teams = await db.teams.findMany(); const teamsWithFaction = await Promise.all( teams.map(async (team) => { const teamFactionId = await getTeamFaction(team.id); const teamFaction = await getFaction(teamFactionId); - return { - ...team, - teamFaction - }; + return { ...team, teamFaction }; }) ); return teamsWithFaction; - -} +}; export const modifyTeam = async (teamID: number, teamMembers: number[], factionID: number, name?: string, type?: string) => { // 1. Mise à jour des champs de l'équipe - if (name !== undefined) { - await db - .update(teamSchema) - .set({ name: name }) - .where(eq(teamSchema.id, teamID)); - } - if (type !== undefined) { - await db - .update(teamSchema) - .set({ type: type }) - .where(eq(teamSchema.id, teamID)); + const updateData: Record = {}; + if (name !== undefined) updateData.name = name; + if (type !== undefined) updateData.type = type; + + if (Object.keys(updateData).length > 0) { + await db.teams.update({ where: { id: teamID }, data: updateData }); } // 2. Mise à jour des membres de l'équipe (remplace les anciens) if (teamMembers.length !== 0) { - if (Array.isArray(teamMembers)) { - // Supprimer les anciens membres - await db.delete(userTeamsSchema).where(eq(userTeamsSchema.team_id, teamID)); - - // Ajouter les nouveaux - if (teamMembers.length > 0) { - await db.insert(userTeamsSchema).values( - teamMembers.map((userID) => ({ - user_id: userID, - team_id: teamID, - })) - ); - } - } + await db.user_teams.deleteMany({ where: { team_id: teamID } }); + await db.user_teams.createMany({ + data: teamMembers.map(userID => ({ user_id: userID, team_id: teamID })), + skipDuplicates: true, + }); } else { - await db.delete(userTeamsSchema).where(eq(userTeamsSchema.team_id, teamID)); + await db.user_teams.deleteMany({ where: { team_id: teamID } }); } // 3. Mise à jour de la faction (remplace la relation précédente) if (factionID !== undefined) { - // Supprimer l'ancienne relation - await db.delete(teamFactionSchema).where(eq(teamFactionSchema.team_id, teamID)); - - // Ajouter la nouvelle - await db.insert(teamFactionSchema).values({ - team_id: teamID, - faction_id: factionID, - }); + await db.team_faction.deleteMany({ where: { team_id: teamID } }); + await db.team_faction.create({ data: { team_id: teamID, faction_id: factionID } }); } // 4. Retour de la team modifiée - const updatedTeam = await db - .select() - .from(teamSchema) - .where(eq(teamSchema.id, teamID)); - - return updatedTeam[0]; + return await db.teams.findUnique({ where: { id: teamID } }); }; export const getTeamUsers = async (teamId: any) => { - const users = await db - .select({ - userId: userSchema.id, - firstName: userSchema.first_name, - lastName: userSchema.last_name, - email: userSchema.email, - permission: userSchema.permission - }) - .from(userSchema) - .innerJoin(userTeamsSchema, eq(userSchema.id, userTeamsSchema.user_id)) - .where(eq(userTeamsSchema.team_id, teamId)); - - return users; + const users = await db.users.findMany({ + where: { user_teams: { some: { team_id: teamId } } }, + select: { + id: true, + first_name: true, + last_name: true, + email: true, + permission: true, + } + }); + return users.map(u => ({ + userId: u.id, + firstName: u.first_name, + lastName: u.last_name, + email: u.email, + permission: u.permission, + })); }; -export const getAllTeamsWithUsers = async () => { - const results = await db - .select({ - teamId: teamSchema.id, - teamName: teamSchema.name, - teamType: teamSchema.type, - teamFaction: factionSchema.name, - userId: userSchema.id, - firstName: userSchema.first_name, - lastName: userSchema.last_name, - discordId: userSchema.discord_id, - permission: userSchema.permission, - }) - .from(teamSchema) - .innerJoin(userTeamsSchema, eq(teamSchema.id, userTeamsSchema.team_id)) - .innerJoin(userSchema, eq(userSchema.id, userTeamsSchema.user_id)) - .innerJoin(teamFactionSchema, eq(teamSchema.id, teamFactionSchema.team_id)) - .innerJoin(factionSchema, eq(factionSchema.id, teamFactionSchema.faction_id)); - const teamsMap = new Map; - }>(); - - for (const row of results) { - if (!teamsMap.has(row.teamId)) { - teamsMap.set(row.teamId, { - id: row.teamId, - name: row.teamName, - type: row.teamType, - faction: row.teamFaction, - users: [], - }); +export const getAllTeamsWithUsers = async () => { + const teams = await db.teams.findMany({ + include: { + user_teams: { + include: { + users: { + select: { id: true, first_name: true, last_name: true, discord_id: true, permission: true } + } + } + }, + team_faction: { + include: { factions: { select: { name: true } } } + } } + }); - teamsMap.get(row.teamId)!.users.push({ - id: row.userId, - firstName: row.firstName, - lastName: row.lastName, - discordId: row.discordId, - permission: row.permission - }); - } - - return Array.from(teamsMap.values()); -} + return teams + .filter(t => t.user_teams.length > 0 && t.team_faction.length > 0) + .map(team => ({ + id: team.id, + name: team.name, + type: team.type, + faction: team.team_faction[0]?.factions.name ?? null, + users: team.user_teams.map(ut => ({ + id: ut.users.id, + firstName: ut.users.first_name, + lastName: ut.users.last_name, + discordId: ut.users.discord_id, + permission: ut.users.permission, + })) + })); +}; export const getTeamFaction = async (teamId: any) => { - const teamFactionId = await db - .select({ faction_id: teamFactionSchema.faction_id }) - .from(teamFactionSchema) - .where(eq(teamFactionSchema.team_id, teamId)); - - if (teamFactionId.length > 0 && teamFactionId[0].faction_id) { - return teamFactionId[0].faction_id; - } else { - return 0; - } + const tf = await db.team_faction.findFirst({ + where: { team_id: teamId }, + select: { faction_id: true } + }); + return tf?.faction_id ?? 0; }; export const deleteTeam = async (teamID: number) => { - // 1. Supprimer la relation avec les utilisateurs (table user_teams) - await db.delete(userTeamsSchema).where(eq(userTeamsSchema.team_id, teamID)); + const existingTeam = await db.teams.findUnique({ where: { id: teamID } }); + if (!existingTeam) throw new Error("Équipe non trouvée."); + // 1. Supprimer la relation avec les utilisateurs (table user_teams) + await db.user_teams.deleteMany({ where: { team_id: teamID } }); // 2. Supprimer la relation avec la faction (table team_faction) - await db.delete(teamFactionSchema).where(eq(teamFactionSchema.team_id, teamID)); - + await db.team_faction.deleteMany({ where: { team_id: teamID } }); // 3. Supprimer la relation avec les utilisateurs (table user_teams) - await db.delete(teamShotgunSchema).where(eq(teamShotgunSchema.team_id, teamID)); - + await db.team_shotgun.deleteMany({ where: { team_id: teamID } }); // 4. Supprimer l'équipe de la table principale (teams) - const deletedTeam = await db - .delete(teamSchema) - .where(eq(teamSchema.id, teamID)) - .returning(); - - // Si aucune équipe n'est supprimée, on retourne une erreur - if (deletedTeam.length === 0) { - throw new Error("Équipe non trouvée."); - } + await db.teams.delete({ where: { id: teamID } }); - return deletedTeam[0]; // Retourne les informations de l'équipe supprimée + return existingTeam; }; export const addTeamMember = async (teamId: number, userId: number) => { - const newTeamMember = await db.insert(userTeamsSchema).values({ user_id: userId, team_id: teamId, }); - - return newTeamMember; + return await db.user_teams.create({ data: { user_id: userId, team_id: teamId } }); }; export const getUsersWithTeam = async () => { try { - const userswithteam = await db.select( - { - userId: userTeamsSchema.user_id, - teamId: userTeamsSchema.team_id, - } - ).from(userTeamsSchema); - - return userswithteam; + const rows = await db.user_teams.findMany({ + select: { user_id: true, team_id: true } + }); + return rows.map(r => ({ userId: r.user_id, teamId: r.team_id })); } catch (err) { - console.error('Erreur lors de la récupération des utilisateurs possédant une team ', err); + console.error('Erreur lors de la récupération des utilisateurs possédant une team:', err); throw new Error('Erreur de base de données'); } }; export const getTeam = async (teamId: any) => { try { - const team = await db.select( - { - teamId: teamSchema.id, - teamName: teamSchema.name - } - ).from(teamSchema).where(eq(teamSchema.id, teamId)); - - return team[0]; + const team = await db.teams.findUnique({ + where: { id: teamId }, + select: { id: true, name: true } + }); + return team ? { teamId: team.id, teamName: team.name } : undefined; } catch (err) { - console.error('Erreur lors de la récupération des utilisateurs possédant une team ', err); + console.error('Erreur lors de la récupération de la team:', err); throw new Error('Erreur de base de données'); } }; diff --git a/backend/src/services/tent.service.ts b/backend/src/services/tent.service.ts index ed9b32b..0700c8c 100644 --- a/backend/src/services/tent.service.ts +++ b/backend/src/services/tent.service.ts @@ -1,8 +1,4 @@ -import { and, eq, or } from "drizzle-orm"; -import { alias } from "drizzle-orm/pg-core"; -import { db } from "../database/db"; -import { userSchema } from "../schemas/Basic/user.schema"; -import { userTentSchema } from "../schemas/Relational/usertent.schema"; +import { db } from "../prisma/db"; /** * Créer une réservation de tente entre 2 utilisateurs. @@ -13,72 +9,79 @@ export const createTent = async (userId1: number, userId2: number) => { } // Vérifier si l'un des deux a déjà une tente - const existing = await db - .select() - .from(userTentSchema) - .where( - or( - eq(userTentSchema.user_id_1, userId1), - eq(userTentSchema.user_id_2, userId1), - eq(userTentSchema.user_id_1, userId2), - eq(userTentSchema.user_id_2, userId2) - ) - ); + const existing = await db.user_tent.findMany({ + where: { + OR: [ + { user_id_1: userId1 }, + { user_id_2: userId1 }, + { user_id_1: userId2 }, + { user_id_2: userId2 }, + ] + } + }); if (existing.length > 0) { throw new Error("Un des utilisateurs a déjà une tente."); } - return await db.insert(userTentSchema).values({ - user_id_1: userId1, - user_id_2: userId2, - }); + return await db.user_tent.create({ data: { user_id_1: userId1, user_id_2: userId2 } }); }; /** * Annuler une tente (par l'un ou l'autre des utilisateurs). */ export const cancelTent = async (userId1: number) => { - return await db - .delete(userTentSchema) - .where( - or(eq(userTentSchema.user_id_1, userId1), eq(userTentSchema.user_id_2, userId1)), - ); + return await db.user_tent.deleteMany({ + where: { + OR: [ + { user_id_1: userId1 }, + { user_id_2: userId1 } + ] + } + }); }; /** * Récupérer la tente d'un utilisateur. */ export const getTentByUser = async (userId: number) => { - return await db - .select() - .from(userTentSchema) - .where(or(eq(userTentSchema.user_id_1, userId), eq(userTentSchema.user_id_2, userId))); + return await db.user_tent.findMany({ + where: { + OR: [ + { user_id_1: userId }, + { user_id_2: userId } + ] + } + }); }; /** * Récupérer toutes les tentes (avec infos des 2 utilisateurs). */ export const getAllTents = async () => { - const user2 = alias(userSchema, "user2"); - - return await db - .select({ - user1_id: userTentSchema.user_id_1, - user2_id: userTentSchema.user_id_2, - user1_first_name: userSchema.first_name, - user1_last_name: userSchema.last_name, - user1_email: userSchema.email, - user1_majeur: userSchema.majeur, - user2_first_name: user2.first_name, - user2_last_name: user2.last_name, - user2_email: user2.email, - user2_majeur: user2.majeur, - confirmed: userTentSchema.confirmed - }) - .from(userTentSchema) - .innerJoin(userSchema, eq(userTentSchema.user_id_1, userSchema.id)) - .innerJoin(user2, eq(userTentSchema.user_id_2, user2.id)); + const tents = await db.user_tent.findMany({ + include: { + users_user_tent_user_id_1Tousers: { + select: { first_name: true, last_name: true, email: true, majeur: true } + }, + users_user_tent_user_id_2Tousers: { + select: { first_name: true, last_name: true, email: true, majeur: true } + }, + } + }); + return tents.map(t => ({ + user1_id: t.user_id_1, + user2_id: t.user_id_2, + user1_first_name: t.users_user_tent_user_id_1Tousers.first_name, + user1_last_name: t.users_user_tent_user_id_1Tousers.last_name, + user1_email: t.users_user_tent_user_id_1Tousers.email, + user1_majeur: t.users_user_tent_user_id_1Tousers.majeur, + user2_first_name: t.users_user_tent_user_id_2Tousers.first_name, + user2_last_name: t.users_user_tent_user_id_2Tousers.last_name, + user2_email: t.users_user_tent_user_id_2Tousers.email, + user2_majeur: t.users_user_tent_user_id_2Tousers.majeur, + confirmed: t.confirmed, + })); }; /** @@ -94,42 +97,28 @@ export const toggleTentConfirmation = async ( } // Vérifier si la tente existe - const existingTent = await db - .select() - .from(userTentSchema) - .where( - or( - and( - eq(userTentSchema.user_id_1, userId1), - eq(userTentSchema.user_id_2, userId2) - ), - and( - eq(userTentSchema.user_id_1, userId2), - eq(userTentSchema.user_id_2, userId1) - ) - ) - ); + const existingTent = await db.user_tent.findFirst({ + where: { + OR: [ + { user_id_1: userId1, user_id_2: userId2 }, + { user_id_1: userId2, user_id_2: userId1 }, + ] + } + }); - if (existingTent.length === 0) { + if (!existingTent) { throw new Error("La tente entre ces deux utilisateurs n'existe pas."); } - // Mettre à jour la confirmation - await db - .update(userTentSchema) - .set({ confirmed }) - .where( - or( - and( - eq(userTentSchema.user_id_1, userId1), - eq(userTentSchema.user_id_2, userId2) - ), - and( - eq(userTentSchema.user_id_1, userId2), - eq(userTentSchema.user_id_2, userId1) - ) - ) - ); + await db.user_tent.updateMany({ + where: { + OR: [ + { user_id_1: userId1, user_id_2: userId2 }, + { user_id_1: userId2, user_id_2: userId1 }, + ] + }, + data: { confirmed } + }); return { success: true, message: confirmed ? "Tente validée." : "Tente dévalidée." }; }; diff --git a/backend/src/services/user.service.ts b/backend/src/services/user.service.ts index c457be9..c556c2f 100644 --- a/backend/src/services/user.service.ts +++ b/backend/src/services/user.service.ts @@ -1,8 +1,5 @@ import bcrypt from 'bcryptjs'; -import { eq } from 'drizzle-orm'; -import { db } from '../database/db'; // Import de la connexion PostgreSQL -import { type User, userSchema } from '../schemas/Basic/user.schema'; -import { registrationSchema } from '../schemas/Relational/registration.schema'; +import { db } from '../prisma/db'; import { getFaction } from './faction.service'; import { getUserRoles } from './role.service'; import { getTeam, getTeamFaction, getUserTeam } from './team.service'; @@ -10,32 +7,45 @@ import { getTeam, getTeamFaction, getUserTeam } from './team.service'; // Fonction pour récupérer un utilisateur par email export const getUserByEmail = async (email: string) => { try { - const users = await db.select().from(userSchema).where(eq(userSchema.email, email)); - return users[0]; + const user = await db.users.findFirst({ where: { email } }); + return user ?? undefined; } catch (err) { console.error('Erreur lors de la récupération de l\'utilisateur par email:', err); throw new Error('Erreur de base de données'); } }; +// Fonction pour récupérer un utilisateur par ID export const getUserById = async (userId: number) => { try { - const user = await db.select( - { - userId: userSchema.id, - firstName: userSchema.first_name, - lastName: userSchema.last_name, - email: userSchema.email, - majeur: userSchema.majeur, - branch: userSchema.branch, - contact: userSchema.contact, - permission: userSchema.permission, - discord_id: userSchema.discord_id + const user = await db.users.findUnique({ + where: { id: userId }, + select: { + id: true, + first_name: true, + last_name: true, + email: true, + majeur: true, + branch: true, + contact: true, + permission: true, + discord_id: true, } - ).from(userSchema).where(eq(userSchema.id, userId)); - return user[0]; + }); + if (!user) return undefined; + return { + userId: user.id, + firstName: user.first_name, + lastName: user.last_name, + email: user.email, + majeur: user.majeur, + branch: user.branch, + contact: user.contact, + permission: user.permission, + discord_id: user.discord_id, + }; } catch (err) { - console.error('Erreur lors de la récupération de l\'utilisateur par email:', err); + console.error('Erreur lors de la récupération de l\'utilisateur par id:', err); throw new Error('Erreur de base de données'); } }; @@ -48,24 +58,22 @@ export const createUser = async ( majeur: boolean, permission: string, branch: string, - password: string) => { + password: string +) => { try { - // Hacher le mot de passe const hashedPassword = await bcrypt.hash(password, 10); - - const newUser: Partial = { - first_name: firstName, - last_name: lastName, - email: email, - branch: branch === "CV_ING" ? "RI" : branch, - majeur: majeur, - password: hashedPassword, - permission: permission - }; - // Insérer un nouvel utilisateur dans la base de données - const result = await db.insert(userSchema).values(newUser).returning() - - return result[0]; + const newUser = await db.users.create({ + data: { + first_name: firstName, + last_name: lastName, + email: email, + branch: branch === "CV_ING" ? "RI" : branch, + majeur: majeur, + password: hashedPassword, + permission: permission, + } + }); + return newUser; } catch (err) { console.error('Erreur lors de la création de l\'utilisateur:', err); throw new Error('Erreur de base de données'); @@ -79,86 +87,98 @@ export const comparePassword = async (enteredPassword: string, storedPassword: s export const updateUserStudent = async (firstName: string, lastName: string, email: string) => { try { - const result = await db.update(userSchema) - .set({ - first_name: firstName, - last_name: lastName - }) - .where(eq(userSchema.email, email)); - - return result.rows[0]; + await db.users.updateMany({ + where: { email }, + data: { first_name: firstName, last_name: lastName } + }); } catch (err) { - console.error('Erreur lors de la récupération et de l\'update de l\'utilisateur par email:', err); + console.error('Erreur lors de l\'update de l\'utilisateur par email:', err); throw new Error('Erreur de base de données'); } -} +}; export const getUsersAdmin = async () => { try { - const users = await db.select( - { - userId: userSchema.id, - firstName: userSchema.first_name, - lastName: userSchema.last_name, - email: userSchema.email, - majeur: userSchema.majeur, - branch: userSchema.branch, - contact: userSchema.contact, - permission: userSchema.permission, - discord_id: userSchema.discord_id + const users = await db.users.findMany({ + select: { + id: true, + first_name: true, + last_name: true, + email: true, + majeur: true, + branch: true, + contact: true, + permission: true, + discord_id: true, } - ).from(userSchema); - return users; + }); + return users.map(u => ({ + userId: u.id, + firstName: u.first_name, + lastName: u.last_name, + email: u.email, + majeur: u.majeur, + branch: u.branch, + contact: u.contact, + permission: u.permission, + discord_id: u.discord_id, + })); } catch (err) { - console.error('Erreur lors de la récupération des utilisateurs ', err); + console.error('Erreur lors de la récupération des utilisateurs:', err); throw new Error('Erreur de base de données'); } }; export const getUsers = async () => { try { - const users = await db.select( - { - userId: userSchema.id, - firstName: userSchema.first_name, - lastName: userSchema.last_name, - permission: userSchema.permission, - email: userSchema.email + const users = await db.users.findMany({ + select: { + id: true, + first_name: true, + last_name: true, + permission: true, + email: true, } - ).from(userSchema); - return users; + }); + return users.map(u => ({ + userId: u.id, + firstName: u.first_name, + lastName: u.last_name, + permission: u.permission, + email: u.email, + })); } catch (err) { - console.error('Erreur lors de la récupération des utilisateurs ', err); + console.error('Erreur lors de la récupération des utilisateurs:', err); throw new Error('Erreur de base de données'); } }; export const getUsersAll = async () => { try { - const users = await db.select().from(userSchema); + const users = await db.users.findMany(); const userWithTeam = await Promise.all( users.map(async (user) => { const roles = await getUserRoles(user.id); let teamId = await getUserTeam(user.id); - teamId = teamId ?? null; + const resolvedTeamId = teamId ?? null; let teamName: string | null = null; let factionId: number | null = null; let factionName: string | null = null; - if (teamId) { - const team = await getTeam(teamId); + if (resolvedTeamId) { + const team = await getTeam(resolvedTeamId); teamName = team?.teamName ?? null; - factionId = await getTeamFaction(teamId); + factionId = await getTeamFaction(resolvedTeamId); const faction = await getFaction(factionId); factionName = faction?.name ?? null; } return { ...user, - teamId, + teamId: resolvedTeamId, teamName, factionId, factionName, @@ -167,46 +187,46 @@ export const getUsersAll = async () => { }) ); - return userWithTeam; } catch (err) { - console.error('Erreur lors de la récupération des utilisateurs ', err); + console.error('Erreur lors de la récupération des utilisateurs:', err); throw new Error('Erreur de base de données'); } }; export const getUsersbyPermission = async (permission: string) => { try { - const users = await db.select( - { - userId: userSchema.id, - firstName: userSchema.first_name, - lastName: userSchema.last_name, - email: userSchema.email, - branch: userSchema.branch + const users = await db.users.findMany({ + where: { permission }, + select: { + id: true, + first_name: true, + last_name: true, + email: true, + branch: true, } - ).from(userSchema).where(eq(userSchema.permission, permission)); - return users; + }); + return users.map(u => ({ + userId: u.id, + firstName: u.first_name, + lastName: u.last_name, + email: u.email, + branch: u.branch, + })); } catch (err) { - console.error('Erreur lors de la récupération des utilisateurs ', err); + console.error('Erreur lors de la récupération des utilisateurs:', err); throw new Error('Erreur de base de données'); } }; export const updateUserPassword = async (userId: number, password: string) => { try { - const result = await db.update(userSchema) - .set({ - password: password - }) - .where(eq(userSchema.id, userId)); - - return result.rows[0]; + await db.users.update({ where: { id: userId }, data: { password } }); } catch (err) { - console.error('Erreur lors de la récupération et de l\'update de l\'utilisateur par email:', err); + console.error('Erreur lors de l\'update du mot de passe:', err); throw new Error('Erreur de base de données'); } -} +}; export const updateUserInfoByUserId = async ( userId: number, @@ -214,14 +234,7 @@ export const updateUserInfoByUserId = async ( contact?: string ) => { try { - const result = await db.update(userSchema) - .set({ - branch: branch, - contact: contact - }) - .where(eq(userSchema.id, userId)); - - return result; + await db.users.update({ where: { id: userId }, data: { branch, contact } }); } catch (err) { console.error('Erreur lors de la mise à jour des infos utilisateur:', err); throw new Error('Erreur de base de données'); @@ -230,21 +243,13 @@ export const updateUserInfoByUserId = async ( export const updateUserByAdmin = async ( userId: number, - updates: Partial + updates: Record ) => { try { - if (Object.keys(updates).length === 0) { throw new Error('Aucune donnée à mettre à jour'); } - - const result = await db.update(userSchema) - .set( - updates - ) - .where(eq(userSchema.id, userId)); - - return result; + await db.users.update({ where: { id: userId }, data: updates }); } catch (err) { console.error('Erreur lors de la mise à jour par l\'admin:', err); throw new Error('Erreur de base de données'); @@ -253,15 +258,8 @@ export const updateUserByAdmin = async ( export const deleteUserById = async (userId: number) => { try { - - const user_registration_token = await db.select({ user_id: registrationSchema.user_id }).from(registrationSchema).where(eq(registrationSchema.user_id, userId)); - - if (user_registration_token.length > 0) { - await db.delete(registrationSchema).where(eq(registrationSchema.user_id, userId)); - } - - const result = await db.delete(userSchema).where(eq(userSchema.id, userId)); - return result; + await db.registration_tokens.deleteMany({ where: { user_id: userId } }); + await db.users.delete({ where: { id: userId } }); } catch (err) { console.error('Erreur lors de la suppression de l\'utilisateur:', err); throw new Error('Erreur de base de données'); From daacfea4730d27c788e397d95763cd79366863ff Mon Sep 17 00:00:00 2001 From: Eylexander Date: Mon, 6 Jul 2026 14:52:45 +0200 Subject: [PATCH 3/4] refactor(orm): types + schema loader changed --- backend/prisma/schema.prisma | 6 ++++-- backend/src/controllers/faction.controller.ts | 2 +- backend/src/controllers/team.controller.ts | 4 ++-- backend/src/services/faction.service.ts | 2 +- backend/src/services/team.service.ts | 6 +++--- 5 files changed, 11 insertions(+), 9 deletions(-) diff --git a/backend/prisma/schema.prisma b/backend/prisma/schema.prisma index e1008ff..e180c23 100644 --- a/backend/prisma/schema.prisma +++ b/backend/prisma/schema.prisma @@ -1,6 +1,8 @@ generator client { - provider = "prisma-client" - output = "../src/prisma/build" + provider = "prisma-client" + output = "../src/prisma/build" + moduleFormat = "cjs" + importFileExtension = "" } datasource db { diff --git a/backend/src/controllers/faction.controller.ts b/backend/src/controllers/faction.controller.ts index 312c72e..1289d67 100644 --- a/backend/src/controllers/faction.controller.ts +++ b/backend/src/controllers/faction.controller.ts @@ -16,7 +16,7 @@ export const getFaction = async (req: Request, res: Response) => { const { factionId } = req.query; try { - const faction = await faction_service.getFaction(factionId); + const faction = await faction_service.getFaction(Number(factionId)); Ok(res, { data: faction }); return; } catch { diff --git a/backend/src/controllers/team.controller.ts b/backend/src/controllers/team.controller.ts index b679821..5617b83 100644 --- a/backend/src/controllers/team.controller.ts +++ b/backend/src/controllers/team.controller.ts @@ -97,7 +97,7 @@ export const getTeamUsers = async (req: Request, res: Response) => { const { teamId } = req.query; try { - const teamUsers = await team_service.getTeamUsers(teamId); + const teamUsers = await team_service.getTeamUsers(Number(teamId)); Ok(res, { data: teamUsers }); return; } catch (error) { @@ -123,7 +123,7 @@ export const getTeamFaction = async (req: Request, res: Response) => { const { teamId } = req.query; try { - const factionId = await team_service.getTeamFaction(teamId); + const factionId = await team_service.getTeamFaction(Number(teamId)); const teamFaction = await faction_service.getFaction(factionId); Ok(res, { data: teamFaction }); return; diff --git a/backend/src/services/faction.service.ts b/backend/src/services/faction.service.ts index b03185d..be06d82 100644 --- a/backend/src/services/faction.service.ts +++ b/backend/src/services/faction.service.ts @@ -7,7 +7,7 @@ export const getFactions = async () => { return factions.map(f => ({ factionId: f.id, name: f.name, description: f.description })); }; -export const getFaction = async (factionId: any) => { +export const getFaction = async (factionId: number) => { const faction = await db.factions.findUnique({ where: { id: factionId }, select: { id: true, name: true, description: true } diff --git a/backend/src/services/team.service.ts b/backend/src/services/team.service.ts index 6102e53..dd53bb8 100644 --- a/backend/src/services/team.service.ts +++ b/backend/src/services/team.service.ts @@ -87,7 +87,7 @@ export const modifyTeam = async (teamID: number, teamMembers: number[], factionI return await db.teams.findUnique({ where: { id: teamID } }); }; -export const getTeamUsers = async (teamId: any) => { +export const getTeamUsers = async (teamId: number) => { const users = await db.users.findMany({ where: { user_teams: { some: { team_id: teamId } } }, select: { @@ -140,7 +140,7 @@ export const getAllTeamsWithUsers = async () => { })); }; -export const getTeamFaction = async (teamId: any) => { +export const getTeamFaction = async (teamId: number) => { const tf = await db.team_faction.findFirst({ where: { team_id: teamId }, select: { faction_id: true } @@ -180,7 +180,7 @@ export const getUsersWithTeam = async () => { } }; -export const getTeam = async (teamId: any) => { +export const getTeam = async (teamId: number) => { try { const team = await db.teams.findUnique({ where: { id: teamId }, From 9d360d15e1f08c2dc4d282c455982ebea0a3cf2e Mon Sep 17 00:00:00 2001 From: Arthur Dodin Date: Tue, 7 Jul 2026 02:46:24 +0200 Subject: [PATCH 4/4] lint(prisma): Prettier config --- backend/.prettierrc | 10 ++++++++++ frontend/.prettierrc | 10 ++++++++++ 2 files changed, 20 insertions(+) create mode 100644 backend/.prettierrc create mode 100644 frontend/.prettierrc diff --git a/backend/.prettierrc b/backend/.prettierrc new file mode 100644 index 0000000..b7db128 --- /dev/null +++ b/backend/.prettierrc @@ -0,0 +1,10 @@ +{ + "semi": true, + "trailingComma": "all", + "singleQuote": true, + "printWidth": 120, + "tabWidth": 4, + "arrowParens": "always", + "endOfLine": "lf", + "bracketSameLine": true +} diff --git a/frontend/.prettierrc b/frontend/.prettierrc new file mode 100644 index 0000000..b7db128 --- /dev/null +++ b/frontend/.prettierrc @@ -0,0 +1,10 @@ +{ + "semi": true, + "trailingComma": "all", + "singleQuote": true, + "printWidth": 120, + "tabWidth": 4, + "arrowParens": "always", + "endOfLine": "lf", + "bracketSameLine": true +}