diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 1c8ee0a..6ce061b 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -20,37 +20,51 @@ jobs: - name: Checkout repository uses: actions/checkout@v5 + - name: Setup pnpm + uses: pnpm/action-setup@v4 + with: + version: 11 + - name: Set up Node.js uses: actions/setup-node@v5 with: - node-version: '24' + node-version: '23' + cache: 'pnpm' + cache-dependency-path: ./backend/pnpm-lock.yaml - name: Install dependencies working-directory: ./backend - run: npm ci + run: pnpm install --frozen-lockfile - name: Lint working-directory: ./backend - run: npm run lint + run: pnpm run lint lint-front: runs-on: self-hosted steps: - name: Checkout repository uses: actions/checkout@v5 + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + with: + version: 11 - name: Set up Node.js uses: actions/setup-node@v5 with: - node-version: '24' + node-version: '23' + cache: 'pnpm' + cache-dependency-path: ./frontend/pnpm-lock.yaml - name: Install dependencies working-directory: ./frontend - run: npm ci + run: pnpm install --frozen-lockfile - name: Lint working-directory: ./frontend - run: npm run lint + run: pnpm run lint build-api: runs-on: self-hosted diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 0000000..74ae573 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,2 @@ +pnpm -C frontend lint +pnpm -C backend lint diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..b7db128 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,10 @@ +{ + "semi": true, + "trailingComma": "all", + "singleQuote": true, + "printWidth": 120, + "tabWidth": 4, + "arrowParens": "always", + "endOfLine": "lf", + "bracketSameLine": true +} diff --git a/README.md b/README.md index 2211397..98020dc 100644 --- a/README.md +++ b/README.md @@ -7,13 +7,13 @@ Ce projet est une plateforme pour le site d'intégration de l'Université de Tec - **Frontend** : - Vite.js - React - - Serve (pour servir le build statique) + - Nginx (pour servir le build statique) - Docker - **Backend** : - Node.js - Express.js - - Drizzle ORM + - Prisma - PostgreSQL client - Docker @@ -26,58 +26,140 @@ Ce projet est une plateforme pour le site d'intégration de l'Université de Tec --- -## 🚀 Lancer le projet en local +## Prérequis + +### Cloner le projet -### 1. Prérequis +```bash +git clone https://github.com/ungdev/Integration.git +cd Integration +``` + +### Docker & Docker-Compose Assurez-vous d'avoir installé : + - [Docker](https://www.docker.com/) - [Docker Compose](https://docs.docker.com/compose/) Vérifiez avec : + ```bash docker --version docker-compose --version ``` -### 2. Cloner le projet +### ESLint & Prettier -```bash -git clone https://github.com/ungdev/Integration.git -cd Integration +ESLint & Prettier sont utilisés pour maintenanir une cohérence et propreté de syntaxe dans le code de ce projet ainsi que la lisibilité. + +Il convient donc d'installation les deux extensions sur votre éditeur de code. + +Vérification de syntaxe via la commande + +``` +pnpm lint ``` -### 3. Démarrer les conteneurs +La syntaxe doit être controllée avant chaque commit. -Exécutez la commande suivante à la racine du projet : +### Variables d'environnement -```bash -docker-compose up --build +#### Frontend + +Dans [le dossier frontend](/frontend) + +``` +cp .env.example .env ``` -Cette commande : -- Lance PostgreSQL -- Démarre le backend avec migration automatique via Drizzle ORM -- Compile et sert le frontend +Renseigner les différents champs utiles. + +#### API + +Dans [le dossier backend](/backend) + +``` +cp .env.example .env +``` + +Renseigner les différents champs utiles. + +## 🚀 Lancer le projet en local -### 4. Accéder aux services +### Database + +#### Via Docker-Compose + +A la racine du projet + +``` +docker compose up db +``` + +#### Database externe + +Passage par exemple par `systemctl` + +``` +sudo systemctl start postgresql +``` + +### Frontend + +Dans [le dossier frontend](/frontend) + +Installation des dépendances + +``` +pnpm i +``` + +Pour lancer le serveur + +``` +pnpm run dev +``` + +### API + +Dans [le dossier backend](/backend/) + +Installation des dépendances & Migration DB + +``` +pnpm i +pnpm migrate +``` + +Pour lancer le serveur + +``` +pnpm run dev +``` + +### Accéder aux services - Frontend : [http://localhost:4000](http://localhost:4000) - Backend : [http://localhost:4001](http://localhost:4001) - Base de données PostgreSQL : `localhost:5432` (user: `admin`, password: `password`) ---- - -## 🧪 Gestion des migrations +## Test de build -Le backend utilise **Drizzle ORM** pour la gestion des migrations. +### Via Docker -La commande suivante est exécutée automatiquement au démarrage : +Exécutez la commande suivante à la racine du projet : ```bash -npm run migrate +docker-compose up --build ``` +Cette commande : + +- Lance PostgreSQL +- Démarre le backend avec migration automatique via Prisma +- Compile et sert le frontend + --- ## 📁 Structure du projet @@ -97,8 +179,7 @@ npm run migrate --- - -# 🔧 Installation du site de l'Intégration en local +# 🔧 Routage `/api` sur integration.utt.fr en local Ce guide permet de configurer le site `integration.utt.fr` en local avec Nginx, en HTTPS, pour simuler l'environnement de production. @@ -138,6 +219,7 @@ mkcert integration.utt.fr ``` Cela crée deux fichiers : + - `integration.utt.fr.pem` - `integration.utt.fr-key.pem` @@ -177,30 +259,13 @@ sudo systemctl reload nginx ### 4. Lancer les services -Dans deux terminaux différents : +[Lancer l'API](#api) puis [lancer le Frontend](#frontend) -```bash -cd frontend -npm install -npm run dev -``` - -```bash -cd backend -npm install -npm run dev -``` +Le projet de dev est maintenant accessible sur https://integration.utt.fr, en local. --- -### ✅ Accès - -Ouvrir : -👉 https://integration.utt.fr - ---- - -## 🔚 Nettoyage quand le dev est terminé +## Nettoyage ### 1. Supprimer l'entrée dans `/etc/hosts` @@ -226,10 +291,8 @@ sudo systemctl reload nginx --- -Tu peux maintenant accéder à la version en ligne de : -👉 https://integration.utt.fr - +La site de production est maintenant accessible sur https://integration.utt.fr, en ligne. ## 📜 Licence -Projet destiné à des fins d'utilisation pour l'intégration des étudiants à l'UTT. +Projet destiné à des fins d'utilisation pour l'intégration des nouveaux étudiants à l'Université de Technologie de Troyes. diff --git a/backend/.env.example b/backend/.env.example index 751c426..6c42422 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -10,6 +10,7 @@ EMAIL_HOST= EMAIL_USER= EMAIL_PASSWORD= EMAIL_FROM= +EMAIL_CONCURRENCY=20 CAS_VALIDATE_URL= CAS_LOGIN_URL= diff --git a/backend/package.json b/backend/package.json index f740022..bc5808b 100644 --- a/backend/package.json +++ b/backend/package.json @@ -43,6 +43,7 @@ "jsonwebtoken": "^9.0.2", "multer": "^2.1.1", "nodemailer": "^9.0.1", + "p-limit": "^7.3.0", "papaparse": "^5.5.2", "pg": "^8.14.1", "pg-promise": "^12.6.2", diff --git a/backend/pnpm-lock.yaml b/backend/pnpm-lock.yaml index 0e5e686..9c971a9 100644 --- a/backend/pnpm-lock.yaml +++ b/backend/pnpm-lock.yaml @@ -71,6 +71,9 @@ importers: nodemailer: specifier: ^9.0.1 version: 9.0.3 + p-limit: + specifier: ^7.3.0 + version: 7.3.0 papaparse: specifier: ^5.5.2 version: 5.5.4 @@ -2127,6 +2130,10 @@ packages: resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} engines: {node: '>=10'} + p-limit@7.3.0: + resolution: {integrity: sha512-7cIXg/Z0M5WZRblrsOla88S4wAK+zOQQWeBYfV3qJuJXMr+LnbYjaadrFaS0JILfEDPVqHyKnZ1Z/1d6J9VVUw==} + engines: {node: '>=20'} + p-locate@5.0.0: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} engines: {node: '>=10'} @@ -2757,6 +2764,10 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} + yocto-queue@1.2.2: + resolution: {integrity: sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==} + engines: {node: '>=12.20'} + snapshots: '@asamuzakjp/css-color@3.2.0': @@ -4771,6 +4782,10 @@ snapshots: dependencies: yocto-queue: 0.1.0 + p-limit@7.3.0: + dependencies: + yocto-queue: 1.2.2 + p-locate@5.0.0: dependencies: p-limit: 3.1.0 @@ -5454,3 +5469,5 @@ snapshots: yn@3.1.1: {} yocto-queue@0.1.0: {} + + yocto-queue@1.2.2: {} diff --git a/backend/src/controllers/discord.controller.ts b/backend/src/controllers/discord.controller.ts index 2524e06..225acaf 100644 --- a/backend/src/controllers/discord.controller.ts +++ b/backend/src/controllers/discord.controller.ts @@ -1,5 +1,5 @@ import * as discord_service from '../services/discord.service'; -import { Error, Ok } from '../utils/responses'; +import { Error, Ok, ServiceUnavailable } from '../utils/responses'; import type { AppRequestHandler } from '../types/http'; import type { DiscordBody } from '../dto/discord.dto'; @@ -7,6 +7,8 @@ export const createChallenge: AppRequestHandler = async (req, res) const { code } = req.body; const userId = req.user?.userId; + return ServiceUnavailable(res, { msg: 'Synchronisation Discord désactivée.' }); + if (!code) { Error(res, { msg: "Code manquant dans l'URL" }); return; @@ -19,6 +21,6 @@ export const createChallenge: AppRequestHandler = async (req, res) }); } catch (err) { console.error('Erreur dans handleDiscordCallback:', err); - Error(res, { msg: 'Erreur pendant la liaison avec Discord' }); + Error(res, { msg: 'Erreur pendant la liaison avec Discord.' }); } }; diff --git a/backend/src/controllers/team.controller.ts b/backend/src/controllers/team.controller.ts index 85a357e..302b67f 100644 --- a/backend/src/controllers/team.controller.ts +++ b/backend/src/controllers/team.controller.ts @@ -2,19 +2,9 @@ import { type Event } from '../schemas/Basic/event.schema'; import * as event_service from '../services/event.service'; import * as faction_service from '../services/faction.service'; import * as team_service from '../services/team.service'; -import * as user_service from '../services/user.service'; import { Error, Ok } from '../utils/responses'; import type { AppRequestHandler } from '../types/http'; -import type { - CreateTeamBody, - CreateTeamLightBody, - ModifyTeamBody, - StudentRow, - TeamMemberRow, - TeamQuery, - TeamRow, - TeamSizeRow, -} from '../dto/team.dto'; +import type { CreateTeamBody, CreateTeamLightBody, ModifyTeamBody, TeamQuery } from '../dto/team.dto'; export const createNewTeam: AppRequestHandler = async (req, res) => { const { teamName, members } = req.body; @@ -70,13 +60,23 @@ export const createNewTeamLight: AppRequestHandler = async export const getTeams: AppRequestHandler = async (_req, res) => { try { const teams = await team_service.getTeams(); - Ok(res, { data: teams }); + Ok(res, { data: teams ?? {} }); return; } catch { Error(res, { msg: 'Erreur lors de la récupération des équipes.' }); } }; +export const getUserTeam: AppRequestHandler = async (req, res) => { + try { + const team = await team_service.getUserTeamDisplayInfos(req.user?.userId); + Ok(res, { data: team }); + return; + } catch { + Error(res, { msg: "Erreur lors de la récupération de l'équipe." }); + } +}; + export const getTeamsWithfactions: AppRequestHandler = async (_req, res) => { try { const teams = await team_service.getTeamsAll(); @@ -89,13 +89,13 @@ export const getTeamsWithfactions: AppRequestHandler = async (_req, res) => { export const modifyTeam: AppRequestHandler = async (req, res) => { try { - const { teamID, teamName, teamMembers, factionID, type } = req.body; + const { teamID, teamName, teamMembers, factionID, socialLink, type } = req.body; if (!teamID) { Error(res, { msg: 'teamID est requis pour la mise à jour.' }); } - const updatedTeam = await team_service.modifyTeam(teamID, teamMembers, factionID, teamName, type); + const updatedTeam = await team_service.modifyTeam(teamID, teamMembers, factionID, socialLink, teamName, type); Ok(res, { data: updatedTeam }); } catch (error) { console.error(error); @@ -162,93 +162,7 @@ export const deleteTeam: AppRequestHandler = async (req, res export const teamDistribution: AppRequestHandler = async (_req, res) => { try { - const newStudents = (await user_service.getUsersbyPermission('Nouveau')) as StudentRow[]; - const userswithteams = ((await team_service.getUsersWithTeam()) as TeamMemberRow[]).map( - (entry) => entry.userId, - ); - const teams = (await team_service.getTeams()) as TeamRow[]; - - // Filtrer les étudiants qui ne sont pas déjà assignés à une équipe - const filteredStudents = newStudents - // .filter((student: StudentRow) => student.branch !== "RI") // A decommenter pour ignorer les RI dans la répartition automatique - .filter((student) => !userswithteams.includes(student.userId)); - - // Filtrer les utilisateurs en fonction de la spécialité - const tcStudents = filteredStudents - .filter((student) => student.branch === 'TC') - .map((student) => ({ - id: student.userId, - email: student.email, - branch: student.branch, - })); - - const otherStudents = filteredStudents - // .filter((student: StudentRow) => student.branch !== "TC" && student.branch !== "RI" && student.branch !== "MM") A decommenter pour ignorer les RI dans la répartition automatique - .filter((student) => student.branch !== 'TC' && student.branch !== 'MM') - .map((student) => ({ - id: student.userId, - email: student.email, - branch: student.branch, - })); - - const PMOMStudents = filteredStudents - .filter((student) => student.branch == 'MM') - .map((student) => ({ - id: student.userId, - email: student.email, - branch: student.branch, - })); - - // Filtrer les équipes en fonction de leur type - const tcTeams = teams.filter((team) => team.type === 'TC'); - const PMOMTeams = teams.filter((team) => team.type === 'MM'); - // const otherTeams = teams.filter(team => team.type !== "TC" && team.type !== "RI" && team.type !== "MM"); A decommenter pour ignorer les RI dans la répartition automatique - const otherTeams = teams.filter((team) => team.type !== 'TC' && team.type !== 'MM'); - - // Fonction pour assigner les utilisateurs à des équipes équilibrées - async function assignUsersToTeams(users: Array<{ id: number }>, teams: TeamRow[]) { - // Calculer la taille actuelle des équipes - const teamSizes = await Promise.all( - teams.map(async (team) => { - const members = await team_service.getTeamUsers(team.teamId); - return { - teamId: team.teamId, - size: members.length, - } satisfies TeamSizeRow; - }), - ); - - // Trier les équipes par taille (ascendant) - teamSizes.sort((a, b) => a.size - b.size); - - for (const user of users) { - // Assigner l'utilisateur à l'équipe avec le moins de membres - const smallestTeam = teamSizes[0]; - await team_service.addTeamMember(smallestTeam.teamId, user.id); - - // Mettre à jour la taille de l'équipe après l'ajout - smallestTeam.size += 1; - - // Réordonner les équipes pour garder la plus petite en premier - teamSizes.sort((a, b) => a.size - b.size); - } - } - - // Assigner les utilisateurs TC aux équipes TC - if (tcStudents && tcTeams) { - await assignUsersToTeams(tcStudents, tcTeams); - } - - // Assigner les autres utilisateurs aux équipes non-TC - if (otherStudents && otherTeams) { - await assignUsersToTeams(otherStudents, otherTeams); - } - - //Assigner les utilisateurs MM aux équipes MM - if (PMOMStudents && PMOMTeams) { - await assignUsersToTeams(PMOMStudents, PMOMTeams); - } - + team_service.teamDistribution(); Ok(res, { msg: 'NewStudents distributed!' }); } catch (error) { Error(res, { error }); diff --git a/backend/src/database/migrations/0024_optimal_garia.sql b/backend/src/database/migrations/0024_optimal_garia.sql new file mode 100644 index 0000000..6b29aee --- /dev/null +++ b/backend/src/database/migrations/0024_optimal_garia.sql @@ -0,0 +1 @@ +ALTER TABLE "teams" ADD COLUMN "socialLink" text; \ No newline at end of file diff --git a/backend/src/database/migrations/0025_perfect_ulik.sql b/backend/src/database/migrations/0025_perfect_ulik.sql new file mode 100644 index 0000000..8bc1d9b --- /dev/null +++ b/backend/src/database/migrations/0025_perfect_ulik.sql @@ -0,0 +1,2 @@ +ALTER TABLE "teams" ADD COLUMN "riCompatible" boolean DEFAULT false;--> statement-breakpoint +ALTER TABLE "users" ADD COLUMN "male" boolean; \ No newline at end of file diff --git a/backend/src/database/migrations/meta/0024_snapshot.json b/backend/src/database/migrations/meta/0024_snapshot.json new file mode 100644 index 0000000..56629c4 --- /dev/null +++ b/backend/src/database/migrations/meta/0024_snapshot.json @@ -0,0 +1,1323 @@ +{ + "id": "f9312a1e-e654-452c-a1cb-d841f1ecefdc", + "prevId": "128e2d49-22e7-477d-a202-ae518c2e21ed", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.banned_addresses": { + "name": "banned_addresses", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "banned_addresses_email_unique": { + "name": "banned_addresses_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.challenges": { + "name": "challenges", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "points": { + "name": "points", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "challenges_created_by_users_id_fk": { + "name": "challenges_created_by_users_id_fk", + "tableFrom": "challenges", + "tableTo": "users", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.events": { + "name": "events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pre_registration_open": { + "name": "pre_registration_open", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "shotgun_open": { + "name": "shotgun_open", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "sdi_open": { + "name": "sdi_open", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "wei_open": { + "name": "wei_open", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "food_open": { + "name": "food_open", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "chall_open": { + "name": "chall_open", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.factions": { + "name": "factions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "factions_name_unique": { + "name": "factions_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.news": { + "name": "news", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "published": { + "name": "published", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "target": { + "name": "target", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "image_url": { + "name": "image_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.permanences": { + "name": "permanences", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "location": { + "name": "location", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "start_at": { + "name": "start_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "end_at": { + "name": "end_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "capacity": { + "name": "capacity", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "is_open": { + "name": "is_open", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "difficulty": { + "name": "difficulty", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.roles": { + "name": "roles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "roles_name_unique": { + "name": "roles_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.teams": { + "name": "teams", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "socialLink": { + "name": "socialLink", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "teams_name_unique": { + "name": "teams_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "first_name": { + "name": "first_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_name": { + "name": "last_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "majeur": { + "name": "majeur", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "branch": { + "name": "branch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "contact": { + "name": "contact", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "permission": { + "name": "permission", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'Nouveau'" + }, + "discord_id": { + "name": "discord_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "users_email_unique": { + "name": "users_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.bus_attribution": { + "name": "bus_attribution", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": true, + "notNull": true + }, + "bus": { + "name": "bus", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "departure_time": { + "name": "departure_time", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "bus_attribution_user_id_users_id_fk": { + "name": "bus_attribution_user_id_users_id_fk", + "tableFrom": "bus_attribution", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.challenge_validation": { + "name": "challenge_validation", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "challenge_id": { + "name": "challenge_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "validated_by_admin_id": { + "name": "validated_by_admin_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "validated_at": { + "name": "validated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "target_user_id": { + "name": "target_user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "target_team_id": { + "name": "target_team_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "target_faction_id": { + "name": "target_faction_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "points": { + "name": "points", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "added_by_admin_id": { + "name": "added_by_admin_id", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "challenge_validation_challenge_id_challenges_id_fk": { + "name": "challenge_validation_challenge_id_challenges_id_fk", + "tableFrom": "challenge_validation", + "tableTo": "challenges", + "columnsFrom": [ + "challenge_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "challenge_validation_validated_by_admin_id_users_id_fk": { + "name": "challenge_validation_validated_by_admin_id_users_id_fk", + "tableFrom": "challenge_validation", + "tableTo": "users", + "columnsFrom": [ + "validated_by_admin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "challenge_validation_target_user_id_users_id_fk": { + "name": "challenge_validation_target_user_id_users_id_fk", + "tableFrom": "challenge_validation", + "tableTo": "users", + "columnsFrom": [ + "target_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "challenge_validation_target_team_id_teams_id_fk": { + "name": "challenge_validation_target_team_id_teams_id_fk", + "tableFrom": "challenge_validation", + "tableTo": "teams", + "columnsFrom": [ + "target_team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "challenge_validation_target_faction_id_factions_id_fk": { + "name": "challenge_validation_target_faction_id_factions_id_fk", + "tableFrom": "challenge_validation", + "tableTo": "factions", + "columnsFrom": [ + "target_faction_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "challenge_validation_added_by_admin_id_users_id_fk": { + "name": "challenge_validation_added_by_admin_id_users_id_fk", + "tableFrom": "challenge_validation", + "tableTo": "users", + "columnsFrom": [ + "added_by_admin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.registration_tokens": { + "name": "registration_tokens", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "registration_tokens_user_id_users_id_fk": { + "name": "registration_tokens_user_id_users_id_fk", + "tableFrom": "registration_tokens", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "registration_tokens_token_unique": { + "name": "registration_tokens_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.role_points": { + "name": "role_points", + "schema": "", + "columns": { + "role_points": { + "name": "role_points", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "points": { + "name": "points", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "role_points_role_points_roles_id_fk": { + "name": "role_points_role_points_roles_id_fk", + "tableFrom": "role_points", + "tableTo": "roles", + "columnsFrom": [ + "role_points" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "role_points_role_points_pk": { + "name": "role_points_role_points_pk", + "columns": [ + "role_points" + ] + } + }, + "uniqueConstraints": { + "role_points_role_points_unique": { + "name": "role_points_role_points_unique", + "nullsNotDistinct": false, + "columns": [ + "role_points" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.team_faction": { + "name": "team_faction", + "schema": "", + "columns": { + "faction_id": { + "name": "faction_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "team_id": { + "name": "team_id", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "team_faction_faction_id_factions_id_fk": { + "name": "team_faction_faction_id_factions_id_fk", + "tableFrom": "team_faction", + "tableTo": "factions", + "columnsFrom": [ + "faction_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "team_faction_team_id_teams_id_fk": { + "name": "team_faction_team_id_teams_id_fk", + "tableFrom": "team_faction", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "team_faction_faction_id_team_id_pk": { + "name": "team_faction_faction_id_team_id_pk", + "columns": [ + "faction_id", + "team_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.team_shotgun": { + "name": "team_shotgun", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "timestamp": { + "name": "timestamp", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "team_shotgun_team_id_teams_id_fk": { + "name": "team_shotgun_team_id_teams_id_fk", + "tableFrom": "team_shotgun", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.respo_permanences": { + "name": "respo_permanences", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "permanence_id": { + "name": "permanence_id", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "respo_permanences_user_id_users_id_fk": { + "name": "respo_permanences_user_id_users_id_fk", + "tableFrom": "respo_permanences", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "respo_permanences_permanence_id_permanences_id_fk": { + "name": "respo_permanences_permanence_id_permanences_id_fk", + "tableFrom": "respo_permanences", + "tableTo": "permanences", + "columnsFrom": [ + "permanence_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "respo_permanences_user_id_permanence_id_pk": { + "name": "respo_permanences_user_id_permanence_id_pk", + "columns": [ + "user_id", + "permanence_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_permanences": { + "name": "user_permanences", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "permanence_id": { + "name": "permanence_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "registered_at": { + "name": "registered_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "claimed": { + "name": "claimed", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + } + }, + "indexes": {}, + "foreignKeys": { + "user_permanences_user_id_users_id_fk": { + "name": "user_permanences_user_id_users_id_fk", + "tableFrom": "user_permanences", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_permanences_permanence_id_permanences_id_fk": { + "name": "user_permanences_permanence_id_permanences_id_fk", + "tableFrom": "user_permanences", + "tableTo": "permanences", + "columnsFrom": [ + "permanence_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "user_permanences_user_id_permanence_id_pk": { + "name": "user_permanences_user_id_permanence_id_pk", + "columns": [ + "user_id", + "permanence_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_preferences": { + "name": "user_preferences", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "role_id": { + "name": "role_id", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "user_preferences_user_id_users_id_fk": { + "name": "user_preferences_user_id_users_id_fk", + "tableFrom": "user_preferences", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_preferences_role_id_roles_id_fk": { + "name": "user_preferences_role_id_roles_id_fk", + "tableFrom": "user_preferences", + "tableTo": "roles", + "columnsFrom": [ + "role_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "user_preferences_user_id_role_id_pk": { + "name": "user_preferences_user_id_role_id_pk", + "columns": [ + "user_id", + "role_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_roles": { + "name": "user_roles", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "role_id": { + "name": "role_id", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "user_roles_user_id_users_id_fk": { + "name": "user_roles_user_id_users_id_fk", + "tableFrom": "user_roles", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_roles_role_id_roles_id_fk": { + "name": "user_roles_role_id_roles_id_fk", + "tableFrom": "user_roles", + "tableTo": "roles", + "columnsFrom": [ + "role_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_teams": { + "name": "user_teams", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "team_id": { + "name": "team_id", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "user_teams_user_id_users_id_fk": { + "name": "user_teams_user_id_users_id_fk", + "tableFrom": "user_teams", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_teams_team_id_teams_id_fk": { + "name": "user_teams_team_id_teams_id_fk", + "tableFrom": "user_teams", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "user_teams_user_id_team_id_pk": { + "name": "user_teams_user_id_team_id_pk", + "columns": [ + "user_id", + "team_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_tent": { + "name": "user_tent", + "schema": "", + "columns": { + "user_id_1": { + "name": "user_id_1", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "user_id_2": { + "name": "user_id_2", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "confirmed": { + "name": "confirmed", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "user_tent_user_id_1_users_id_fk": { + "name": "user_tent_user_id_1_users_id_fk", + "tableFrom": "user_tent", + "tableTo": "users", + "columnsFrom": [ + "user_id_1" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_tent_user_id_2_users_id_fk": { + "name": "user_tent_user_id_2_users_id_fk", + "tableFrom": "user_tent", + "tableTo": "users", + "columnsFrom": [ + "user_id_2" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "user_tent_user_id_1_user_id_2_pk": { + "name": "user_tent_user_id_1_user_id_2_pk", + "columns": [ + "user_id_1", + "user_id_2" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/backend/src/database/migrations/meta/0025_snapshot.json b/backend/src/database/migrations/meta/0025_snapshot.json new file mode 100644 index 0000000..060def6 --- /dev/null +++ b/backend/src/database/migrations/meta/0025_snapshot.json @@ -0,0 +1,1336 @@ +{ + "id": "f8bcae73-ebc2-4afa-99c0-cf9aa304b17b", + "prevId": "f9312a1e-e654-452c-a1cb-d841f1ecefdc", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.banned_addresses": { + "name": "banned_addresses", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "banned_addresses_email_unique": { + "name": "banned_addresses_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.challenges": { + "name": "challenges", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "points": { + "name": "points", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "challenges_created_by_users_id_fk": { + "name": "challenges_created_by_users_id_fk", + "tableFrom": "challenges", + "tableTo": "users", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.events": { + "name": "events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pre_registration_open": { + "name": "pre_registration_open", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "shotgun_open": { + "name": "shotgun_open", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "sdi_open": { + "name": "sdi_open", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "wei_open": { + "name": "wei_open", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "food_open": { + "name": "food_open", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "chall_open": { + "name": "chall_open", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.factions": { + "name": "factions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "factions_name_unique": { + "name": "factions_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.news": { + "name": "news", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "published": { + "name": "published", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "target": { + "name": "target", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "image_url": { + "name": "image_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.permanences": { + "name": "permanences", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "location": { + "name": "location", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "start_at": { + "name": "start_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "end_at": { + "name": "end_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "capacity": { + "name": "capacity", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "is_open": { + "name": "is_open", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "difficulty": { + "name": "difficulty", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.roles": { + "name": "roles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "roles_name_unique": { + "name": "roles_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.teams": { + "name": "teams", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "socialLink": { + "name": "socialLink", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "riCompatible": { + "name": "riCompatible", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "teams_name_unique": { + "name": "teams_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "first_name": { + "name": "first_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_name": { + "name": "last_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "majeur": { + "name": "majeur", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "male": { + "name": "male", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "branch": { + "name": "branch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "contact": { + "name": "contact", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "permission": { + "name": "permission", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'Nouveau'" + }, + "discord_id": { + "name": "discord_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "users_email_unique": { + "name": "users_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.bus_attribution": { + "name": "bus_attribution", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": true, + "notNull": true + }, + "bus": { + "name": "bus", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "departure_time": { + "name": "departure_time", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "bus_attribution_user_id_users_id_fk": { + "name": "bus_attribution_user_id_users_id_fk", + "tableFrom": "bus_attribution", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.challenge_validation": { + "name": "challenge_validation", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "challenge_id": { + "name": "challenge_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "validated_by_admin_id": { + "name": "validated_by_admin_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "validated_at": { + "name": "validated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "target_user_id": { + "name": "target_user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "target_team_id": { + "name": "target_team_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "target_faction_id": { + "name": "target_faction_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "points": { + "name": "points", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "added_by_admin_id": { + "name": "added_by_admin_id", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "challenge_validation_challenge_id_challenges_id_fk": { + "name": "challenge_validation_challenge_id_challenges_id_fk", + "tableFrom": "challenge_validation", + "tableTo": "challenges", + "columnsFrom": [ + "challenge_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "challenge_validation_validated_by_admin_id_users_id_fk": { + "name": "challenge_validation_validated_by_admin_id_users_id_fk", + "tableFrom": "challenge_validation", + "tableTo": "users", + "columnsFrom": [ + "validated_by_admin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "challenge_validation_target_user_id_users_id_fk": { + "name": "challenge_validation_target_user_id_users_id_fk", + "tableFrom": "challenge_validation", + "tableTo": "users", + "columnsFrom": [ + "target_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "challenge_validation_target_team_id_teams_id_fk": { + "name": "challenge_validation_target_team_id_teams_id_fk", + "tableFrom": "challenge_validation", + "tableTo": "teams", + "columnsFrom": [ + "target_team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "challenge_validation_target_faction_id_factions_id_fk": { + "name": "challenge_validation_target_faction_id_factions_id_fk", + "tableFrom": "challenge_validation", + "tableTo": "factions", + "columnsFrom": [ + "target_faction_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "challenge_validation_added_by_admin_id_users_id_fk": { + "name": "challenge_validation_added_by_admin_id_users_id_fk", + "tableFrom": "challenge_validation", + "tableTo": "users", + "columnsFrom": [ + "added_by_admin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.registration_tokens": { + "name": "registration_tokens", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "registration_tokens_user_id_users_id_fk": { + "name": "registration_tokens_user_id_users_id_fk", + "tableFrom": "registration_tokens", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "registration_tokens_token_unique": { + "name": "registration_tokens_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.role_points": { + "name": "role_points", + "schema": "", + "columns": { + "role_points": { + "name": "role_points", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "points": { + "name": "points", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "role_points_role_points_roles_id_fk": { + "name": "role_points_role_points_roles_id_fk", + "tableFrom": "role_points", + "tableTo": "roles", + "columnsFrom": [ + "role_points" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "role_points_role_points_pk": { + "name": "role_points_role_points_pk", + "columns": [ + "role_points" + ] + } + }, + "uniqueConstraints": { + "role_points_role_points_unique": { + "name": "role_points_role_points_unique", + "nullsNotDistinct": false, + "columns": [ + "role_points" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.team_faction": { + "name": "team_faction", + "schema": "", + "columns": { + "faction_id": { + "name": "faction_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "team_id": { + "name": "team_id", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "team_faction_faction_id_factions_id_fk": { + "name": "team_faction_faction_id_factions_id_fk", + "tableFrom": "team_faction", + "tableTo": "factions", + "columnsFrom": [ + "faction_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "team_faction_team_id_teams_id_fk": { + "name": "team_faction_team_id_teams_id_fk", + "tableFrom": "team_faction", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "team_faction_faction_id_team_id_pk": { + "name": "team_faction_faction_id_team_id_pk", + "columns": [ + "faction_id", + "team_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.team_shotgun": { + "name": "team_shotgun", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "timestamp": { + "name": "timestamp", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "team_shotgun_team_id_teams_id_fk": { + "name": "team_shotgun_team_id_teams_id_fk", + "tableFrom": "team_shotgun", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.respo_permanences": { + "name": "respo_permanences", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "permanence_id": { + "name": "permanence_id", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "respo_permanences_user_id_users_id_fk": { + "name": "respo_permanences_user_id_users_id_fk", + "tableFrom": "respo_permanences", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "respo_permanences_permanence_id_permanences_id_fk": { + "name": "respo_permanences_permanence_id_permanences_id_fk", + "tableFrom": "respo_permanences", + "tableTo": "permanences", + "columnsFrom": [ + "permanence_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "respo_permanences_user_id_permanence_id_pk": { + "name": "respo_permanences_user_id_permanence_id_pk", + "columns": [ + "user_id", + "permanence_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_permanences": { + "name": "user_permanences", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "permanence_id": { + "name": "permanence_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "registered_at": { + "name": "registered_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "claimed": { + "name": "claimed", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + } + }, + "indexes": {}, + "foreignKeys": { + "user_permanences_user_id_users_id_fk": { + "name": "user_permanences_user_id_users_id_fk", + "tableFrom": "user_permanences", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_permanences_permanence_id_permanences_id_fk": { + "name": "user_permanences_permanence_id_permanences_id_fk", + "tableFrom": "user_permanences", + "tableTo": "permanences", + "columnsFrom": [ + "permanence_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "user_permanences_user_id_permanence_id_pk": { + "name": "user_permanences_user_id_permanence_id_pk", + "columns": [ + "user_id", + "permanence_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_preferences": { + "name": "user_preferences", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "role_id": { + "name": "role_id", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "user_preferences_user_id_users_id_fk": { + "name": "user_preferences_user_id_users_id_fk", + "tableFrom": "user_preferences", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_preferences_role_id_roles_id_fk": { + "name": "user_preferences_role_id_roles_id_fk", + "tableFrom": "user_preferences", + "tableTo": "roles", + "columnsFrom": [ + "role_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "user_preferences_user_id_role_id_pk": { + "name": "user_preferences_user_id_role_id_pk", + "columns": [ + "user_id", + "role_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_roles": { + "name": "user_roles", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "role_id": { + "name": "role_id", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "user_roles_user_id_users_id_fk": { + "name": "user_roles_user_id_users_id_fk", + "tableFrom": "user_roles", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_roles_role_id_roles_id_fk": { + "name": "user_roles_role_id_roles_id_fk", + "tableFrom": "user_roles", + "tableTo": "roles", + "columnsFrom": [ + "role_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_teams": { + "name": "user_teams", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "team_id": { + "name": "team_id", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "user_teams_user_id_users_id_fk": { + "name": "user_teams_user_id_users_id_fk", + "tableFrom": "user_teams", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_teams_team_id_teams_id_fk": { + "name": "user_teams_team_id_teams_id_fk", + "tableFrom": "user_teams", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "user_teams_user_id_team_id_pk": { + "name": "user_teams_user_id_team_id_pk", + "columns": [ + "user_id", + "team_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_tent": { + "name": "user_tent", + "schema": "", + "columns": { + "user_id_1": { + "name": "user_id_1", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "user_id_2": { + "name": "user_id_2", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "confirmed": { + "name": "confirmed", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "user_tent_user_id_1_users_id_fk": { + "name": "user_tent_user_id_1_users_id_fk", + "tableFrom": "user_tent", + "tableTo": "users", + "columnsFrom": [ + "user_id_1" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_tent_user_id_2_users_id_fk": { + "name": "user_tent_user_id_2_users_id_fk", + "tableFrom": "user_tent", + "tableTo": "users", + "columnsFrom": [ + "user_id_2" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "user_tent_user_id_1_user_id_2_pk": { + "name": "user_tent_user_id_1_user_id_2_pk", + "columns": [ + "user_id_1", + "user_id_2" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/backend/src/database/migrations/meta/_journal.json b/backend/src/database/migrations/meta/_journal.json index 7c107c1..b787921 100644 --- a/backend/src/database/migrations/meta/_journal.json +++ b/backend/src/database/migrations/meta/_journal.json @@ -169,6 +169,20 @@ "when": 1782943789540, "tag": "0023_zippy_colonel_america", "breakpoints": true + }, + { + "idx": 24, + "version": "7", + "when": 1783776285772, + "tag": "0024_optimal_garia", + "breakpoints": true + }, + { + "idx": 25, + "version": "7", + "when": 1784742510283, + "tag": "0025_perfect_ulik", + "breakpoints": true } ] } \ No newline at end of file diff --git a/backend/src/dto/team.dto.ts b/backend/src/dto/team.dto.ts index 1f080fb..ca34e97 100644 --- a/backend/src/dto/team.dto.ts +++ b/backend/src/dto/team.dto.ts @@ -13,6 +13,7 @@ export type ModifyTeamBody = { teamName?: string; teamMembers: number[]; factionID?: number; + socialLink?: string; type?: string; }; @@ -29,14 +30,30 @@ export type StudentRow = { userId: number; email: string; branch: string; + male?: boolean; +}; + +export type TeamAssignmentNotification = { + email: string; + teamId: number; }; export type TeamRow = { teamId: number; + name: string; + description: string; type: string; + socialLink: string; + riCompatible: boolean; +}; + +export type TeamDistributionState = TeamRow & { + size: number; + girlsCount: number; }; export type TeamSizeRow = { teamId: number; + teamName: string; size: number; }; diff --git a/backend/src/email/email.preview-data.ts b/backend/src/email/email.preview-data.ts index f319dd3..b2d14d9 100644 --- a/backend/src/email/email.preview-data.ts +++ b/backend/src/email/email.preview-data.ts @@ -32,4 +32,8 @@ export const defaultPreviewData: Record = { permanence: {}, }, templateMentorReminder: {}, + templateNotifyTeamAssignment: { + teamName: 'Bisounours', + factionName: 'Dessin Animé', + }, }; diff --git a/backend/src/email/email.registry.ts b/backend/src/email/email.registry.ts index 60247f2..140074e 100644 --- a/backend/src/email/email.registry.ts +++ b/backend/src/email/email.registry.ts @@ -1,4 +1,4 @@ -import type { PermanenceEmailData, TemplateRenderer } from '../../types/email'; +import type { PermanenceEmailData, TemplateRenderer, TeamAssignmentEmailData } from '../../types/email'; export const templateResetPassword = 'reset-password.html'; const templateNotebook = 'notebook.html'; @@ -7,6 +7,7 @@ const templateWelcome = 'welcome.html'; const templateNotifyNews = 'notify-news.html'; const templateNotifyTentConfirmation = 'notify-tent-confirmation.html'; const templateNotifyPermanenceReminder = 'notify-permanence-reminder.html'; +const templateNotifyTeamAssignment = 'notify-team-assignment.html'; const templateMentorReminder = 'mentor-reminder.html'; export const templateRenderers: Record = { @@ -85,4 +86,11 @@ export const templateRenderers: Record = { return {}; }, }, + templateNotifyTeamAssignment: { + fileName: templateNotifyTeamAssignment, + buildData: (data) => { + const typedData = data as TeamAssignmentEmailData; + return typedData; + }, + }, }; diff --git a/backend/src/email/templates/notify-team-assignment.html b/backend/src/email/templates/notify-team-assignment.html new file mode 100644 index 0000000..34c0ef9 --- /dev/null +++ b/backend/src/email/templates/notify-team-assignment.html @@ -0,0 +1,191 @@ + + + + + + Intégration UTT + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + +
+ Logo Intégration UTT +
+ INTEGRATION UTT +
+

+ Découvre ton équipe d'intégration ! +

+ +

{{teamName}}

+

+ Faction {{factionName}} +

+ +

+ L'équipe {{teamName}} est consituée de Chefs d'équipes (CE), déjà + étudiants à l'UTT, et d'un certain nombre de nouveaux comme toi ! L'idéal pour + découvrir la vie à Troyes, sur le Campus, rencontrer tes futurs amis et réaliser les + défis d'Intégration ! +

+ +

Contact les dès maintenant !

+ +

+ Rejoins dès maintenant le groupe WhatsApp de ton équipe pour ne rien manquer des + infos et activités à venir. +

+ +

+ Accéder au groupe WhatsApp +

+
+

+ Retrouve des informations utiles sur notre Instagram ! +

+
+ + Instagram + +
+

Cordialement,
L'équipe intégration UTT

+

+ Si vous avez des questions, n'hésitez pas à + nous contacter. +

+
+

+ Discover your integration team ! +

+ +

{{teamName}}

+

+ Faction {{factionName}} +

+ +

+ The {{teamName}} team is made up of Team Leaders (who are current + UTT students) and several new students just like you! It's the perfect way to + discover life in Troyes and on campus, make new friends, and take on the Integration + challenges! +

+ +

Get in touch with them right now !

+ +

+ Join your team's WhatsApp group today so you don't miss out on any news or upcoming + activities. +

+ +

+ Access the WhatsApp's group +

+
+

Stay updated by following us on Instagram!

+
+ + Instagram + +
+

Regards,
The UTT's Integration Team

+

+ If you have any questions, feel free to + contact us. +

+
+
+ + diff --git a/backend/src/email/templates/welcome.html b/backend/src/email/templates/welcome.html index 60769ad..e451369 100644 --- a/backend/src/email/templates/welcome.html +++ b/backend/src/email/templates/welcome.html @@ -164,7 +164,7 @@

Make sure to complete the following tasks before you arrive:

-

1. Log in to the integration website

+

Log in to the integration website

To access the integration website, you simply need to reset your password by clicking the link below: @@ -201,7 +201,7 @@

2. Mentorship Program

To match you with the perfect mentor, please fill out the form on the Parrainage page of the - integration website ! + Integration website !

diff --git a/backend/src/routes/team.routes.ts b/backend/src/routes/team.routes.ts index 80ec385..a2d484b 100644 --- a/backend/src/routes/team.routes.ts +++ b/backend/src/routes/team.routes.ts @@ -5,17 +5,18 @@ import { checkRole } from '../middlewares/user.middleware'; const teamRouter = express.Router(); //User routes -teamRouter.post("/user/create", checkRole("Student", []), teamController.createNewTeam); +teamRouter.post('/user/create', checkRole('Student', []), teamController.createNewTeam); +teamRouter.get('/user/me', teamController.getUserTeam); //Admin Routes -teamRouter.post("/admin/createlight", checkRole("Admin", ["Respo CE"]), teamController.createNewTeamLight); -teamRouter.get("/admin/teams", checkRole("Admin", ["Respo CE", "Arbitre"]), teamController.getTeams); -teamRouter.get("/admin/teamswithfactions", checkRole("Admin", ["Respo CE"]), teamController.getTeamsWithfactions); -teamRouter.get("/admin/teamfaction", checkRole("Admin", ["Respo CE"]), teamController.getTeamFaction); -teamRouter.get("/admin/teamswithusers", checkRole("Admin", ["Respo CE"]), teamController.getAllTeamsWithUsers); -teamRouter.put("/admin/modify", checkRole("Admin", ["Respo CE"]), teamController.modifyTeam); -teamRouter.get("/admin/teamusers", checkRole("Admin", ["Respo CE"]), teamController.getTeamUsers); -teamRouter.delete("/admin/delete", checkRole("Admin", ["Respo CE"]), teamController.deleteTeam); -teamRouter.post('/admin/distributeteam', checkRole("Admin", []), teamController.teamDistribution); +teamRouter.post('/admin/createlight', checkRole('Admin', ['Respo CE']), teamController.createNewTeamLight); +teamRouter.get('/admin/teams', checkRole('Admin', ['Respo CE', 'Arbitre']), teamController.getTeams); +teamRouter.get('/admin/teamswithfactions', checkRole('Admin', ['Respo CE']), teamController.getTeamsWithfactions); +teamRouter.get('/admin/teamfaction', checkRole('Admin', ['Respo CE']), teamController.getTeamFaction); +teamRouter.get('/admin/teamswithusers', checkRole('Admin', ['Respo CE']), teamController.getAllTeamsWithUsers); +teamRouter.put('/admin/modify', checkRole('Admin', ['Respo CE']), teamController.modifyTeam); +teamRouter.get('/admin/teamusers', checkRole('Admin', ['Respo CE']), teamController.getTeamUsers); +teamRouter.delete('/admin/delete', checkRole('Admin', ['Respo CE']), teamController.deleteTeam); +teamRouter.post('/admin/distributeteam', checkRole('Admin', []), teamController.teamDistribution); export default teamRouter; diff --git a/backend/src/schemas/Basic/team.schema.ts b/backend/src/schemas/Basic/team.schema.ts index ba63242..02f9801 100644 --- a/backend/src/schemas/Basic/team.schema.ts +++ b/backend/src/schemas/Basic/team.schema.ts @@ -1,10 +1,12 @@ -import { pgTable, serial, text } from "drizzle-orm/pg-core"; +import { pgTable, serial, text, boolean } from 'drizzle-orm/pg-core'; -export const teamSchema = pgTable("teams", { - id: serial("id").primaryKey(), - name: text("name").unique(), // Le nom de l'équipe - description: text("description"), // Description de l'équipe +export const teamSchema = pgTable('teams', { + id: serial('id').primaryKey(), + name: text('name').unique(), // Le nom de l'équipe + description: text('description'), // Description de l'équipe type: text('type'), + social_link: text('socialLink'), + ri_compatible: boolean('riCompatible').default(false), // Indique si l'équipe est compatible avec le RI }); export type Team = typeof teamSchema.$inferSelect; diff --git a/backend/src/schemas/Basic/user.schema.ts b/backend/src/schemas/Basic/user.schema.ts index 8ada041..94cc4f2 100644 --- a/backend/src/schemas/Basic/user.schema.ts +++ b/backend/src/schemas/Basic/user.schema.ts @@ -1,17 +1,18 @@ -import { boolean, pgTable, serial, text, timestamp } from "drizzle-orm/pg-core"; +import { boolean, pgTable, serial, text, timestamp } from 'drizzle-orm/pg-core'; -export const userSchema = pgTable("users", { - id: serial("id").primaryKey(), - first_name: text("first_name"), - last_name: text("last_name"), - email: text("email").unique(), - majeur: boolean("majeur"), - branch: text("branch"), - contact: text("contact"), - password: text("password"), - permission: text("permission").default("Nouveau"), // Par défaut, le rôle sera "Nouveau" - discord_id: text("discord_id"), - created_at: timestamp("created_at").defaultNow(), +export const userSchema = pgTable('users', { + id: serial('id').primaryKey(), + first_name: text('first_name'), + last_name: text('last_name'), + email: text('email').unique(), + majeur: boolean('majeur'), + male: boolean('male'), + branch: text('branch'), + contact: text('contact'), + password: text('password'), + permission: text('permission').default('Nouveau'), // Par défaut, le rôle sera "Nouveau" + discord_id: text('discord_id'), + created_at: timestamp('created_at').defaultNow(), }); export type User = typeof userSchema.$inferSelect; diff --git a/backend/src/services/team.service.ts b/backend/src/services/team.service.ts index 1083479..9a66594 100644 --- a/backend/src/services/team.service.ts +++ b/backend/src/services/team.service.ts @@ -1,20 +1,22 @@ -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 { getFaction } from "./faction.service"; +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 { getFaction } from './faction.service'; +import * as user_service from '../services/user.service'; +import type { StudentRow, TeamMemberRow, TeamRow, TeamAssignmentNotification } from '../dto/team.dto'; +import sendEmailToNewAssignedStudents from './team/email.team'; +import assignUsersToTeams from './team/assignation.team'; export const createTeam = async (teamName: string, members: number[]) => { const newTeam = await db.insert(teamSchema).values({ name: teamName }).returning(); const teamId = newTeam[0].id; - await Promise.all(members.map((userId) => - db.insert(userTeamsSchema).values({ team_id: teamId, user_id: userId }) - )); + await Promise.all(members.map((userId) => db.insert(userTeamsSchema).values({ team_id: teamId, user_id: userId }))); return newTeam; }; @@ -30,22 +32,46 @@ export const createTeamLight = async (teamName: string, factionId: number) => { }; export const getUserTeam = async (userId: number) => { - const userTeam = await db.select({ userTeamId: userTeamsSchema.team_id }).from(userTeamsSchema).where(eq(userTeamsSchema.user_id, userId)); + const userTeam = await db + .select({ userTeamId: userTeamsSchema.team_id }) + .from(userTeamsSchema) + .where(eq(userTeamsSchema.user_id, userId)); - return userTeam[0]?.userTeamId -} + return userTeam[0]?.userTeamId; +}; + +export const getUserTeamDisplayInfos = async (userId: number) => { + return ( + await db + .select({ + id: teamSchema.id, + name: teamSchema.name, + faction_id: factionSchema.id, + faction_name: factionSchema.name, + social_link: teamSchema.social_link, + }) + .from(userTeamsSchema) + .innerJoin(teamSchema, eq(teamSchema.id, userTeamsSchema.team_id)) + .innerJoin(teamFactionSchema, eq(teamFactionSchema.team_id, teamSchema.id)) + .innerJoin(factionSchema, eq(factionSchema.id, teamFactionSchema.faction_id)) + .where(eq(userTeamsSchema.user_id, userId)) + )[0]; +}; export const getTeams = async () => { - const teams = await db.select( - { + const teams = await db + .select({ teamId: teamSchema.id, name: teamSchema.name, description: teamSchema.description, - type: teamSchema.type - }).from(teamSchema); + type: teamSchema.type, + socialLink: teamSchema.social_link, + riCompatible: teamSchema.ri_compatible, + }) + .from(teamSchema); - return teams -} + return teams; +}; export const getTeamsAll = async () => { const teams = await db.select().from(teamSchema); @@ -56,27 +82,30 @@ export const getTeamsAll = async () => { const teamFaction = await getFaction(teamFactionId); return { ...team, - teamFaction + teamFaction, }; - }) + }), ); return teamsWithFaction; +}; -} - -export const modifyTeam = async (teamID: number, teamMembers: number[], factionID: number, name?: string, type?: string) => { +export const modifyTeam = async ( + teamID: number, + teamMembers: number[], + factionID: number, + socialLink: string, + 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)); + 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)); + await db.update(teamSchema).set({ type: type }).where(eq(teamSchema.id, teamID)); + } + if (socialLink !== undefined) { + await db.update(teamSchema).set({ social_link: socialLink }).where(eq(teamSchema.id, teamID)); } // 2. Mise à jour des membres de l'équipe (remplace les anciens) @@ -91,7 +120,7 @@ export const modifyTeam = async (teamID: number, teamMembers: number[], factionI teamMembers.map((userID) => ({ user_id: userID, team_id: teamID, - })) + })), ); } } @@ -112,10 +141,7 @@ export const modifyTeam = async (teamID: number, teamMembers: number[], factionI } // 4. Retour de la team modifiée - const updatedTeam = await db - .select() - .from(teamSchema) - .where(eq(teamSchema.id, teamID)); + const updatedTeam = await db.select().from(teamSchema).where(eq(teamSchema.id, teamID)); return updatedTeam[0]; }; @@ -127,7 +153,7 @@ export const getTeamUsers = async (teamId: any) => { firstName: userSchema.first_name, lastName: userSchema.last_name, email: userSchema.email, - permission: userSchema.permission + permission: userSchema.permission, }) .from(userSchema) .innerJoin(userTeamsSchema, eq(userSchema.id, userTeamsSchema.user_id)) @@ -135,6 +161,7 @@ export const getTeamUsers = async (teamId: any) => { return users; }; + export const getAllTeamsWithUsers = async () => { const results = await db .select({ @@ -154,19 +181,22 @@ export const getAllTeamsWithUsers = async () => { .innerJoin(teamFactionSchema, eq(teamSchema.id, teamFactionSchema.team_id)) .innerJoin(factionSchema, eq(factionSchema.id, teamFactionSchema.faction_id)); - const teamsMap = new Map; - }>(); + name: string; + type: string; + faction: string; + users: Array<{ + id: number; + firstName: string; + lastName: string; + discordId: string; + permission: string; + }>; + } + >(); for (const row of results) { if (!teamsMap.has(row.teamId)) { @@ -184,12 +214,12 @@ export const getAllTeamsWithUsers = async () => { firstName: row.firstName, lastName: row.lastName, discordId: row.discordId, - permission: row.permission + permission: row.permission, }); } return Array.from(teamsMap.values()); -} +}; export const getTeamFaction = async (teamId: any) => { const teamFactionId = await db @@ -215,33 +245,24 @@ export const deleteTeam = async (teamID: number) => { await db.delete(teamShotgunSchema).where(eq(teamShotgunSchema.team_id, teamID)); // 4. Supprimer l'équipe de la table principale (teams) - const deletedTeam = await db - .delete(teamSchema) - .where(eq(teamSchema.id, teamID)) - .returning(); + 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."); + throw new Error('Équipe non trouvée.'); } return deletedTeam[0]; // Retourne les informations de l'équipe supprimée }; -export const addTeamMember = async (teamId: number, userId: number) => { - const newTeamMember = await db.insert(userTeamsSchema).values({ user_id: userId, team_id: teamId, }); - - return newTeamMember; -}; - export const getUsersWithTeam = async () => { try { - const userswithteam = await db.select( - { + const userswithteam = await db + .select({ userId: userTeamsSchema.user_id, teamId: userTeamsSchema.team_id, - } - ).from(userTeamsSchema); + }) + .from(userTeamsSchema); return userswithteam; } catch (err) { @@ -252,12 +273,13 @@ export const getUsersWithTeam = async () => { export const getTeam = async (teamId: any) => { try { - const team = await db.select( - { + const team = await db + .select({ teamId: teamSchema.id, - teamName: teamSchema.name - } - ).from(teamSchema).where(eq(teamSchema.id, teamId)); + teamName: teamSchema.name, + }) + .from(teamSchema) + .where(eq(teamSchema.id, teamId)); return team[0]; } catch (err) { @@ -265,3 +287,81 @@ export const getTeam = async (teamId: any) => { throw new Error('Erreur de base de données'); } }; + +export const teamDistribution = async () => { + const newStudents = (await user_service.getUsersbyPermission('Nouveau')) as StudentRow[]; + const userswithteams = ((await getUsersWithTeam()) as TeamMemberRow[]).map((entry) => entry.userId); + const teams = (await getTeams()) as TeamRow[]; + + // Filtrer les étudiants qui ne sont pas déjà assignés à une équipe + const filteredStudents = newStudents.filter((student) => !userswithteams.includes(student.userId)); + + // Filtrer les utilisateurs en fonction de la spécialité + const tcStudents: StudentRow[] = filteredStudents + .filter((student) => student.branch === 'TC') + .map((student) => ({ + userId: student.userId, + email: student.email, + branch: student.branch, + male: student.male, + })); + + const RIStudents: StudentRow[] = filteredStudents + .filter((student) => student.branch === 'RI') + .map((student) => ({ + userId: student.userId, + email: student.email, + branch: student.branch, + })); + + const PMOMStudents: StudentRow[] = filteredStudents + .filter((student) => student.branch == 'MM') + .map((student) => ({ + userId: student.userId, + email: student.email, + branch: student.branch, + male: student.male, + })); + + const otherStudents: StudentRow[] = filteredStudents + .filter((student) => student.branch !== 'TC' && student.branch !== 'RI' && student.branch !== 'MM') + .map((student) => ({ + userId: student.userId, + email: student.email, + branch: student.branch, + male: student.male, + })); + + // Filtrer les équipes en fonction de leur type + const tcTeams = teams.filter((team) => team.type === 'TC'); + const PMOMTeams = teams.filter((team) => team.type === 'MM'); + const otherTeams = teams.filter((team) => team.type !== 'TC' && team.type !== 'MM'); + const RICompatibleTeams = teams.filter((team) => team.riCompatible === true); + + const notificationsToSend: TeamAssignmentNotification[] = []; + const addNotification = async (notification: TeamAssignmentNotification) => { + notificationsToSend.push(notification); + }; + + // Assigner les utilisateurs TC aux équipes TC + if (tcStudents && tcTeams) { + await assignUsersToTeams(tcStudents, tcTeams, addNotification); + } + + // Assigner les autres utilisateurs aux équipes non-TC + if (otherStudents && otherTeams) { + await assignUsersToTeams(otherStudents, otherTeams, addNotification); + } + + // Assigner les utilisateurs RI aux équipes compatibles RI + if (RIStudents && RICompatibleTeams) { + await assignUsersToTeams(RIStudents, RICompatibleTeams, addNotification); + } + + //Assigner les utilisateurs MM aux équipes MM + if (PMOMStudents && PMOMTeams) { + await assignUsersToTeams(PMOMStudents, PMOMTeams, addNotification); + } + + sendEmailToNewAssignedStudents(notificationsToSend); +}; diff --git a/backend/src/services/team/assignation.team.ts b/backend/src/services/team/assignation.team.ts new file mode 100644 index 0000000..6e0e009 --- /dev/null +++ b/backend/src/services/team/assignation.team.ts @@ -0,0 +1,183 @@ +import { eq, inArray } from 'drizzle-orm'; +import { db } from '../../database/db'; +import { userSchema } from '../../schemas/Basic/user.schema'; +import { userTeamsSchema } from '../../schemas/Relational/userteams.schema'; +import type { StudentRow, TeamRow, TeamDistributionState, TeamAssignmentNotification } from '../../dto/team.dto'; + +/** + * Détermine si un(e) étudiant(e) est une fille. + * Le champ `male` peut être stocké sous forme de booléen ou de nombre (0/1), + * on gère donc les deux cas : `false` ou `0` signifient "fille". + * + * @param student - La ligne étudiant à tester. + * @returns `true` si l'étudiant(e) est une fille, sinon `false`. + */ +const isFemale = (student: StudentRow) => { + const maleValue: unknown = student.male; + return maleValue === false || maleValue === 0; +}; + +/** + * Ajoute un utilisateur à une équipe en créant l'entrée correspondante + * dans la table de liaison `userTeamsSchema`. + * + * @param teamId - Identifiant de l'équipe cible. + * @param userId - Identifiant de l'utilisateur à ajouter. + * @returns Le résultat de l'insertion en base de données. + */ +export const addTeamMember = async (teamId: number, userId: number) => + await db.insert(userTeamsSchema).values({ user_id: userId, team_id: teamId }); + +/** + * Compare deux équipes candidates afin de déterminer laquelle est + * la plus adaptée pour recevoir un nouvel étudiant, dans le but + * d'équilibrer les équipes en taille et en mixité. + * + * Logique : + * - Si l'étudiant(e) est une fille : on privilégie l'équipe avec le moins + * de filles, puis la plus petite taille, puis le plus petit teamId (déterminisme). + * - Si l'étudiant est un garçon : on privilégie la plus petite taille, + * puis le moins de filles, puis le plus petit teamId. + * + * @param candidate - L'équipe candidate à évaluer. + * @param currentBest - La meilleure équipe trouvée jusqu'à présent. + * @param female - `true` si l'étudiant à placer est une fille. + * @returns `true` si `candidate` est un meilleur choix que `currentBest`. + */ +const isBetterTeam = (candidate: TeamDistributionState, currentBest: TeamDistributionState, female: boolean) => { + if (female) { + if (candidate.girlsCount !== currentBest.girlsCount) { + return candidate.girlsCount < currentBest.girlsCount; + } + + if (candidate.size !== currentBest.size) { + return candidate.size < currentBest.size; + } + + return candidate.teamId < currentBest.teamId; + } + + if (candidate.size !== currentBest.size) { + return candidate.size < currentBest.size; + } + + if (candidate.girlsCount !== currentBest.girlsCount) { + return candidate.girlsCount < currentBest.girlsCount; + } + + return candidate.teamId < currentBest.teamId; +}; + +/** + * Construit, pour chaque équipe candidate, un état de distribution + * (`TeamDistributionState`) contenant sa taille actuelle et son nombre + * de filles, en interrogeant les membres déjà assignés en base de données. + * + * @param candidateTeams - Liste des équipes à analyser. + * @returns Un tableau d'états d'équipe (taille + nombre de filles), + * ou un tableau vide si aucune équipe n'est fournie. + */ +const buildTeamStates = async (candidateTeams: TeamRow[]) => { + if (candidateTeams.length === 0) { + return [] as TeamDistributionState[]; + } + + const teamIds = candidateTeams.map((team) => team.teamId); + const assignedMembers = await db + .select({ + teamId: userTeamsSchema.team_id, + male: userSchema.male, + }) + .from(userTeamsSchema) + .innerJoin(userSchema, eq(userSchema.id, userTeamsSchema.user_id)) + .where(inArray(userTeamsSchema.team_id, teamIds)); + + const teamStates = new Map(); + + for (const team of candidateTeams) { + teamStates.set(team.teamId, { + ...team, + size: 0, + girlsCount: 0, + }); + } + + for (const member of assignedMembers) { + const teamState = teamStates.get(member.teamId); + + if (!teamState) { + continue; + } + + teamState.size += 1; + + const maleValue: unknown = member.male; + + if (maleValue === false || maleValue === 0) { + teamState.girlsCount += 1; + } + } + + return Array.from(teamStates.values()); +}; + +/** + * Répartit une liste d'utilisateurs (étudiants) dans les équipes fournies, + * en cherchant à équilibrer à la fois la taille des équipes et la mixité + * (nombre de filles par équipe). + * + * Pour chaque utilisateur : + * 1. On détermine s'il s'agit d'une fille. + * 2. On sélectionne la meilleure équipe selon `isBetterTeam`. + * 3. On l'ajoute à cette équipe en base de données. + * 4. On déclenche une notification d'assignation. + * 5. On met à jour l'état local de l'équipe (taille et nombre de filles) + * pour que les assignations suivantes en tiennent compte. + * + * Ne fait rien si la liste des équipes ou des utilisateurs est vide. + * + * @param users - Liste des étudiants à assigner. + * @param teams - Liste des équipes disponibles. + * @param addNotification - Callback appelé après chaque assignation + * pour notifier l'utilisateur de son équipe. + */ +async function assignUsersToTeams( + users: StudentRow[], + teams: TeamRow[], + addNotification: (notification: TeamAssignmentNotification) => Promise, +) { + if (teams.length === 0 || users.length === 0) return; + + const teamStates = await buildTeamStates(teams); + + if (teamStates.length === 0) return; + + for (const user of users) { + const female = isFemale(user); + + let bestTeam = teamStates[0]; + + for (let index = 1; index < teamStates.length; index += 1) { + const candidate = teamStates[index]; + + if (isBetterTeam(candidate, bestTeam, female)) { + bestTeam = candidate; + } + } + + await addTeamMember(bestTeam.teamId, user.userId); + + await addNotification({ + email: user.email, + teamId: bestTeam.teamId, + }); + + bestTeam.size += 1; + + if (female) { + bestTeam.girlsCount += 1; + } + } +} + +export default assignUsersToTeams; diff --git a/backend/src/services/team/email.team.ts b/backend/src/services/team/email.team.ts new file mode 100644 index 0000000..0bb39fe --- /dev/null +++ b/backend/src/services/team/email.team.ts @@ -0,0 +1,106 @@ +import { eq, inArray } from 'drizzle-orm'; +import { db } from '../../database/db'; +import { factionSchema } from '../../schemas/Basic/faction.schema'; +import { teamSchema } from '../../schemas/Basic/team.schema'; +import { teamFactionSchema } from '../../schemas/Relational/teamfaction.schema'; +import { generateEmailHtml, sendEmail } from '../email.service'; +import type { TeamAssignmentNotification } from '../../dto/team.dto'; +import { email_from, email_concurrency } from '../../utils/secret'; +import type { TeamAssignmentEmailData } from '../../../types/email'; +import getPLimit from '../../utils/pLimit'; + +/** + * Envoie un email de notification à chaque étudiant venant d'être + * assigné à une équipe, l'informant de son équipe et de sa faction. + * + * Fonctionnement : + * 1. Récupère en base les informations (nom d'équipe + nom de faction) + * pour chaque équipe unique concernée par les notifications, afin + * d'éviter les requêtes redondantes. + * 2. Pré-génère et met en cache le HTML de l'email pour chaque équipe + * (un seul rendu de template par équipe, même si plusieurs élèves + * y sont assignés). + * 3. Envoie les emails en parallèle avec une limite de concurrence + * (`email_concurrency`) afin de ne pas surcharger le service d'envoi. + * 4. Logue les échecs d'envoi sans interrompre le traitement des autres + * emails (utilisation de `Promise.allSettled`). + * + * Ne fait rien si la liste de notifications est vide. + * + * @param notifications - Liste des notifications d'assignation + * (email de l'étudiant + identifiant de l'équipe) + * pour lesquelles envoyer un email. + * @throws Ne rejette pas globalement : les erreurs d'envoi individuelles + * sont capturées et simplement loguées dans la console. + */ +const sendEmailToNewAssignedStudents = async (notifications: TeamAssignmentNotification[]) => { + if (notifications.length === 0) { + return; + } + + // Récupération des équipes uniques + const uniqueTeamIds = [...new Set(notifications.map((n) => n.teamId))]; + + // Récupération de toutes les équipes en DB + const teams = await db + .select({ + teamId: teamSchema.id, + teamName: teamSchema.name, + factionName: factionSchema.name, + }) + .from(teamSchema) + .innerJoin(teamFactionSchema, eq(teamFactionSchema.team_id, teamSchema.id)) + .innerJoin(factionSchema, eq(factionSchema.id, teamFactionSchema.faction_id)) + .where(inArray(teamSchema.id, uniqueTeamIds)); + + // Cache de données pour éviter les executions multiples de fonctions + const teamCache = new Map(); + const htmlCache = new Map(); + + // Génération des datas et du template mail pour chaque équipe concernée + for (const team of teams) { + const data = { + teamName: team.teamName, + factionName: team.factionName, + }; + + teamCache.set(team.teamId, data); + + htmlCache.set(team.teamId, generateEmailHtml('templateNotifyTeamAssignment', data)); + } + + const pLimit = await getPLimit(); + const limit = pLimit(Number(email_concurrency)); + + const results = await Promise.allSettled( + notifications.map((notification) => + limit(async () => { + const html = htmlCache.get(notification.teamId); + + if (!html) { + throw new Error(`No email template found for team ${notification.teamId}`); + } + + await sendEmail({ + from: email_from, + to: [notification.email], + subject: '[EN BELOW] Tu as été affecté à une équipe !', + text: '', + html, + }); + }), + ), + ); + + // Log des erreurs + const failures = results.filter((result): result is PromiseRejectedResult => result.status === 'rejected'); + + if (failures.length > 0) { + console.error( + `${failures.length} email(s) failed to send.`, + failures.map((f) => f.reason), + ); + } +}; + +export default sendEmailToNewAssignedStudents; diff --git a/backend/src/utils/pLimit.ts b/backend/src/utils/pLimit.ts new file mode 100644 index 0000000..192c54e --- /dev/null +++ b/backend/src/utils/pLimit.ts @@ -0,0 +1,12 @@ +import type pLimit from 'p-limit' with { 'resolution-mode': 'import' }; + +let pLimitPromise: Promise | null = null; + +function getPLimit() { + if (!pLimitPromise) { + pLimitPromise = import('p-limit').then((mod) => mod.default); + } + return pLimitPromise; +} + +export default getPLimit; diff --git a/backend/src/utils/responses.ts b/backend/src/utils/responses.ts index acc1ff8..af22a98 100644 --- a/backend/src/utils/responses.ts +++ b/backend/src/utils/responses.ts @@ -1,6 +1,6 @@ import { type Response } from 'express'; -export const Error = (res: Response, details: { error?: any, msg?: string }) => { +export const Error = (res: Response, details: { error?: any; msg?: string }) => { if (details.error) { console.error('Error:', details.error); } @@ -8,42 +8,46 @@ export const Error = (res: Response, details: { error?: any, msg?: string }) => return res.status(Code.BAD_REQUEST).json(new HttpResponse(Code.BAD_REQUEST, msg)); }; -export const Created = (res: Response, details: { data?: any, msg?: string }) => { +export const Created = (res: Response, details: { data?: any; msg?: string }) => { const msg = details.msg || 'Entity created'; res.status(Code.CREATED).json(new HttpResponse(Code.CREATED, msg, details.data)); }; -export const Ok = (res: Response, details: { data?: any, msg?: string }) => { +export const Ok = (res: Response, details: { data?: any; msg?: string }) => { const msg = details.msg || 'Ok'; res.status(Code.OK).json(new HttpResponse(Code.OK, msg, details.data)); }; -export const Accepted = (res: Response, details: { data?: any, msg?: string }) => { +export const Accepted = (res: Response, details: { data?: any; msg?: string }) => { const msg = details.msg || 'Accepted'; - res.status(Code.OK).json(new HttpResponse(Code.ACCEPTED, msg, details.data)); + res.status(Code.ACCEPTED).json(new HttpResponse(Code.ACCEPTED, msg, details.data)); }; -export const Unauthorized = (res: Response, details: { data?: any, msg?: string }) => { +export const Unauthorized = (res: Response, details: { data?: any; msg?: string }) => { const msg = details.msg || 'Unauthorized'; res.status(Code.UNAUTHORIZED).json(new HttpResponse(Code.UNAUTHORIZED, msg, details.data)); }; -export const Forbidden = (res: Response, details: { data?: any, msg?: string }) => { +export const Forbidden = (res: Response, details: { data?: any; msg?: string }) => { const msg = details.msg || 'Forbidden'; res.status(Code.FORBIDDEN).json(new HttpResponse(Code.FORBIDDEN, msg, details.data)); }; - -export const Conflict = (res: Response, details: { data?: any, msg?: string }) => { - const msg = details.msg || "Conflict"; +export const Conflict = (res: Response, details: { data?: any; msg?: string }) => { + const msg = details.msg || 'Conflict'; res.status(Code.CONFLICT).json(new HttpResponse(Code.CONFLICT, msg, details.data)); }; -export const Teapot = (res: Response, details: { data?: any, msg?: string }) => { +export const Teapot = (res: Response, details: { data?: any; msg?: string }) => { const msg = details.msg || "I'm a teapot"; res.status(Code.IM_A_TEAPOT).json(new HttpResponse(Code.IM_A_TEAPOT, msg, details.data)); }; +export const ServiceUnavailable = (res: Response, details: { data?: any; msg?: string }) => { + const msg = details.msg || 'Service Unavailable'; + res.status(Code.SERVICE_UNAVAILABLE).json(new HttpResponse(Code.SERVICE_UNAVAILABLE, msg, details.data)); +}; + export enum Code { OK = 200, CREATED = 201, @@ -54,14 +58,19 @@ export enum Code { NOT_FOUND = 404, CONFLICT = 409, IM_A_TEAPOT = 418, - ISE = 500 + ISE = 500, + SERVICE_UNAVAILABLE = 503, } export class HttpResponse { private timeStamp: string; private httpStatus: string; - constructor(private statusCode: Code, private message?: string, private data?: any) { + constructor( + private statusCode: Code, + private message?: string, + private data?: any, + ) { this.timeStamp = new Date().toLocaleString(); this.statusCode = statusCode; this.httpStatus = this.getStatus(); @@ -73,6 +82,8 @@ export class HttpResponse { switch (this.statusCode) { case Code.OK: return 'OK'; + case Code.ACCEPTED: + return 'ACCEPTED'; case Code.NOT_FOUND: return 'NOT_FOUND'; case Code.BAD_REQUEST: @@ -89,6 +100,8 @@ export class HttpResponse { return 'CONFLICT'; case Code.IM_A_TEAPOT: return 'IM_A_TEAPOT'; + case Code.SERVICE_UNAVAILABLE: + return 'SERVICE_UNAVAILABLE'; } } } diff --git a/backend/src/utils/secret.ts b/backend/src/utils/secret.ts index f7c51fc..e7f78e8 100644 --- a/backend/src/utils/secret.ts +++ b/backend/src/utils/secret.ts @@ -1,33 +1,34 @@ -import dotenv from "dotenv"; +import dotenv from 'dotenv'; dotenv.config(); -export const jwtSecret = process.env.JWT_SECRET || "default"; +export const jwtSecret = process.env.JWT_SECRET || 'default'; export const server_port = process.env.SERVER_PORT; -export const cas_login_url = process.env.CAS_LOGIN_URL || "default"; -export const cas_validate_url = process.env.CAS_VALIDATE_URL || "default"; -export const service_url = process.env.SERVICE_URL || "default"; -export const dev_db_url = process.env.DATABASE_URL || "default"; -export const postgres_password = process.env.POSTGRES_PASSWORD || "default"; -export const postgres_user = process.env.POSTGRES_USER || "default"; -export const postgres_port = process.env.POSTGRES_PORT || "default"; -export const postgres_db = process.env.POSTGRES_DB || "default"; -export const postgres_host = process.env.POSTGRES_HOST || "default"; -export const google_client_id = process.env.GOOGLE_CLIENT_ID || "default"; -export const google_client_secret = process.env.GOOGLE_CLIENT_SECRET || "default"; -export const google_client_uri = process.env.GOOGLE_REDIRECT_URI || "default"; -export const spreadsheet_id = process.env.SPREADSHEET_ID || "default"; -export const api_utt_username = process.env.API_UTT_USERNAME || "default"; -export const api_utt_password = process.env.API_UTT_PASSWORD || "default"; -export const api_utt_auth_url = process.env.API_UTT_AUTH_URL || "default"; -export const api_utt_admis_url = process.env.API_UTT_ADMIS_URL || "default"; -export const api_utt_admis_url_ismajor = process.env.API_UTT_ADMIS_URL_ISMAJOR || "default"; -export const email_host = process.env.EMAIL_HOST || "default"; -export const email_user = process.env.EMAIL_USER || "default"; -export const email_password = process.env.EMAIL_PASSWORD || "default"; -export const email_from = process.env.EMAIL_FROM || "default"; -export const discord_client_id = process.env.DISCORD_CLIENT_ID || "default"; -export const discord_client_secret = process.env.DISCORD_CLIENT_SECRET || "default"; -export const discord_redirect_uri = process.env.DISCORD_REDIRECT_URI || "default"; -export const shotgun_password = process.env.SHOTGUN_PASSWORD || ""; -export const automation_token = process.env.AUTOMATION_TOKEN || ""; +export const cas_login_url = process.env.CAS_LOGIN_URL || 'default'; +export const cas_validate_url = process.env.CAS_VALIDATE_URL || 'default'; +export const service_url = process.env.SERVICE_URL || 'default'; +export const dev_db_url = process.env.DATABASE_URL || 'default'; +export const postgres_password = process.env.POSTGRES_PASSWORD || 'default'; +export const postgres_user = process.env.POSTGRES_USER || 'default'; +export const postgres_port = process.env.POSTGRES_PORT || 'default'; +export const postgres_db = process.env.POSTGRES_DB || 'default'; +export const postgres_host = process.env.POSTGRES_HOST || 'default'; +export const google_client_id = process.env.GOOGLE_CLIENT_ID || 'default'; +export const google_client_secret = process.env.GOOGLE_CLIENT_SECRET || 'default'; +export const google_client_uri = process.env.GOOGLE_REDIRECT_URI || 'default'; +export const spreadsheet_id = process.env.SPREADSHEET_ID || 'default'; +export const api_utt_username = process.env.API_UTT_USERNAME || 'default'; +export const api_utt_password = process.env.API_UTT_PASSWORD || 'default'; +export const api_utt_auth_url = process.env.API_UTT_AUTH_URL || 'default'; +export const api_utt_admis_url = process.env.API_UTT_ADMIS_URL || 'default'; +export const api_utt_admis_url_ismajor = process.env.API_UTT_ADMIS_URL_ISMAJOR || 'default'; +export const email_host = process.env.EMAIL_HOST || 'default'; +export const email_user = process.env.EMAIL_USER || 'default'; +export const email_password = process.env.EMAIL_PASSWORD || 'default'; +export const email_from = process.env.EMAIL_FROM || 'default'; +export const email_concurrency = process.env.EMAIL_CONCURRENCY || 20; +export const discord_client_id = process.env.DISCORD_CLIENT_ID || 'default'; +export const discord_client_secret = process.env.DISCORD_CLIENT_SECRET || 'default'; +export const discord_redirect_uri = process.env.DISCORD_REDIRECT_URI || 'default'; +export const shotgun_password = process.env.SHOTGUN_PASSWORD || ''; +export const automation_token = process.env.AUTOMATION_TOKEN || ''; diff --git a/backend/types/email.d.ts b/backend/types/email.d.ts index 62d2d79..a9375d8 100644 --- a/backend/types/email.d.ts +++ b/backend/types/email.d.ts @@ -24,3 +24,10 @@ export interface PermanenceEmailData extends TemplateData { permLocation: string; permDescription: string; } + +export interface TeamAssignmentEmailData extends TemplateData { + teamName: string; + factionName: string; +} + +export type TemplatesRecordByNumber = Record; diff --git a/frontend/.env.exemple b/frontend/.env.exemple index 4095081..19a87e9 100644 --- a/frontend/.env.exemple +++ b/frontend/.env.exemple @@ -2,4 +2,19 @@ VITE_ROADBOOK_URL_FRENCH= VITE_ROADBOOK_URL_ENGLISH= VITE_SERVICE_URL=https://localhost:4000 VITE_API_URL =http://localhost:4001/api -VITE_CAS_LOGIN_URL=https://auth.utt.fr/cas/login \ No newline at end of file +VITE_CAS_LOGIN_URL=https://auth.utt.fr/cas/login +VITE_ASSOCIATION_ADDRESS=UNG_ADRESSE +VITE_ASSOCIATION_EMAIL=UNG_EMAIL +VITE_ASSOCIATION_NAME=UNG_LE_NOM +VITE_ASSOCIATION_PHONE=+33ung +VITE_ASSOCIATION_RNA=UNG_RNA +VITE_ASSOCIATION_RCS=UNG_RCS +VITE_BDE_ADDRESS=BDE_ADRESSE +VITE_BDE_EMAIL=BDE_EMAIL +VITE_BDE_NAME=BDE_LE_NOM +VITE_BDE_PHONE=+33bde +VITE_BDE_RNA=BDE_RNA +VITE_BDE_SIRET=BDE_SIRET +VITE_BDE_SIREN=BDE_SIREN +VITE_DPO_EMAIL=DPO_EMAIL +VITE_DPO_NAME=DPO_NOM diff --git a/frontend/src/components/Admin/adminTeam.tsx b/frontend/src/components/Admin/adminTeam.tsx index 01f89c3..2afd7b4 100644 --- a/frontend/src/components/Admin/adminTeam.tsx +++ b/frontend/src/components/Admin/adminTeam.tsx @@ -31,6 +31,7 @@ export const AdminTeamManagement = () => { const [editName, setEditName] = useState(''); const [editType, setEditType] = useState(''); const [editFactionId, setEditFactionId] = useState(null); + const [editSocialLink, setEditSocialLink] = useState(''); const [editLeaders, setEditLeaders] = useState([]); const [editNewMembers, setEditNewMembers] = useState([]); @@ -72,6 +73,7 @@ export const AdminTeamManagement = () => { setEditName(team.name); setEditType(team.type); setEditFactionId(faction || null); + setEditSocialLink(team.socialLink || ''); setEditLeaders(members.filter((m) => m.permission !== 'Nouveau').map((m) => m.userId)); setEditNewMembers(members.filter((m) => m.permission === 'Nouveau').map((m) => m.userId)); } @@ -101,6 +103,7 @@ export const AdminTeamManagement = () => { teamID: selectedTeamId, teamName: editName, factionID: editFactionId, + socialLink: editSocialLink, teamMembers: [...editLeaders, ...editNewMembers], type: editType, }); @@ -252,6 +255,12 @@ export const AdminTeamManagement = () => { className="w-full md:w-96 mx-auto" placeholder="Sélectionner une faction" /> + setEditSocialLink(e.target.value)} + className="w-full md:w-96 mx-auto" + placeholder="Lien groupe de discussion (https://...)" + />

👨‍💼 Chefs d'équipe

diff --git a/frontend/src/components/home/infosSection.tsx b/frontend/src/components/home/infosSection.tsx index 7f11941..b9641c2 100644 --- a/frontend/src/components/home/infosSection.tsx +++ b/frontend/src/components/home/infosSection.tsx @@ -1,11 +1,12 @@ -import "swiper/swiper-bundle.css"; +import 'swiper/swiper-bundle.css'; -import { Link } from "react-router-dom"; -import { Autoplay, Pagination } from "swiper/modules"; -import { Swiper, SwiperSlide } from "swiper/react"; +import { Link } from 'react-router-dom'; +import { Autoplay, Pagination } from 'swiper/modules'; +import { Swiper, SwiperSlide } from 'swiper/react'; -import { Button } from "../ui/button"; -import { RevealSection } from "../ui/revealSection"; +import { Button } from '../ui/button'; +import { RevealSection } from '../ui/revealSection'; +import { Team } from './teamSection'; export const Infos = () => (
@@ -20,7 +21,7 @@ export const Infos = () => ( className="w-full h-full" effect="fade" // Smooth transition de fond > - {["Home1", "Home2", "Home3", "Home4"].map((img, i) => ( + {['Home1', 'Home2', 'Home3', 'Home4'].map((img, i) => ( ( {/* SVG vague bas */}
- +
+ {/* Team */} + + + {/* Texte d'info */}
- +

L'intégration, c'est quoi ?

- C'est l'événement où l'ensemble des étudiants de l'UTT se mobilise pour concocter aux nouveaux (comme toi) une incroyable semaine durant laquelle tu découvriras la vie sur le campus, rencontreras une tonne de nouveaux amis et démarreras ta nouvelle vie d'étudiant ! Le tout se fait dans la bonne humeur et avec bienveillance, l'objectif c'est juste de s'amuser à fond ! + C'est l'événement où l'ensemble des étudiants de l'UTT se mobilise pour concocter aux + nouveaux (comme toi) une incroyable semaine durant laquelle tu découvriras la vie sur le + campus, rencontreras une tonne de nouveaux amis et démarreras ta nouvelle vie d'étudiant ! + Le tout se fait dans la bonne humeur et avec bienveillance, l'objectif c'est juste de + s'amuser à fond !

- +

La petite histoire

- Chaque année, deux factions composées d'une multitude d'équipes s'affrontent. Le thème de cette année : Divinités vs Monstres. + Chaque année, deux factions composées d'une multitude d'équipes s'affrontent. Le thème de + cette année : Divinités vs Monstres.

- +

Ce site, c'est quoi ?

- C'est ici que tu trouveras toutes les informations nécessaires au déroulement de la semaine d'inté. Par exemple, tu pourras prendre ta place pour le WEI ou regarder quelle faction est la plus proche de la victoire. + C'est ici que tu trouveras toutes les informations nécessaires au déroulement de la semaine + d'inté. Par exemple, tu pourras prendre ta place pour le WEI ou regarder quelle faction est + la plus proche de la victoire.

- +

Le Roadbook de l'inté

- Retrouve ici toutes les informations de l'intégration ! Les plannings, la prévention, les activités, les menus… Tout pour passer des moments incroyables ! + Retrouve ici toutes les informations de l'intégration ! Les plannings, la prévention, les + activités, les menus… Tout pour passer des moments incroyables !

@@ -109,9 +116,7 @@ export const Infos = () => ( {/* Roulette des partenaires */}
-

- Nos Partenaires -

+

Nos Partenaires

( }} autoplay={{ delay: 3000, disableOnInteraction: false }} loop={true} - className="px-4" - > + className="px-4"> {[ - { logo: "CIC.png", name: "CIC", link: "https://www.cic.fr/fr/particuliers/comptes/jeunes-pouvoir-avancer.html" }, - { logo: "CVEC.png", name: "CVEC", link: "https://www.crous-reims.fr/le-crous/la-contribution-vie-etudiante-et-de-campus-cvec-une-demarche-obligatoire-et-utile-pour-les-etudiants/" }, - { logo: "DAMONTE.png", name: "DAMONTE", link: "https://www.yves-damonte.fr/" }, - { logo: "FONDATIONUTT.png", name: "Fondation UTT", link: "https://fondation.utt.fr/" }, - { logo: "POPEYE.png", name: "POPEYE", link: "https://popeye-troyes.fr/" }, - { logo: "GRANDEST.png", name: "GRANDEST", link: "https://www.grandest.fr/vos-aides-regionales/jeunest-15-29-ans/" }, - { logo: "UTT.png", name: "UTT", link: "https://utt.fr/" }, - { logo: "SECUTT.png", name: "SECUTT", link: "https://ffssaube.fr/secutt/" }, + { + logo: 'CIC.png', + name: 'CIC', + link: 'https://www.cic.fr/fr/particuliers/comptes/jeunes-pouvoir-avancer.html', + }, + { + logo: 'CVEC.png', + name: 'CVEC', + link: 'https://www.crous-reims.fr/le-crous/la-contribution-vie-etudiante-et-de-campus-cvec-une-demarche-obligatoire-et-utile-pour-les-etudiants/', + }, + { logo: 'DAMONTE.png', name: 'DAMONTE', link: 'https://www.yves-damonte.fr/' }, + { logo: 'FONDATIONUTT.png', name: 'Fondation UTT', link: 'https://fondation.utt.fr/' }, + { logo: 'POPEYE.png', name: 'POPEYE', link: 'https://popeye-troyes.fr/' }, + { + logo: 'GRANDEST.png', + name: 'GRANDEST', + link: 'https://www.grandest.fr/vos-aides-regionales/jeunest-15-29-ans/', + }, + { logo: 'UTT.png', name: 'UTT', link: 'https://utt.fr/' }, + { logo: 'SECUTT.png', name: 'SECUTT', link: 'https://ffssaube.fr/secutt/' }, ].map((partner, i) => ( + className="surface-card p-4 flex items-center justify-center w-40 h-28 sm:w-48 sm:h-32 transition-transform duration-300 hover:scale-105"> {`Logo { + const [teamInfos, setTeamInfos] = useState(null); + const [loading, setLoading] = useState(true); + + useEffect(() => { + fetchTeam(); + }, []); + + const fetchTeam = async () => { + const team = await getUserTeam(); + setTeamInfos(team); + setLoading(false); + }; + + if (loading) { + return ( +
+ + +

Chargement de ton équipe...

+
+
+
+ ); + } + + if (!teamInfos) { + return ( +
+ + + + Ton équipe arrive bientôt ! + + + +

+ Tu n'as pas encore été placé(e) dans une équipe d'intégration. +
+ Pas de panique ! Ce sera bientôt le cas et tu pourras rejoindre le groupe WhatsApp de ton + équipe pour ne rien manquer des infos et activités à venir ! +

+
+
+
+ ); + } + + return ( +
+ + + + Découvre ton équipe d'intégration ! + + + +

Tu as été placé(e) dans l'équipe

+

+ + {teamInfos.name} + +
+ Faction + + {' ' + teamInfos.faction_name} + +

+

+ Cette équipe est consituée de Chefs d'équipes (CE), déjà étudiants à l'UTT, et d'un certain + nombre de Nouveaux comme toi ! +
+ L'idéal pour découvrir la vie à Troyes, sur le Campus, rencontrer tes futurs amis et réaliser + les défis d'Intégration ! +

+

+ Rejoins dès maintenant le groupe WhatsApp de ton équipe pour ne rien manquer des infos et + activités à venir ! +

+ {teamInfos.social_link ? ( + + ) : ( + + )} +
+
+
+ ); +}; diff --git a/frontend/src/components/profil/profilForm.tsx b/frontend/src/components/profil/profilForm.tsx index 368fc14..6eba1b0 100644 --- a/frontend/src/components/profil/profilForm.tsx +++ b/frontend/src/components/profil/profilForm.tsx @@ -1,34 +1,34 @@ -import { useEffect, useState } from "react"; -import { FaDiscord } from "react-icons/fa"; -import Select from "react-select"; +import { useEffect, useState } from 'react'; +// import { FaDiscord } from 'react-icons/fa'; +import Select from 'react-select'; -import { type User } from "../../interfaces/user.interface"; -import { getCurrentUser, updateCurrentUser } from "../../services/requests/user.service"; -import { Button } from "../ui/button"; -import { Card, CardContent, CardHeader, CardTitle } from "../ui/card"; -import { Input } from "../ui/input"; +import { type User } from '../../interfaces/user.interface'; +import { getCurrentUser, updateCurrentUser } from '../../services/requests/user.service'; +import { Button } from '../ui/button'; +import { Card, CardContent, CardHeader, CardTitle } from '../ui/card'; +import { Input } from '../ui/input'; const branchOptions = [ - { value: "TC", label: "Tronc Commun" }, - { value: "RT", label: "Réseaux et Télcommunications" }, - { value: "ISI", label: "Informatique et Systèmes d'Information" }, - { value: "GM", label: "Génie Mécanique" }, - { value: "GI", label: "Génie Industriel" }, - { value: "MTE", label: "Matériaux : Technologie et Economie" }, - { value: "A2I", label: "Automatique & Informatique Industrielle" }, - { value: "GI_APPR", label: "Génie Industriel en Apprentissage" }, - { value: "GM_APPR", label: "Génie Mécanique en Apprentissage" }, - { value: "SN_APPR", label: "Systeme Numérique en Apprentissage" }, - { value: "Branch", label: "Branche" }, - { value: "MM", label: "Mécanique et Matériaux" }, - { value: "Master", label: "Master" }, - { value: "RI", label: "Ressources International" }, + { value: 'TC', label: 'Tronc Commun' }, + { value: 'RT', label: 'Réseaux et Télcommunications' }, + { value: 'ISI', label: "Informatique et Systèmes d'Information" }, + { value: 'GM', label: 'Génie Mécanique' }, + { value: 'GI', label: 'Génie Industriel' }, + { value: 'MTE', label: 'Matériaux : Technologie et Economie' }, + { value: 'A2I', label: 'Automatique & Informatique Industrielle' }, + { value: 'GI_APPR', label: 'Génie Industriel en Apprentissage' }, + { value: 'GM_APPR', label: 'Génie Mécanique en Apprentissage' }, + { value: 'SN_APPR', label: 'Systeme Numérique en Apprentissage' }, + { value: 'Branch', label: 'Branche' }, + { value: 'MM', label: 'Mécanique et Matériaux' }, + { value: 'Master', label: 'Master' }, + { value: 'RI', label: 'Ressources International' }, ]; export const ProfilForm = () => { const [user, setUser] = useState(null); - const [branch, setBranch] = useState(""); - const [contact, setContact] = useState(""); + const [branch, setBranch] = useState(''); + const [contact, setContact] = useState(''); const [loading, setLoading] = useState(false); useEffect(() => { @@ -36,8 +36,8 @@ export const ProfilForm = () => { try { const me = await getCurrentUser(); setUser(me); - setBranch(me.branch || ""); - setContact(me.contact || ""); + setBranch(me.branch || ''); + setContact(me.contact || ''); } catch (err) { console.error("Erreur lors de la récupération de l'utilisateur", err); } @@ -61,23 +61,33 @@ export const ProfilForm = () => {
- +
- +
- +
- +
- + setContact(e.target.value)} className={user.contact === null || user.contact === "" ? "bg-white" : ""} /> + setContact(e.target.value)} + className={user.contact === null || user.contact === '' ? 'bg-white' : ''} + />
- <> + {/* <> {user.discord_id ? (
✅ Ton compte Discord est bien lié ! @@ -113,15 +133,14 @@ export const ProfilForm = () => { )} - + */} ); diff --git a/frontend/src/interfaces/team.interface.ts b/frontend/src/interfaces/team.interface.ts index 3e34ed0..2e07009 100644 --- a/frontend/src/interfaces/team.interface.ts +++ b/frontend/src/interfaces/team.interface.ts @@ -1,7 +1,16 @@ export interface Team { - faction_id: any; + faction_id: number; teamId: number; name: string; description: string; type: string; + socialLink: string; +} + +export interface TeamDisplayInfos { + id: number; + name: string; + faction_id: number; + faction_name: string; + social_link: string; } diff --git a/frontend/src/pages/home.tsx b/frontend/src/pages/home.tsx index 54d9601..23e7c93 100644 --- a/frontend/src/pages/home.tsx +++ b/frontend/src/pages/home.tsx @@ -1,7 +1,7 @@ -import { Footer } from "../components/footer"; -import { Infos } from "../components/home/infosSection"; -import { SocialLinks } from "../components/home/socialSection"; -import { Navbar } from "../components/navbar"; +import { Footer } from '../components/footer'; +import { Infos } from '../components/home/infosSection'; +import { SocialLinks } from '../components/home/socialSection'; +import { Navbar } from '../components/navbar'; const HomePage = () => (
diff --git a/frontend/src/services/api.ts b/frontend/src/services/api.ts index e2a1161..693559d 100644 --- a/frontend/src/services/api.ts +++ b/frontend/src/services/api.ts @@ -1,22 +1,22 @@ -import axios, { type AxiosError, type AxiosResponse } from "axios"; +import axios, { type AxiosError, type AxiosResponse } from 'axios'; export const api = axios.create({ baseURL: import.meta.env.VITE_API_URL, headers: { - "Content-Type": "application/json", + 'Content-Type': 'application/json', }, }); // Intercepteur de requête : Ajoute le token api.interceptors.request.use( (config) => { - const token = localStorage.getItem("authToken")?.trim(); + const token = localStorage.getItem('authToken')?.trim(); if (token) { config.headers.Authorization = `Bearer ${token}`; } return config; }, - (error) => Promise.reject(error) + (error) => Promise.reject(error), ); // Intercepteur de réponse : Gestion des erreurs globales @@ -24,23 +24,21 @@ api.interceptors.response.use( (response) => response, (error: AxiosError) => { if (error.response?.status === 401) { - console.warn("🔑 Token expiré ou invalide, redirection..."); - window.location.replace("/"); + console.warn('🔑 Token expiré ou invalide, redirection...'); + window.location.replace('/'); } return Promise.reject(error); - } + }, ); // 🔧 Utilitaire pour extraire data.data et gérer les erreurs -export const handleApiResponse = async ( - request: Promise> -): Promise => { +export const handleApiResponse = async (request: Promise>): Promise => { try { const response = await request; return response.data.data; } catch (error: any) { - const message = error?.response?.data?.message || error.message || "Erreur API"; - console.error("❌ API Error:", message); + const message = error?.response?.data?.message || error.message || 'Erreur API'; + console.error('❌ API Error:', message); throw new Error(message); } }; diff --git a/frontend/src/services/requests/team.service.ts b/frontend/src/services/requests/team.service.ts index c666b9b..124e70b 100644 --- a/frontend/src/services/requests/team.service.ts +++ b/frontend/src/services/requests/team.service.ts @@ -1,12 +1,17 @@ -import api from "../api"; +import api from '../api'; export const createTeam = async (teamName: string, members: number[]) => { const response = await api.post(`team/user/create`, { teamName, members }); return response.data; }; +export const getUserTeam = async () => { + const res = await api.get(`/team/user/me`); + return res.data.data; +}; + export const getAllTeams = async () => { - const res = await api.get("/team/admin/teams"); + const res = await api.get('/team/admin/teams'); return res.data.data; }; @@ -24,24 +29,25 @@ export const updateTeam = async (payload: { teamID: number | null; teamName: string | null; factionID: number | null; + socialLink: string | null; teamMembers: number[] | null; type: string | null; }) => { - const res = await api.put("/team/admin/modify", payload); + const res = await api.put('/team/admin/modify', payload); return res.data; }; export const deleteTeam = async (teamID: number) => { - const res = await api.delete("/team/admin/delete", { params: { teamID } }); + const res = await api.delete('/team/admin/delete', { params: { teamID } }); return res.data; }; export const createTeamLight = async (payload: { teamName: string; factionId: number | null }) => { - const res = await api.post("/team/admin/createlight", payload); + const res = await api.post('/team/admin/createlight', payload); return res.data; }; export const teamDistribution = async () => { - const response = await api.post("team/admin/distributeteam"); - return response.data + const response = await api.post('team/admin/distributeteam'); + return response.data; }; diff --git a/node_modules/.bin/husky b/node_modules/.bin/husky new file mode 100755 index 0000000..d65de15 --- /dev/null +++ b/node_modules/.bin/husky @@ -0,0 +1,43 @@ +#!/bin/sh +basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") +basedir_win="$basedir" +exe="" +msys="" + +case `uname -a` in + *CYGWIN*|*MINGW*|*MSYS*) + if command -v cygpath > /dev/null 2>&1; then + basedir_win=`cygpath -w "$basedir"` + fi + exe=".exe" + msys="true" + ;; + *WSL2*) + if command -v wslpath > /dev/null 2>&1; then + basedir_win="$(wslpath -w "$basedir" 2> /dev/null)" + if [ $? -ne 0 ] || [ -z "$basedir_win" ]; then + basedir_win="$basedir" + else + exe=".exe" + fi + fi + ;; +esac + +if [ -z "$NODE_PATH" ]; then + export NODE_PATH="/home/dodin/Documents/integration/Integration/node_modules/.pnpm/husky@9.1.7/node_modules/husky/node_modules:/home/dodin/Documents/integration/Integration/node_modules/.pnpm/husky@9.1.7/node_modules:/home/dodin/Documents/integration/Integration/node_modules/.pnpm/node_modules" +else + export NODE_PATH="/home/dodin/Documents/integration/Integration/node_modules/.pnpm/husky@9.1.7/node_modules/husky/node_modules:/home/dodin/Documents/integration/Integration/node_modules/.pnpm/husky@9.1.7/node_modules:/home/dodin/Documents/integration/Integration/node_modules/.pnpm/node_modules:$NODE_PATH" +fi +if [ -n "$exe" ] && [ -x "$basedir/node.exe" ]; then + exec "$basedir/node.exe" "$basedir_win/../.pnpm/husky@9.1.7/node_modules/husky/bin.js" "$@" +elif [ -x "$basedir/node" ]; then + exec "$basedir/node" "$basedir/../.pnpm/husky@9.1.7/node_modules/husky/bin.js" "$@" +elif command -v node >/dev/null 2>&1; then + exec node "$basedir/../.pnpm/husky@9.1.7/node_modules/husky/bin.js" "$@" +elif [ -n "$exe" ] && command -v node.exe >/dev/null 2>&1; then + exec node.exe "$basedir_win/../.pnpm/husky@9.1.7/node_modules/husky/bin.js" "$@" +else + exec node "$basedir/../.pnpm/husky@9.1.7/node_modules/husky/bin.js" "$@" +fi +# cmd-shim-target=/home/dodin/Documents/integration/Integration/node_modules/.pnpm/husky@9.1.7/node_modules/husky/bin.js diff --git a/node_modules/.modules.yaml b/node_modules/.modules.yaml new file mode 100644 index 0000000..544b294 --- /dev/null +++ b/node_modules/.modules.yaml @@ -0,0 +1,26 @@ +{ + "hoistedDependencies": {}, + "hoistPattern": [ + "*" + ], + "included": { + "dependencies": true, + "devDependencies": true, + "optionalDependencies": true + }, + "injectedDeps": {}, + "layoutVersion": 5, + "nodeLinker": "isolated", + "packageManager": "pnpm@11.11.0", + "pendingBuilds": [], + "publicHoistPattern": [], + "prunedAt": "Sat, 11 Jul 2026 10:10:41 GMT", + "registries": { + "default": "https://registry.npmjs.org/", + "@jsr": "https://npm.jsr.io/" + }, + "skipped": [], + "storeDir": "/home/dodin/.local/share/pnpm/store/v11", + "virtualStoreDir": ".pnpm", + "virtualStoreDirMaxLength": 120 +} \ No newline at end of file diff --git a/node_modules/.package-map.json b/node_modules/.package-map.json new file mode 100644 index 0000000..93dc7b8 --- /dev/null +++ b/node_modules/.package-map.json @@ -0,0 +1 @@ +{"packages":{".":{"url":"..","dependencies":{"Integration":".","husky":"husky@9.1.7"}},"husky@9.1.7":{"url":"./.pnpm/husky@9.1.7/node_modules/husky","dependencies":{"husky":"husky@9.1.7"}}}} diff --git a/node_modules/.pnpm-workspace-state-v1.json b/node_modules/.pnpm-workspace-state-v1.json new file mode 100644 index 0000000..b08124e --- /dev/null +++ b/node_modules/.pnpm-workspace-state-v1.json @@ -0,0 +1,29 @@ +{ + "lastValidatedTimestamp": 1783764641456, + "projects": {}, + "pnpmfiles": [], + "settings": { + "autoInstallPeers": true, + "dedupeDirectDeps": false, + "dedupeInjectedDeps": true, + "dedupePeerDependents": true, + "dedupePeers": false, + "dev": true, + "excludeLinksFromLockfile": false, + "hoistPattern": [ + "*" + ], + "hoistWorkspacePackages": true, + "injectWorkspacePackages": false, + "linkWorkspacePackages": false, + "minimumReleaseAge": 1440, + "minimumReleaseAgeIgnoreMissingTime": true, + "nodeLinker": "isolated", + "optional": true, + "peersSuffixMaxLength": 1000, + "preferWorkspacePackages": false, + "production": true, + "publicHoistPattern": [] + }, + "filteredInstall": false +} diff --git a/node_modules/.pnpm/husky@9.1.7/node_modules/husky/LICENSE b/node_modules/.pnpm/husky@9.1.7/node_modules/husky/LICENSE new file mode 100644 index 0000000..049c9dc --- /dev/null +++ b/node_modules/.pnpm/husky@9.1.7/node_modules/husky/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 typicode + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/node_modules/.pnpm/husky@9.1.7/node_modules/husky/README.md b/node_modules/.pnpm/husky@9.1.7/node_modules/husky/README.md new file mode 100644 index 0000000..405cda7 --- /dev/null +++ b/node_modules/.pnpm/husky@9.1.7/node_modules/husky/README.md @@ -0,0 +1 @@ +https://typicode.github.io/husky diff --git a/node_modules/.pnpm/husky@9.1.7/node_modules/husky/bin.js b/node_modules/.pnpm/husky@9.1.7/node_modules/husky/bin.js new file mode 100755 index 0000000..244311b --- /dev/null +++ b/node_modules/.pnpm/husky@9.1.7/node_modules/husky/bin.js @@ -0,0 +1,26 @@ +#!/usr/bin/env node +import f, { writeFileSync as w } from 'fs' +import i from './index.js' + +let p, a, n, s, o, d + +p = process +a = p.argv[2] + +if (a == 'init') { + n = 'package.json' + s = f.readFileSync(n) + o = JSON.parse(s) + ;(o.scripts ||= {}).prepare = 'husky' + w(n, JSON.stringify(o, 0, /\t/.test(s) ? '\t' : 2) + '\n') + p.stdout.write(i()) + try { f.mkdirSync('.husky') } catch {} + w('.husky/pre-commit', (p.env.npm_config_user_agent?.split('/')[0] ?? 'npm') + ' test\n') + p.exit() +} + +d = c => console.error(`husky - ${c} command is DEPRECATED`) +if (['add', 'set', 'uninstall'].includes(a)) { d(a); p.exit(1) } +if (a == 'install') d(a) + +p.stdout.write(i(a == 'install' ? undefined : a)) diff --git a/node_modules/.pnpm/husky@9.1.7/node_modules/husky/husky b/node_modules/.pnpm/husky@9.1.7/node_modules/husky/husky new file mode 100644 index 0000000..bf7c896 --- /dev/null +++ b/node_modules/.pnpm/husky@9.1.7/node_modules/husky/husky @@ -0,0 +1,22 @@ +#!/usr/bin/env sh +[ "$HUSKY" = "2" ] && set -x +n=$(basename "$0") +s=$(dirname "$(dirname "$0")")/$n + +[ ! -f "$s" ] && exit 0 + +if [ -f "$HOME/.huskyrc" ]; then + echo "husky - '~/.huskyrc' is DEPRECATED, please move your code to ~/.config/husky/init.sh" +fi +i="${XDG_CONFIG_HOME:-$HOME/.config}/husky/init.sh" +[ -f "$i" ] && . "$i" + +[ "${HUSKY-}" = "0" ] && exit 0 + +export PATH="node_modules/.bin:$PATH" +sh -e "$s" "$@" +c=$? + +[ $c != 0 ] && echo "husky - $n script failed (code $c)" +[ $c = 127 ] && echo "husky - command not found in PATH=$PATH" +exit $c diff --git a/node_modules/.pnpm/husky@9.1.7/node_modules/husky/index.d.ts b/node_modules/.pnpm/husky@9.1.7/node_modules/husky/index.d.ts new file mode 100644 index 0000000..72a5495 --- /dev/null +++ b/node_modules/.pnpm/husky@9.1.7/node_modules/husky/index.d.ts @@ -0,0 +1 @@ +export default function (dir?: string): string; \ No newline at end of file diff --git a/node_modules/.pnpm/husky@9.1.7/node_modules/husky/index.js b/node_modules/.pnpm/husky@9.1.7/node_modules/husky/index.js new file mode 100644 index 0000000..bebf0d5 --- /dev/null +++ b/node_modules/.pnpm/husky@9.1.7/node_modules/husky/index.js @@ -0,0 +1,25 @@ +import c from 'child_process' +import f, { readdir, writeFileSync as w } from 'fs' +import p from 'path' + +let l = [ 'pre-commit', 'pre-merge-commit', 'prepare-commit-msg', 'commit-msg', 'post-commit', 'applypatch-msg', 'pre-applypatch', 'post-applypatch', 'pre-rebase', 'post-rewrite', 'post-checkout', 'post-merge', 'pre-push', 'pre-auto-gc' ], + msg = `echo "husky - DEPRECATED\n\nPlease remove the following two lines from $0:\n\n#!/usr/bin/env sh\n. \\"\\$(dirname -- \\"\\$0\\")/_/husky.sh\\"\n\nThey WILL FAIL in v10.0.0\n"` + +export default (d = '.husky') => { + if (process.env.HUSKY === '0') return 'HUSKY=0 skip install' + if (d.includes('..')) return '.. not allowed' + if (!f.existsSync('.git')) return `.git can't be found` + + let _ = (x = '') => p.join(d, '_', x) + let { status: s, stderr: e } = c.spawnSync('git', ['config', 'core.hooksPath', `${d}/_`]) + if (s == null) return 'git command not found' + if (s) return '' + e + + f.rmSync(_('husky.sh'), { force: true }) + f.mkdirSync(_(), { recursive: true }) + w(_('.gitignore'), '*') + f.copyFileSync(new URL('husky', import.meta.url), _('h')) + l.forEach(h => w(_(h), `#!/usr/bin/env sh\n. "\$(dirname "\$0")/h"`, { mode: 0o755 })) + w(_('husky.sh'), msg) + return '' +} diff --git a/node_modules/.pnpm/husky@9.1.7/node_modules/husky/node_modules/.bin/husky b/node_modules/.pnpm/husky@9.1.7/node_modules/husky/node_modules/.bin/husky new file mode 100755 index 0000000..0c238c8 --- /dev/null +++ b/node_modules/.pnpm/husky@9.1.7/node_modules/husky/node_modules/.bin/husky @@ -0,0 +1,43 @@ +#!/bin/sh +basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") +basedir_win="$basedir" +exe="" +msys="" + +case `uname -a` in + *CYGWIN*|*MINGW*|*MSYS*) + if command -v cygpath > /dev/null 2>&1; then + basedir_win=`cygpath -w "$basedir"` + fi + exe=".exe" + msys="true" + ;; + *WSL2*) + if command -v wslpath > /dev/null 2>&1; then + basedir_win="$(wslpath -w "$basedir" 2> /dev/null)" + if [ $? -ne 0 ] || [ -z "$basedir_win" ]; then + basedir_win="$basedir" + else + exe=".exe" + fi + fi + ;; +esac + +if [ -z "$NODE_PATH" ]; then + export NODE_PATH="/home/dodin/Documents/integration/Integration/node_modules/.pnpm/husky@9.1.7/node_modules/husky/node_modules:/home/dodin/Documents/integration/Integration/node_modules/.pnpm/husky@9.1.7/node_modules:/home/dodin/Documents/integration/Integration/node_modules/.pnpm/node_modules" +else + export NODE_PATH="/home/dodin/Documents/integration/Integration/node_modules/.pnpm/husky@9.1.7/node_modules/husky/node_modules:/home/dodin/Documents/integration/Integration/node_modules/.pnpm/husky@9.1.7/node_modules:/home/dodin/Documents/integration/Integration/node_modules/.pnpm/node_modules:$NODE_PATH" +fi +if [ -n "$exe" ] && [ -x "$basedir/node.exe" ]; then + exec "$basedir/node.exe" "$basedir_win/../../bin.js" "$@" +elif [ -x "$basedir/node" ]; then + exec "$basedir/node" "$basedir/../../bin.js" "$@" +elif command -v node >/dev/null 2>&1; then + exec node "$basedir/../../bin.js" "$@" +elif [ -n "$exe" ] && command -v node.exe >/dev/null 2>&1; then + exec node.exe "$basedir_win/../../bin.js" "$@" +else + exec node "$basedir/../../bin.js" "$@" +fi +# cmd-shim-target=/home/dodin/Documents/integration/Integration/node_modules/.pnpm/husky@9.1.7/node_modules/husky/bin.js diff --git a/node_modules/.pnpm/husky@9.1.7/node_modules/husky/package.json b/node_modules/.pnpm/husky@9.1.7/node_modules/husky/package.json new file mode 100644 index 0000000..b9d3810 --- /dev/null +++ b/node_modules/.pnpm/husky@9.1.7/node_modules/husky/package.json @@ -0,0 +1,25 @@ +{ + "name": "husky", + "version": "9.1.7", + "type": "module", + "description": "Modern native Git hooks", + "keywords": [ + "git", + "hooks", + "pre-commit" + ], + "repository": { + "type": "git", + "url": "git+https://github.com/typicode/husky.git" + }, + "funding": "https://github.com/sponsors/typicode", + "license": "MIT", + "author": "typicode", + "bin": { + "husky": "bin.js" + }, + "exports": "./index.js", + "engines": { + "node": ">=18" + } +} diff --git a/node_modules/.pnpm/lock.yaml b/node_modules/.pnpm/lock.yaml new file mode 100644 index 0000000..a177305 --- /dev/null +++ b/node_modules/.pnpm/lock.yaml @@ -0,0 +1,24 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + devDependencies: + husky: + specifier: ^9.1.7 + version: 9.1.7 + +packages: + + husky@9.1.7: + resolution: {integrity: sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==} + engines: {node: '>=18'} + hasBin: true + +snapshots: + + husky@9.1.7: {} diff --git a/node_modules/husky b/node_modules/husky new file mode 120000 index 0000000..69c483c --- /dev/null +++ b/node_modules/husky @@ -0,0 +1 @@ +.pnpm/husky@9.1.7/node_modules/husky \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..679af4e --- /dev/null +++ b/package.json @@ -0,0 +1,23 @@ +{ + "name": "Integration", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "prepare": "husky" + }, + "keywords": [], + "author": "", + "license": "ISC", + "devEngines": { + "packageManager": { + "name": "pnpm", + "version": "^11.10.0", + "onFail": "download" + } + }, + "type": "module", + "devDependencies": { + "husky": "^9.1.7" + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..40d12e2 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,223 @@ +--- +lockfileVersion: '9.0' + +importers: + + .: + configDependencies: {} + packageManagerDependencies: + '@pnpm/exe': + specifier: ^11.10.0 + version: 11.11.0 + pnpm: + specifier: ^11.10.0 + version: 11.11.0 + +packages: + + '@pnpm/exe@11.11.0': + resolution: {integrity: sha512-OpTrbkAU0Ur8gBwhAT6mbWwlA1bFU8zPcFNdp/img/RqFIc7Dn0j0crW9rz5hxTWo/UBQjy6Sb9jh+N5bRSd+g==} + hasBin: true + + '@pnpm/linux-arm64@11.11.0': + resolution: {integrity: sha512-62K/kQY3jaoQ96MNUi0NLcTSSDO2QrtziOA2IK5R/m+DxpSwXbxFSQHYx8oOg0r1+MFVqwuDkFjXe6DyW3y58g==} + cpu: [arm64] + os: [linux] + + '@pnpm/linux-x64@11.11.0': + resolution: {integrity: sha512-rwMbNJR+PstRu+ymWoApei1CWrAnsnW3tm+3H8qOxbp8duiaj6u7DxlMzhKbVpFwylxcJdeGwZ5tReBFOVpsdw==} + cpu: [x64] + os: [linux] + + '@pnpm/linuxstatic-arm64@11.11.0': + resolution: {integrity: sha512-OcmrMw1hxNee7KTBpE0yToTZziH/SCmJWwjB7YN2NmsxZVPKM2vtOWFdZufW0YN5JffKMaHbPZ2ulgYBM5qAQw==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@pnpm/linuxstatic-x64@11.11.0': + resolution: {integrity: sha512-pWeAYeS+PPah6mXcJbOr+nPwEpyQau4iPcsqNUhTK9G5/qpG5dU1m8oHeIH83PuUUvvdJddLE1PXUkGM+QCI6g==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@pnpm/macos-arm64@11.11.0': + resolution: {integrity: sha512-62P8Pe4yvMkdl2nxswCjM5X835i9Judk68CZvv8OnWsm7CVCEZ61SEBnNTpjJ4kXzdayFjRpPwE/jfJSvPaWww==} + cpu: [arm64] + os: [darwin] + + '@pnpm/win-arm64@11.11.0': + resolution: {integrity: sha512-yYAx+A1oT+mSgrIzysuAvdYYMYEfAa+z3/UCGY3L5VC9oabs9qi71LbTan1cDui/AadrIvD177k9mV4V38KAJg==} + cpu: [arm64] + os: [win32] + + '@pnpm/win-x64@11.11.0': + resolution: {integrity: sha512-ehTuyM5Rrp4ye0SdtAJkUS+9ykfwdDY9SPqiK3+bxXPx3LD5aeDw2EBksTh/xRTgqFdYmoFR86cABFt9yBr0GA==} + cpu: [x64] + os: [win32] + + '@reflink/reflink-darwin-arm64@0.1.19': + resolution: {integrity: sha512-ruy44Lpepdk1FqDz38vExBY/PVUsjxZA+chd9wozjUH9JjuDT/HEaQYA6wYN9mf041l0yLVar6BCZuWABJvHSA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@reflink/reflink-darwin-x64@0.1.19': + resolution: {integrity: sha512-By85MSWrMZa+c26TcnAy8SDk0sTUkYlNnwknSchkhHpGXOtjNDUOxJE9oByBnGbeuIE1PiQsxDG3Ud+IVV9yuA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@reflink/reflink-linux-arm64-gnu@0.1.19': + resolution: {integrity: sha512-7P+er8+rP9iNeN+bfmccM4hTAaLP6PQJPKWSA4iSk2bNvo6KU6RyPgYeHxXmzNKzPVRcypZQTpFgstHam6maVg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@reflink/reflink-linux-arm64-musl@0.1.19': + resolution: {integrity: sha512-37iO/Dp6m5DDaC2sf3zPtx/hl9FV3Xze4xoYidrxxS9bgP3S8ALroxRK6xBG/1TtfXKTvolvp+IjrUU6ujIGmA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@reflink/reflink-linux-x64-gnu@0.1.19': + resolution: {integrity: sha512-jbI8jvuYCaA3MVUdu8vLoLAFqC+iNMpiSuLbxlAgg7x3K5bsS8nOpTRnkLF7vISJ+rVR8W+7ThXlXlUQ93ulkw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@reflink/reflink-linux-x64-musl@0.1.19': + resolution: {integrity: sha512-e9FBWDe+lv7QKAwtKOt6A2W/fyy/aEEfr0g6j/hWzvQcrzHCsz07BNQYlNOjTfeytrtLU7k449H1PI95jA4OjQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@reflink/reflink-win32-arm64-msvc@0.1.19': + resolution: {integrity: sha512-09PxnVIQcd+UOn4WAW73WU6PXL7DwGS6wPlkMhMg2zlHHG65F3vHepOw06HFCq+N42qkaNAc8AKIabWvtk6cIQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@reflink/reflink-win32-x64-msvc@0.1.19': + resolution: {integrity: sha512-E//yT4ni2SyhwP8JRjVGWr3cbnhWDiPLgnQ66qqaanjjnMiu3O/2tjCPQXlcGc/DEYofpDc9fvhv6tALQsMV9w==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@reflink/reflink@0.1.19': + resolution: {integrity: sha512-DmCG8GzysnCZ15bres3N5AHCmwBwYgp0As6xjhQ47rAUTUXxJiK+lLUxaGsX3hd/30qUpVElh05PbGuxRPgJwA==} + engines: {node: '>= 10'} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + pnpm@11.11.0: + resolution: {integrity: sha512-RGP2X9gO2A1pvB1L8WPulPYFxzgPwxi7Wy6+FfjNEtScUaTVnpUbQB52TTtsp1HL9RvFDtcAGmvLSTXmhMNIgg==} + engines: {node: '>=22.13'} + hasBin: true + +snapshots: + + '@pnpm/exe@11.11.0': + dependencies: + '@reflink/reflink': 0.1.19 + detect-libc: 2.1.2 + optionalDependencies: + '@pnpm/linux-arm64': 11.11.0 + '@pnpm/linux-x64': 11.11.0 + '@pnpm/linuxstatic-arm64': 11.11.0 + '@pnpm/linuxstatic-x64': 11.11.0 + '@pnpm/macos-arm64': 11.11.0 + '@pnpm/win-arm64': 11.11.0 + '@pnpm/win-x64': 11.11.0 + + '@pnpm/linux-arm64@11.11.0': + optional: true + + '@pnpm/linux-x64@11.11.0': + optional: true + + '@pnpm/linuxstatic-arm64@11.11.0': + optional: true + + '@pnpm/linuxstatic-x64@11.11.0': + optional: true + + '@pnpm/macos-arm64@11.11.0': + optional: true + + '@pnpm/win-arm64@11.11.0': + optional: true + + '@pnpm/win-x64@11.11.0': + optional: true + + '@reflink/reflink-darwin-arm64@0.1.19': + optional: true + + '@reflink/reflink-darwin-x64@0.1.19': + optional: true + + '@reflink/reflink-linux-arm64-gnu@0.1.19': + optional: true + + '@reflink/reflink-linux-arm64-musl@0.1.19': + optional: true + + '@reflink/reflink-linux-x64-gnu@0.1.19': + optional: true + + '@reflink/reflink-linux-x64-musl@0.1.19': + optional: true + + '@reflink/reflink-win32-arm64-msvc@0.1.19': + optional: true + + '@reflink/reflink-win32-x64-msvc@0.1.19': + optional: true + + '@reflink/reflink@0.1.19': + optionalDependencies: + '@reflink/reflink-darwin-arm64': 0.1.19 + '@reflink/reflink-darwin-x64': 0.1.19 + '@reflink/reflink-linux-arm64-gnu': 0.1.19 + '@reflink/reflink-linux-arm64-musl': 0.1.19 + '@reflink/reflink-linux-x64-gnu': 0.1.19 + '@reflink/reflink-linux-x64-musl': 0.1.19 + '@reflink/reflink-win32-arm64-msvc': 0.1.19 + '@reflink/reflink-win32-x64-msvc': 0.1.19 + + detect-libc@2.1.2: {} + + pnpm@11.11.0: {} + +--- +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + devDependencies: + husky: + specifier: ^9.1.7 + version: 9.1.7 + +packages: + + husky@9.1.7: + resolution: {integrity: sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==} + engines: {node: '>=18'} + hasBin: true + +snapshots: + + husky@9.1.7: {}