diff --git a/backend/.env.example b/backend/.env.example index 6c42422..8af9a54 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -18,6 +18,18 @@ CAS_LOGIN_URL= DATABASE_URL=postgresql://admin:password@localhost:5432/integration-dev OUTSIDE_DATABASE_URL=postgresql://admin:password@localhost:5432/integration-dev +# Les variables Billetweb permettent d'autoriser l'achat de billets uniquement aux membres d'une liste d'adresses emails commandée par le site. +# En 2026, cette liste comprends automatiquement les Nouveaux et CE ayant remplis le formulaire "Contact de secours" et le questionnaire VSS. +# +# Le token se récupère sur la page https://www.billetweb.fr/bo/api.php#/, en étant préalablement connecté au compte billetweb de l'Intégration. +# +# L'ID de la liste d'accès se récupère sur la page Configuration > Offres Spréciales > Filtrage par liste > Cliquer sur la liste ; Il s'agit de largument "list_parent" dans l'URL. +# Exemple: https://www.billetweb.fr/bo/lists.php?list_parent=925 -> API_BILLETWEB_RESPONDENT_STUDENTS_LIST_ID=925 + +API_BILLETWEB_URL=https://www.billetweb.fr/api +API_BILLETWEB_TOKEN=Basic azerty +API_BILLETWEB_RESPONDENT_STUDENTS_LIST_ID= + JWT_SECRET= POSTGRES_PASSWORD=password diff --git a/backend/drizzle.config.ts b/backend/drizzle.config.ts index 2b91af5..7eeadaa 100644 --- a/backend/drizzle.config.ts +++ b/backend/drizzle.config.ts @@ -1,13 +1,13 @@ -import { defineConfig } from 'drizzle-kit' -import * as dotenv from "dotenv"; +import { defineConfig } from 'drizzle-kit'; +import * as dotenv from 'dotenv'; dotenv.config(); export default defineConfig({ - schema: "./src/schemas/*", - out: "./src/database/migrations", - dialect: "postgresql", - dbCredentials: { - url: process.env.OUTSIDE_DATABASE_URL ?? "" - }, + schema: './src/schemas/*', + out: './src/database/migrations', + dialect: 'postgresql', + dbCredentials: { + url: process.env.OUTSIDE_DATABASE_URL ?? '', + }, }); diff --git a/backend/server.ts b/backend/server.ts index 0847051..1e0cf1d 100644 --- a/backend/server.ts +++ b/backend/server.ts @@ -29,6 +29,7 @@ import tentRoutes from './src/routes/tent.routes'; import userRoutes from './src/routes/user.routes'; import bannedRoutes from './src/routes/banned.routes'; import { server_port } from './src/utils/secret'; +import { initQcmvss } from './src/database/initdb/initQcmvss'; dotenv.config(); @@ -46,6 +47,7 @@ async function startServer() { await initRoles(); await initEvent(); await initChallenge(); + await initQcmvss(); console.log('Base de données initialisée avec succès.'); // Utilisation des routes d'authentification @@ -67,11 +69,11 @@ async function startServer() { app.use('/api/discord', authenticateUser, discordRoutes); app.use('/api/tent', authenticateUser, tentRoutes); app.use('/api/bus', authenticateUser, busRoutes); - app.use("/api/uploads/news", express.static(path.join(__dirname, "/uploads/news"))); - app.use("/api/uploads/notebooks", express.static(path.join(__dirname, "/uploads/notebooks"))); - app.use("/api/uploads/foodmenu", express.static(path.join(__dirname, "/uploads/foodmenu"))); - app.use("/api/uploads/plannings", express.static(path.join(__dirname, "/uploads/plannings"))); - app.use("/api/exports/bus", express.static(path.join(__dirname, "/exports/bus"))); + app.use('/api/uploads/news', express.static(path.join(__dirname, '/uploads/news'))); + app.use('/api/uploads/notebooks', express.static(path.join(__dirname, '/uploads/notebooks'))); + app.use('/api/uploads/foodmenu', express.static(path.join(__dirname, '/uploads/foodmenu'))); + app.use('/api/uploads/plannings', express.static(path.join(__dirname, '/uploads/plannings'))); + app.use('/api/exports/bus', express.static(path.join(__dirname, '/exports/bus'))); // Démarrage du serveur app.listen(server_port, () => { diff --git a/backend/src/controllers/user.controller.ts b/backend/src/controllers/user.controller.ts index 11a6195..f10ef74 100644 --- a/backend/src/controllers/user.controller.ts +++ b/backend/src/controllers/user.controller.ts @@ -1,4 +1,12 @@ -import type { AdminCreateUserDto, PermissionParams, ProfileBody, SyncBody, UserIdParams } from '../dto/user.dto'; +import type { + AdminCreateUserDto, + CreateUserContactInformationDto, + PermissionParams, + ProfileBody, + SyncBody, + UserIdParams, + VssSubmissionPayload, +} from '../dto/user.dto'; import * as user_service from '../services/user.service'; import { Error, Ok } from '../utils/responses'; import type { AppRequestHandler } from '../types/http'; @@ -66,6 +74,61 @@ export const getCurrentUser: AppRequestHandler = async (req, res) => { } }; +export const getUserContactInformation: AppRequestHandler = async (req, res) => { + const { userId } = req.params; + + try { + const userContactInfo = await user_service.getUserContactInformation(parseInt(userId)); + Ok(res, { data: userContactInfo }); + } catch { + Error(res, { msg: "Erreur lors de la récupération des informations de contact de l'utilisateur." }); + } +}; + +export const createUserContactInformation: AppRequestHandler = async (req, res) => { + const userId = req.user?.userId; + const contact = req.body; + + try { + const result = await user_service.createUserContactInformation(userId, contact); + Ok(res, { msg: 'Informations de contact créées', data: result }); + } catch { + Error(res, { msg: 'Erreur lors de la création des informations de contact.' }); + } +}; + +export const getCurrentUserOnboardingStatus: AppRequestHandler = async (req, res) => { + const userId = req.user?.userId; + + try { + const status = await user_service.getCurrentUserOnboardingStatus(userId); + Ok(res, { data: status }); + } catch { + Error(res, { msg: "Erreur lors de la récupération du statut d'onboarding." }); + } +}; + +export const getVssQuestionnaire: AppRequestHandler = async (req, res) => { + try { + const questionnaire = await user_service.getVssQuestionnaire(); + Ok(res, { data: questionnaire }); + } catch { + Error(res, { msg: 'Erreur lors de la récupération du questionnaire VSS.' }); + } +}; + +export const submitVssQuestionnaire: AppRequestHandler = async (req, res) => { + const userId = req.user?.userId; + const payload = req.body; + + try { + const result = await user_service.submitVssQuestionnaire(userId, payload); + Ok(res, { data: result }); + } catch { + Error(res, { msg: 'Erreur lors de la soumission du questionnaire VSS.' }); + } +}; + export const updateProfile: AppRequestHandler = async (req, res) => { const userId = req.user?.userId; const { branch, contact } = req.body; diff --git a/backend/src/database/initdb/initQcmvss.ts b/backend/src/database/initdb/initQcmvss.ts new file mode 100644 index 0000000..8915459 --- /dev/null +++ b/backend/src/database/initdb/initQcmvss.ts @@ -0,0 +1,215 @@ +import { db } from '../db'; +import { vssqcmanswerSchema } from '../../schemas/Relational/vssqcmanswer.schema'; +import { vssqcmquestionSchema } from '../../schemas/Basic/vssqcmquestion.schema'; + +type SeedQuestion = { + question: string; + points: number; + type: 'single_choice' | 'multiple_choice'; + answers: { + answer: string; + is_correct: boolean; + }[]; +}; + +const qcmQuestions: SeedQuestion[] = [ + { + question: 'Oui = ', + points: 1, + type: 'single_choice', + answers: [ + { answer: 'Non', is_correct: false }, + { answer: 'Toujours oui', is_correct: false }, + { answer: 'Peut-être non plus tard', is_correct: true }, + ], + }, + { + question: 'Non = ', + points: 1, + type: 'single_choice', + answers: [ + { answer: 'Oui', is_correct: false }, + { answer: 'Non', is_correct: true }, + { answer: "Peut-être oui si j'insiste", is_correct: false }, + ], + }, + { + question: 'En résumé, le consentement', + points: 2, + type: 'multiple_choice', + answers: [ + { answer: 'concerne une action précise', is_correct: true }, + { answer: "ne peut-être considéré comme éclairé venant d'un personne en état d'ébriété", is_correct: true }, + { answer: 'doit être libre et éclairé', is_correct: true }, + { answer: 'peut être retiré à tout moment', is_correct: true }, + { + answer: "spécifique, enthousiaste; valable quand la personne chancèle sous l'effet de l'alcool", + is_correct: false, + }, + { answer: "peut s'obtenir en insistant", is_correct: false }, + { answer: 'est valable quand la personne est bourrée', is_correct: false }, + ], + }, + { + question: + "B a embrassé A de force pendant le bang. B était complètement bourré. Il s'agit d'une agression sexuelle. La prise d'alcool est alors une condition :", + points: 1, + type: 'single_choice', + answers: [ + { answer: 'Aggravante', is_correct: true }, + { answer: 'Atténuante', is_correct: false }, + ], + }, + { + question: 'Parmi les situations suivantes, lesquelles sont des agressions sexuelles :', + points: 1, + type: 'multiple_choice', + answers: [ + { answer: "Se frotter à quelqu'un•e", is_correct: true }, + { answer: 'Caresser les fesses de son•sa partenaire endormi•e', is_correct: true }, + { answer: "Embrasser quelqu'un•e de force", is_correct: true }, + { answer: "Embrasser par surprise quelqu'un•e qui danse au milieu de la foule", is_correct: true }, + { answer: "Embrasser quelqu'un•e tant alcoolisé•e qu'iel vient de vomir", is_correct: true }, + ], + }, + { + question: "Un.e de tes amis touche les fesses de B et l'enlace. B a un mouvement de recul. Que peux-tu faire ?", + points: 1, + type: 'multiple_choice', + answers: [ + { answer: "Rien de particulier. B ne s'en souviendra sûrement pas.", is_correct: false }, + { answer: 'Demander à B si elle•il va bien', is_correct: true }, + { + answer: "Prendre cet•te ami•e à part et lui faire comprendre qu'il•elle a mal agi, que B n'avait pas envie d'être touché•e.", + is_correct: true, + }, + { answer: 'Eloigner ton ami•e de B', is_correct: true }, + { answer: "Le signaler à un tiers si tu penses que B peut avoir besoin d'aide", is_correct: true }, + ], + }, + { + question: "A qui et où peux-tu demander de l'aide si tu en as besoin ?", + points: 1, + type: 'multiple_choice', + answers: [ + { answer: 'Dans la Safe Zone', is_correct: true }, + { answer: 'Aux personnes des confiance', is_correct: true }, + { answer: 'Au stand de prévention', is_correct: true }, + { answer: 'A la team prévention', is_correct: true }, + { answer: 'Aux super orgas', is_correct: true }, + { answer: "A tes chefs d'équipe", is_correct: true }, + { answer: 'A ta marraine / A ton parrain', is_correct: true }, + { answer: 'À un•e ami•e', is_correct: true }, + ], + }, + { + question: + 'En cas de VSS, quelles sont les peines maximales légalement encourue par une personne ayant commis une agression sexuelle ?', + points: 1, + type: 'single_choice', + answers: [ + { answer: "75 000 € d'amende et 5 ans d'emprisonnement", is_correct: true }, + { answer: "10 000€ d'amende", is_correct: false }, + { answer: '15 ans de prison', is_correct: false }, + ], + }, + { + question: 'Quelles sont les conséquences possibles pour la victime de VSS ?', + points: 1, + type: 'multiple_choice', + answers: [ + { answer: 'Aucun effet particulier', is_correct: false }, + { answer: 'Problèmes somatiques (nausées, migraines, fatigue)', is_correct: true }, + { answer: 'Dysfonction sexuelle', is_correct: true }, + { answer: "Crainte de l'intimité", is_correct: true }, + { answer: 'Dépression majeure', is_correct: true }, + { answer: 'Détresse psychologique', is_correct: true }, + ], + }, + { + question: 'Que puis-je faire si je suis témoins de VSS ?', + points: 1, + type: 'multiple_choice', + answers: [ + { answer: "Dire à la victime de faire attention à elle et de mieux s'habiller", is_correct: false }, + { answer: 'Aller voir la team prévention ou les super-orgas', is_correct: true }, + { answer: 'Appeler France Victime (01 80 52 33 86)', is_correct: true }, + { answer: "Appeler le numéro d'astreinte", is_correct: true }, + ], + }, + { + question: + "A quelle sentence s'expose une personne commettant un viol ? \n Article 222-23 Version en vigueur depuis le 23 avril 2021 \n Tout acte de pénétration sexuelle, de quelque nature qu'il soit, ou tout acte bucco-génital commis sur la personne d'autrui ou sur la personne de l'auteur par violence, contrainte, menace ou surprise est un viol. \n Le viol est puni de quinze ans de réclusion criminelle.", + points: 1, + type: 'multiple_choice', + answers: [ + { answer: '15 ans de réclusion criminelle', is_correct: true }, + { answer: "100 000€ d'amence et 20 ans de réclusion criminelle", is_correct: false }, + { answer: "100 000€ d'amence et 10 ans de réclusion criminelle", is_correct: false }, + ], + }, + { + question: "Qu'est-ce qui est considéré comme un acte de bizutage (et qui est donc interdit) ? ", + points: 1, + type: 'multiple_choice', + answers: [ + { answer: "Se dénuder ou inciter quelqu'un à se dénuder (Limousin, Maréchal...)", is_correct: true }, + { answer: "Obliger quelqu'un à boire de l'alcool de force lors d'une soirée", is_correct: true }, + { + answer: 'Organiser une chasse au trésor géante à travers toute la ville pour les nouveaux', + is_correct: false, + }, + { answer: 'Humilier publiquement un nouveau devant le groupe', is_correct: true }, + { answer: 'Forcer une personne à effectuer des tâches dégradantes ou dangereuses', is_correct: true }, + { answer: "Défier un nouveau à réciter l'annuaire téléphonique en dansant la macarena", is_correct: false }, + { answer: 'Motiver les nouveaux à se déguiser en canard', is_correct: false }, + { answer: 'Forcer les nouveaux à porter un déguisement obscène', is_correct: true }, + ], + }, + { + question: "A quelles sanctions s'expose l'auteur du bizutage ?", + points: 1, + type: 'multiple_choice', + answers: [ + { answer: "Une exclusion de l'intégration", is_correct: true }, + { answer: 'Rien du tout', is_correct: false }, + { + answer: "Le bizutage est un délit. Il est puni de 6 mois d'emprisonnement et de 7 500 € d'amende.", + is_correct: true, + }, + { answer: 'Si la victime est une personne vulnérable, les peines sont doublées', is_correct: true }, + { answer: 'Une mauvaise note', is_correct: false }, + ], + }, +]; + +export const initQcmvss = async () => { + const existingQuestion = await db.select().from(vssqcmquestionSchema).limit(1); + + if (existingQuestion.length > 0) { + return; + } + + for (const seedQuestion of qcmQuestions) { + const [createdQuestion] = await db + .insert(vssqcmquestionSchema) + .values({ + question: seedQuestion.question, + points: seedQuestion.points, + type: seedQuestion.type, + }) + .returning({ id: vssqcmquestionSchema.id }); + + if (!createdQuestion) { + throw new Error(`Question not inserted: ${seedQuestion.question}`); + } + + await db.insert(vssqcmanswerSchema).values( + seedQuestion.answers.map((seedAnswer) => ({ + questionid: createdQuestion.id, + answer: seedAnswer.answer, + is_correct: seedAnswer.is_correct, + })), + ); + } +}; diff --git a/backend/src/database/migrations/0024_optimal_garia.sql b/backend/src/database/migrations/0024_optimal_garia.sql index 6b29aee..89851e6 100644 --- a/backend/src/database/migrations/0024_optimal_garia.sql +++ b/backend/src/database/migrations/0024_optimal_garia.sql @@ -1 +1 @@ -ALTER TABLE "teams" ADD COLUMN "socialLink" text; \ No newline at end of file +ALTER TABLE "teams" ADD COLUMN "socialLink" text; diff --git a/backend/src/database/migrations/0025_perfect_ulik.sql b/backend/src/database/migrations/0025_perfect_ulik.sql index 8bc1d9b..3147526 100644 --- a/backend/src/database/migrations/0025_perfect_ulik.sql +++ b/backend/src/database/migrations/0025_perfect_ulik.sql @@ -1,2 +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 +ALTER TABLE "users" ADD COLUMN "male" boolean; diff --git a/backend/src/database/migrations/0026_swift_excalibur.sql b/backend/src/database/migrations/0026_swift_excalibur.sql new file mode 100644 index 0000000..3a1a08d --- /dev/null +++ b/backend/src/database/migrations/0026_swift_excalibur.sql @@ -0,0 +1,25 @@ +CREATE TYPE "public"."vss_form" AS ENUM('pending', 'toretry', 'validated', 'rejected');--> statement-breakpoint +CREATE TYPE "public"."question_type" AS ENUM('single_choice', 'multiple_choice');--> statement-breakpoint +CREATE TABLE "vssqcmquestion" ( + "id" serial PRIMARY KEY NOT NULL, + "question" text NOT NULL, + "points" integer NOT NULL, + "type" "question_type" NOT NULL +); +--> statement-breakpoint +CREATE TABLE "user_informations" ( + "user_id" integer PRIMARY KEY NOT NULL, + "emergency_contact_name" text, + "emergency_contact_phone" text +); +--> statement-breakpoint +CREATE TABLE "vssqcmanswer" ( + "id" serial PRIMARY KEY NOT NULL, + "questionid" integer NOT NULL, + "answer" text NOT NULL, + "is_correct" boolean NOT NULL +); +--> statement-breakpoint +ALTER TABLE "users" ADD COLUMN "vss_form" "vss_form" DEFAULT 'pending';--> statement-breakpoint +ALTER TABLE "user_informations" ADD CONSTRAINT "user_informations_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "vssqcmanswer" ADD CONSTRAINT "vssqcmanswer_questionid_vssqcmquestion_id_fk" FOREIGN KEY ("questionid") REFERENCES "public"."vssqcmquestion"("id") ON DELETE cascade ON UPDATE no action; \ No newline at end of file diff --git a/backend/src/database/migrations/meta/0023_snapshot.json b/backend/src/database/migrations/meta/0023_snapshot.json index 54df657..3d53326 100644 --- a/backend/src/database/migrations/meta/0023_snapshot.json +++ b/backend/src/database/migrations/meta/0023_snapshot.json @@ -1,1317 +1,1183 @@ { - "id": "128e2d49-22e7-477d-a202-ae518c2e21ed", - "prevId": "93925edf-9622-4ce2-80bc-26050abbce0b", - "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 - } - }, - "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" - ] + "id": "128e2d49-22e7-477d-a202-ae518c2e21ed", + "prevId": "93925edf-9622-4ce2-80bc-26050abbce0b", + "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 + } + }, + "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 } - }, - "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": {}, + "enums": {}, "schemas": {}, - "tables": {} - } -} \ No newline at end of file + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} diff --git a/backend/src/database/migrations/meta/0024_snapshot.json b/backend/src/database/migrations/meta/0024_snapshot.json index 56629c4..07fab54 100644 --- a/backend/src/database/migrations/meta/0024_snapshot.json +++ b/backend/src/database/migrations/meta/0024_snapshot.json @@ -1320,4 +1320,4 @@ "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 index 060def6..2e383ca 100644 --- a/backend/src/database/migrations/meta/0025_snapshot.json +++ b/backend/src/database/migrations/meta/0025_snapshot.json @@ -1333,4 +1333,4 @@ "schemas": {}, "tables": {} } -} \ No newline at end of file +} diff --git a/backend/src/database/migrations/meta/0026_snapshot.json b/backend/src/database/migrations/meta/0026_snapshot.json new file mode 100644 index 0000000..973912e --- /dev/null +++ b/backend/src/database/migrations/meta/0026_snapshot.json @@ -0,0 +1,1497 @@ +{ + "id": "af2f7f68-b5fb-4945-87a6-dcf6b539c002", + "prevId": "f8bcae73-ebc2-4afa-99c0-cf9aa304b17b", + "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()" + }, + "vss_form": { + "name": "vss_form", + "type": "vss_form", + "typeSchema": "public", + "primaryKey": false, + "notNull": false, + "default": "'pending'" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "users_email_unique": { + "name": "users_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.vssqcmquestion": { + "name": "vssqcmquestion", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "question": { + "name": "question", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "points": { + "name": "points", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "question_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "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.user_informations": { + "name": "user_informations", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": true, + "notNull": true + }, + "emergency_contact_name": { + "name": "emergency_contact_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "emergency_contact_phone": { + "name": "emergency_contact_phone", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "user_informations_user_id_users_id_fk": { + "name": "user_informations_user_id_users_id_fk", + "tableFrom": "user_informations", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "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 + }, + "public.vssqcmanswer": { + "name": "vssqcmanswer", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "questionid": { + "name": "questionid", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "answer": { + "name": "answer", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "is_correct": { + "name": "is_correct", + "type": "boolean", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "vssqcmanswer_questionid_vssqcmquestion_id_fk": { + "name": "vssqcmanswer_questionid_vssqcmquestion_id_fk", + "tableFrom": "vssqcmanswer", + "tableTo": "vssqcmquestion", + "columnsFrom": [ + "questionid" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.vss_form": { + "name": "vss_form", + "schema": "public", + "values": [ + "pending", + "toretry", + "validated", + "rejected" + ] + }, + "public.question_type": { + "name": "question_type", + "schema": "public", + "values": [ + "single_choice", + "multiple_choice" + ] + } + }, + "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 b787921..73ad2f7 100644 --- a/backend/src/database/migrations/meta/_journal.json +++ b/backend/src/database/migrations/meta/_journal.json @@ -183,6 +183,13 @@ "when": 1784742510283, "tag": "0025_perfect_ulik", "breakpoints": true + }, + { + "idx": 26, + "version": "7", + "when": 1784824629918, + "tag": "0026_swift_excalibur", + "breakpoints": true } ] } \ No newline at end of file diff --git a/backend/src/dto/user.dto.ts b/backend/src/dto/user.dto.ts index 41c11de..7e0107a 100644 --- a/backend/src/dto/user.dto.ts +++ b/backend/src/dto/user.dto.ts @@ -1,3 +1,5 @@ +import { type VssSubmissionAnswer } from '../services/user.service'; + export interface AdminCreateUserDto { firstName: string; lastName: string; @@ -23,3 +25,12 @@ export type ProfileBody = { branch: string; contact: string; }; + +export type CreateUserContactInformationDto = { + emergency_contact_name: string; + emergency_contact_phone: string; +}; + +export type VssSubmissionPayload = { + answers: VssSubmissionAnswer[]; +}; diff --git a/backend/src/routes/user.routes.ts b/backend/src/routes/user.routes.ts index fe9f230..1872054 100644 --- a/backend/src/routes/user.routes.ts +++ b/backend/src/routes/user.routes.ts @@ -6,9 +6,14 @@ const userRouter = express.Router(); // Admin routes userRouter.get('/admin/getusersbypermission', checkRole('Admin', []), userController.getUsersByPermission); -userRouter.post('/admin/user', checkRole('Admin', []), userController.adminCreateUser); userRouter.patch('/admin/user/:userId', checkRole('Admin', []), userController.adminUpdateUser); userRouter.delete('/admin/user/:userId', checkRole('Admin', []), userController.adminDeleteUser); +userRouter.get( + '/admin/getusercontactinformation/:userId', + checkRole('Admin', []), + userController.getUserContactInformation, +); +userRouter.post('/admin/user', checkRole('Admin', []), userController.adminCreateUser); userRouter.get('/admin/getusers', checkRole('Admin', ['Respo CE']), userController.getUsersAdmin); userRouter.post('/admin/syncnewstudent', checkRole('Admin', []), userController.syncNewstudent); @@ -16,5 +21,9 @@ userRouter.post('/admin/syncnewstudent', checkRole('Admin', []), userController. userRouter.patch('/user/me', userController.updateProfile); userRouter.get('/user/me', userController.getCurrentUser); userRouter.get('/user/getusers', userController.getUsers); +userRouter.post('/user/usercontactinformation', userController.createUserContactInformation); +userRouter.get('/onboarding-status', userController.getCurrentUserOnboardingStatus); +userRouter.get('/vss/questionnaire', userController.getVssQuestionnaire); +userRouter.post('/vss/questionnaire', userController.submitVssQuestionnaire); export default userRouter; diff --git a/backend/src/schemas/Basic/user.schema.ts b/backend/src/schemas/Basic/user.schema.ts index 94cc4f2..78b16ca 100644 --- a/backend/src/schemas/Basic/user.schema.ts +++ b/backend/src/schemas/Basic/user.schema.ts @@ -1,4 +1,6 @@ -import { boolean, pgTable, serial, text, timestamp } from 'drizzle-orm/pg-core'; +import { boolean, pgEnum, pgTable, serial, text, timestamp } from 'drizzle-orm/pg-core'; + +export const vssFormEnum = pgEnum('vss_form', ['pending', 'toretry', 'validated', 'rejected']); export const userSchema = pgTable('users', { id: serial('id').primaryKey(), @@ -13,6 +15,7 @@ export const userSchema = pgTable('users', { permission: text('permission').default('Nouveau'), // Par défaut, le rôle sera "Nouveau" discord_id: text('discord_id'), created_at: timestamp('created_at').defaultNow(), + vss_form: vssFormEnum('vss_form').default('pending'), }); export type User = typeof userSchema.$inferSelect; diff --git a/backend/src/schemas/Basic/vssqcmquestion.schema.ts b/backend/src/schemas/Basic/vssqcmquestion.schema.ts new file mode 100644 index 0000000..cc9c3c1 --- /dev/null +++ b/backend/src/schemas/Basic/vssqcmquestion.schema.ts @@ -0,0 +1,12 @@ +import { integer, pgEnum, pgTable, serial, text } from 'drizzle-orm/pg-core'; + +export const questionTypeEnum = pgEnum('question_type', ['single_choice', 'multiple_choice']); + +export const vssqcmquestionSchema = pgTable('vssqcmquestion', { + id: serial('id').primaryKey(), + question: text('question').notNull(), + points: integer('points').notNull(), + type: questionTypeEnum('type').notNull(), +}); + +export type VssQcmQuestion = typeof vssqcmquestionSchema.$inferSelect; diff --git a/backend/src/schemas/Relational/userinformation.schema.ts b/backend/src/schemas/Relational/userinformation.schema.ts new file mode 100644 index 0000000..a2707f1 --- /dev/null +++ b/backend/src/schemas/Relational/userinformation.schema.ts @@ -0,0 +1,12 @@ +import { pgTable, integer, text } from 'drizzle-orm/pg-core'; +import { userSchema } from '../Basic/user.schema'; + +export const userInformationSchema = pgTable('user_informations', { + user_id: integer('user_id') + .primaryKey() + .references(() => userSchema.id, { onDelete: 'cascade' }), + emergency_contact_name: text('emergency_contact_name'), + emergency_contact_phone: text('emergency_contact_phone'), +}); + +export type UserInformation = typeof userInformationSchema.$inferSelect; diff --git a/backend/src/schemas/Relational/vssqcmanswer.schema.ts b/backend/src/schemas/Relational/vssqcmanswer.schema.ts new file mode 100644 index 0000000..008c14b --- /dev/null +++ b/backend/src/schemas/Relational/vssqcmanswer.schema.ts @@ -0,0 +1,13 @@ +import { boolean, integer, pgTable, serial, text } from 'drizzle-orm/pg-core'; +import { vssqcmquestionSchema } from '../Basic/vssqcmquestion.schema'; + +export const vssqcmanswerSchema = pgTable('vssqcmanswer', { + id: serial('id').primaryKey(), + questionid: integer('questionid') + .references(() => vssqcmquestionSchema.id, { onDelete: 'cascade' }) + .notNull(), + answer: text('answer').notNull(), + is_correct: boolean('is_correct').notNull(), +}); + +export type VssQcmAnswer = typeof vssqcmanswerSchema.$inferSelect; diff --git a/backend/src/services/user.service.ts b/backend/src/services/user.service.ts index 1f98a04..11d9092 100644 --- a/backend/src/services/user.service.ts +++ b/backend/src/services/user.service.ts @@ -2,8 +2,10 @@ import bcrypt from 'bcryptjs'; import { eq } from 'drizzle-orm'; import * as randomstring from 'randomstring'; import { db } from '../database/db'; // Import de la connexion PostgreSQL -import type { AdminCreateUserDto } from '../dto/user.dto'; +import type { AdminCreateUserDto, CreateUserContactInformationDto, VssSubmissionPayload } from '../dto/user.dto'; import { type User, userSchema } from '../schemas/Basic/user.schema'; +import { vssqcmquestionSchema } from '../schemas/Basic/vssqcmquestion.schema'; +import { vssqcmanswerSchema } from '../schemas/Relational/vssqcmanswer.schema'; import { registrationSchema } from '../schemas/Relational/registration.schema'; import * as auth_service from '../services/auth.service'; import * as SIEP_Utils from '../utils/siep'; @@ -12,9 +14,29 @@ import { createRegistrationToken } from './auth.service'; import { getFaction } from './faction.service'; import { getUserRoles } from './role.service'; import { getTeam, getTeamFaction, getUserTeam } from './team.service'; +import { userInformationSchema } from '../schemas/Relational/userinformation.schema'; +import { addUserToRespondentStudentsList } from '../utils/billetweb'; import { generateEmailHtml, sendEmail } from './email.service'; import { email_from } from '../utils/secret'; +export type VssQuestionnaireAnswer = { + id: number; + answer: string; +}; + +export type VssQuestionnaireQuestion = { + id: number; + question: string; + points: number; + type: 'single_choice' | 'multiple_choice'; + answers: VssQuestionnaireAnswer[]; +}; + +export type VssSubmissionAnswer = { + questionId: number; + answerIds: number[]; +}; + // Fonction pour récupérer un utilisateur par email export const getUserByEmail = async (email: string) => { try { @@ -39,6 +61,7 @@ export const getUserById = async (userId: number) => { contact: userSchema.contact, permission: userSchema.permission, discord_id: userSchema.discord_id, + vss_form: userSchema.vss_form, }) .from(userSchema) .where(eq(userSchema.id, userId)); @@ -228,6 +251,210 @@ export const getUsers = async () => { } }; +export const getUserContactInformation = async (userId: number) => { + try { + const user = await db + .select({ + userId: userInformationSchema.user_id, + emergency_contact_name: userInformationSchema.emergency_contact_name, + emergency_contact_phone: userInformationSchema.emergency_contact_phone, + }) + .from(userInformationSchema) + .where(eq(userInformationSchema.user_id, userId)); + return user[0]; + } catch (err) { + console.error("Erreur lors de la récupération des informations de contact de l'utilisateur ", err); + throw new Error('Erreur de base de données'); + } +}; + +export const createUserContactInformation = async (userId: number, contact: CreateUserContactInformationDto) => { + try { + if (!contact.emergency_contact_name || !contact.emergency_contact_phone) { + throw new Error("Le nom et le numéro de téléphone du contact d'urgence sont requis."); + } + + if (!/^\+?\d{10,15}$/.test(contact.emergency_contact_phone)) { + throw new Error("Le numéro de téléphone du contact d'urgence n'est pas valide."); + } + + const newContactInfo = { + user_id: userId, + emergency_contact_name: contact.emergency_contact_name, + emergency_contact_phone: contact.emergency_contact_phone, + }; + + const result = await db + .insert(userInformationSchema) + .values(newContactInfo) + .onConflictDoUpdate({ + target: userInformationSchema.user_id, + set: { + emergency_contact_name: contact.emergency_contact_name, + emergency_contact_phone: contact.emergency_contact_phone, + }, + }) + .returning(); + return result[0]; + } catch (err) { + console.error("Erreur lors de la création des informations de contact de l'utilisateur:", err); + throw new Error('Erreur de base de données'); + } +}; + +export const getCurrentUserOnboardingStatus = async (userId: number) => { + try { + const [user] = await db + .select({ + vss_form: userSchema.vss_form, + permission: userSchema.permission, + }) + .from(userSchema) + .where(eq(userSchema.id, userId)); + + if (user.permission != 'Nouveau') { + return { + hasemergencyContactInformation: true, + vss_form: 'validated', + needsVssForm: false, + }; + } + + const [contactInformation] = await db + .select({ + userId: userInformationSchema.user_id, + }) + .from(userInformationSchema) + .where(eq(userInformationSchema.user_id, userId)); + + const vssForm = user?.vss_form ?? 'pending'; + + return { + hasemergencyContactInformation: Boolean(contactInformation), + vss_form: vssForm, + needsVssForm: vssForm === 'pending' || vssForm === 'toretry', + }; + } catch (err) { + console.error("Erreur lors de la récupération du statut d'onboarding:", err); + throw new Error('Erreur de base de données'); + } +}; + +export const getVssQuestionnaire = async () => { + try { + const questions = await db.select().from(vssqcmquestionSchema); + const answers = await db.select().from(vssqcmanswerSchema); + + return questions.map((question) => ({ + id: question.id, + question: question.question, + points: question.points, + type: question.type, + answers: answers + .filter((answer) => answer.questionid === question.id) + .map((answer) => ({ + id: answer.id, + answer: answer.answer, + })), + })); + } catch (err) { + console.error('Erreur lors de la récupération du questionnaire VSS:', err); + throw new Error('Erreur de base de données'); + } +}; + +export const submitVssQuestionnaire = async (userId: number, payload: VssSubmissionPayload) => { + try { + const [user] = await db + .select({ + vss_form: userSchema.vss_form, + }) + .from(userSchema) + .where(eq(userSchema.id, userId)); + + if (!user) { + throw new Error('Utilisateur introuvable'); + } + + if (user.vss_form === 'validated' || user.vss_form === 'rejected') { + return { + score: 0, + maxScore: 0, + status: user.vss_form, + }; + } + + const questions = await db.select().from(vssqcmquestionSchema); + const answers = await db.select().from(vssqcmanswerSchema); + + const answersByQuestion = new Map(); + for (const answer of answers) { + const currentAnswers = answersByQuestion.get(answer.questionid) ?? []; + currentAnswers.push(answer); + answersByQuestion.set(answer.questionid, currentAnswers); + } + + const responsesByQuestion = new Map>(); + for (const response of payload.answers ?? []) { + responsesByQuestion.set(response.questionId, new Set(response.answerIds)); + } + + for (const question of questions) { + const response = responsesByQuestion.get(question.id); + if (!response || response.size === 0) { + throw new Error('Toutes les questions doivent recevoir une réponse.'); + } + } + + let score = 0; + const maxScore = questions.reduce((total, question) => total + question.points, 0); + + for (const question of questions) { + const questionAnswers = answersByQuestion.get(question.id) ?? []; + const correctAnswerIds = questionAnswers.filter((answer) => answer.is_correct).map((answer) => answer.id); + const selectedAnswerIds = Array.from(responsesByQuestion.get(question.id) ?? []); + + const isCorrect = + selectedAnswerIds.length === correctAnswerIds.length && + selectedAnswerIds.every((answerId) => correctAnswerIds.includes(answerId)); + + if (isCorrect) { + score += question.points; + } + } + + let status: 'pending' | 'toretry' | 'validated' | 'rejected' = 'validated'; + if (score < Math.ceil(maxScore / 2)) { + status = user.vss_form === 'toretry' ? 'rejected' : 'toretry'; + } + + const [updatedUser] = await db + .update(userSchema) + .set({ vss_form: status }) + .where(eq(userSchema.id, userId)) + .returning({ + vss_form: userSchema.vss_form, + email: userSchema.email, + firstName: userSchema.first_name, + lastName: userSchema.last_name, + }); + + if (status == 'validated') { + addUserToRespondentStudentsList({ + ...updatedUser, + }); + } + return { + score, + maxScore, + status: updatedUser?.vss_form ?? status, + }; + } catch (err) { + console.error('Erreur lors de la soumission du questionnaire VSS:', err); + throw new Error('Erreur de base de données'); + } +}; + export const getUsersAll = async () => { try { const users = await db.select().from(userSchema); diff --git a/backend/src/utils/billetweb.ts b/backend/src/utils/billetweb.ts new file mode 100644 index 0000000..a5135d8 --- /dev/null +++ b/backend/src/utils/billetweb.ts @@ -0,0 +1,52 @@ +import axios from 'axios'; +import { api_billetweb_token, api_billetweb_url, api_billetweb_respondent_students_list_id } from './secret'; +import type { BilletwebUser } from '../../types/billetweb'; + +const headers = { + accept: 'application/json', + 'Content-Type': 'application/json', + Authorization: api_billetweb_token, +}; + +const addUserToList = async (listId: string, user: BilletwebUser) => { + try { + if (!api_billetweb_url || !api_billetweb_token || !api_billetweb_respondent_students_list_id) return; + + await axios.post( + `${api_billetweb_url}/list/${listId}/push`, + { + data: [[user.email, user.firstName, user.lastName]], + }, + { headers }, + ); + } catch (error) { + console.error(`Error adding user ${user.email} to list ${listId}:`, error); + throw error; + } +}; + +const removeUserFromList = async (listId: string, email: string) => { + try { + if (!api_billetweb_url || !api_billetweb_token || !api_billetweb_respondent_students_list_id) return; + + const response = await axios.post( + `${api_billetweb_url}/list/${listId}/remove`, + { + data: [[email]], + }, + { headers }, + ); + return response; + } catch (error) { + console.error(`Error removing ${email} from list ${listId}:`, error); + throw error; + } +}; + +export const addUserToRespondentStudentsList = async (user: BilletwebUser) => { + addUserToList(api_billetweb_respondent_students_list_id, user); +}; + +export const removeUserFromRespondentStudentsList = async (email: string) => { + removeUserFromList(api_billetweb_respondent_students_list_id, email); +}; diff --git a/backend/src/utils/secret.ts b/backend/src/utils/secret.ts index e7f78e8..36340d7 100644 --- a/backend/src/utils/secret.ts +++ b/backend/src/utils/secret.ts @@ -17,6 +17,10 @@ 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_billetweb_url = process.env.API_BILLETWEB_URL || 'default'; +export const api_billetweb_token = process.env.API_BILLETWEB_TOKEN || 'default'; +export const api_billetweb_respondent_students_list_id = + process.env.API_BILLETWEB_RESPONDENT_STUDENTS_LIST_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'; diff --git a/backend/types/billetweb.d.ts b/backend/types/billetweb.d.ts new file mode 100644 index 0000000..11db5a9 --- /dev/null +++ b/backend/types/billetweb.d.ts @@ -0,0 +1,7 @@ +export interface BilletwebUser { + email: string; + firstName: string; + lastName: string; +} + +export type BilletwebMember = [string, ...string[]]; diff --git a/frontend/eslint.config.js b/frontend/eslint.config.js index 761cc9f..a6ada99 100644 --- a/frontend/eslint.config.js +++ b/frontend/eslint.config.js @@ -1,13 +1,13 @@ -import js from '@eslint/js' -import importPlugin from 'eslint-plugin-import' -import jsxA11y from 'eslint-plugin-jsx-a11y' -import reactPlugin from 'eslint-plugin-react' -import reactHooks from 'eslint-plugin-react-hooks' -import reactRefresh from 'eslint-plugin-react-refresh' -import simpleImportSort from 'eslint-plugin-simple-import-sort' -import unusedImports from 'eslint-plugin-unused-imports' -import globals from 'globals' -import tseslint from 'typescript-eslint' +import js from '@eslint/js'; +import importPlugin from 'eslint-plugin-import'; +import jsxA11y from 'eslint-plugin-jsx-a11y'; +import reactPlugin from 'eslint-plugin-react'; +import reactHooks from 'eslint-plugin-react-hooks'; +import reactRefresh from 'eslint-plugin-react-refresh'; +import simpleImportSort from 'eslint-plugin-simple-import-sort'; +import unusedImports from 'eslint-plugin-unused-imports'; +import globals from 'globals'; +import tseslint from 'typescript-eslint'; export default tseslint.config( { @@ -63,10 +63,7 @@ export default tseslint.config( argsIgnorePattern: '^_', }, ], - 'react-refresh/only-export-components': [ - 'warn', - { allowConstantExport: true }, - ], + 'react-refresh/only-export-components': ['warn', { allowConstantExport: true }], }, }, -) +); diff --git a/frontend/src/components/Admin/adminUser.tsx b/frontend/src/components/Admin/adminUser.tsx index bab1bdc..f0b632b 100644 --- a/frontend/src/components/Admin/adminUser.tsx +++ b/frontend/src/components/Admin/adminUser.tsx @@ -3,11 +3,12 @@ import Select from 'react-select'; import { type SingleValue } from 'react-select'; import Swal from 'sweetalert2'; -import { type User } from '../../interfaces/user.interface'; +import type { User, UserContactInformation } from '../../interfaces/user.interface'; import { renewTokenUser, requestPasswordUser } from '../../services/requests/auth.service'; import { createUserByAdmin, deleteUserByAdmin, + getUserContactInformation, getUsersAdmin, syncnewStudent, updateUserByAdmin, @@ -52,6 +53,7 @@ export const AdminUser = () => { const [users, setUsers] = useState([]); const [selectedUser, setSelectedUser] = useState(null); const [formData, setFormData] = useState>({}); + const [contactInformation, setContactInformation] = useState>({}); useEffect(() => { const fetchUsers = async () => { @@ -61,11 +63,12 @@ export const AdminUser = () => { fetchUsers(); }, []); - const handleUserSelect = (option: any) => { + const handleUserSelect = async (option: any) => { const user = users.find((u) => u.userId === option.value); if (user) { setSelectedUser(user); setFormData({ ...user }); + setContactInformation({ ...(await getUserContactInformation(user.userId)) }); } }; @@ -171,104 +174,131 @@ export const AdminUser = () => { }; return ( - - - - 👤 Gérer un utilisateur - - - - - ({ + value: u.userId, + label: `${u.firstName} ${u.lastName} (${u.email})`, + }))} + onChange={handleUserSelect} + /> - {selectedUser && ( -
- - - - -

- Attention : la donnée récupérée dépend de la date de synchro choisie -

- - b.value === formData.branch) || null} - onChange={handleSelectChange('branch')} - options={branchOptions} - placeholder="Choisir une filière" - isClearable - /> - - - - + + + +

+ Attention : la donnée récupérée dépend de la date de synchro choisie +

+ + b.value === formData.branch) || null} + onChange={handleSelectChange('branch')} + options={branchOptions} + placeholder="Choisir une filière" + isClearable + /> + + + + + -
- )} -
-
+ + + )} + ); }; diff --git a/frontend/src/components/WEI_SDI_Food/sdiSection.tsx b/frontend/src/components/WEI_SDI_Food/sdiSection.tsx index b03b479..6b7f3e3 100644 --- a/frontend/src/components/WEI_SDI_Food/sdiSection.tsx +++ b/frontend/src/components/WEI_SDI_Food/sdiSection.tsx @@ -1,26 +1,50 @@ -import { useEffect, useState } from "react"; +import { useEffect, useState } from 'react'; -import { checkSDIStatus } from "../../services/requests/event.service"; -import { Card, CardContent, CardHeader, CardTitle } from "../ui/card"; +import { decodeToken, getToken } from '../../services/requests/auth.service'; +import { checkSDIStatus } from '../../services/requests/event.service'; +import { getCurrentUserOnboardingStatus } from '../../services/requests/user.service'; +import { Card, CardContent, CardHeader, CardTitle } from '../ui/card'; export const SdiSection = () => { const [isSDIOpen, setIsSDIOpen] = useState(false); + const [hasContactInformation, setHasContactInformation] = useState(true); + const [hasVssForm, setHasVssForm] = useState(true); + const [needVssForm, setNeedsVssForm] = useState(false); + const token = getToken(); + const { userPermission, userRoles = [] } = token + ? decodeToken(token) + : { userPermission: undefined, userRoles: [] }; + const roles = [userPermission, ...userRoles.map((r) => r.roleName)].filter(Boolean) as string[]; useEffect(() => { - const script = document.createElement("script"); - script.src = "https://www.billetweb.fr/js/export.js"; + const script = document.createElement('script'); + script.src = 'https://www.billetweb.fr/js/export.js'; script.async = true; document.body.appendChild(script); fetchStatus(); - }, []); + if (roles.includes('Nouveau')) { + fetchOnboardingStatus(); + } + }, [roles]); + + const fetchOnboardingStatus = async () => { + try { + const onboardingStatus = await getCurrentUserOnboardingStatus(); + setHasContactInformation(onboardingStatus.hasemergencyContactInformation); + setHasVssForm(onboardingStatus.vss_form == 'validated'); + setNeedsVssForm(onboardingStatus.needsVssForm); + } catch (error) { + console.error("Erreur lors de la récupération du statut d'onboarding :", error); + } + }; const fetchStatus = async () => { try { const status = await checkSDIStatus(); setIsSDIOpen(status); } catch { - alert("Erreur lors de la récupération du statut de SDI."); + alert('Erreur lors de la récupération du statut de SDI.'); } }; @@ -31,7 +55,8 @@ export const SdiSection = () => { 🎉 Participe à la Soirée d'Intégration (SDI) !

- Un événement incroyable t'attend… Inscris-toi dès maintenant pour ne rien rater de cette Soirée d'Intégration ! + Un événement incroyable t'attend… Inscris-toi dès maintenant pour ne rien rater de cette Soirée + d'Intégration !

@@ -40,8 +65,32 @@ export const SdiSection = () => {

🚫 La billetterie de la Soirée d'intégration (SDI) n'est pas encore disponible.

+

Reste connecté, elle ouvrira bientôt !

+ + ) : !hasContactInformation ? ( +
+

+ 🚫 Tu n'as pas rempli le questionnaire avec tes contacts d'urgence. Tant que ce n'est pas + fait, tu ne peux pas accéder à la billetterie de la Soirée d'intégration (SDI). +

+

Va vite le compléter !

+
+ ) : needVssForm ? ( +
+

+ 🚫 Tu n'as pas rempli le questionnaire de sensibilisation aux VSS. Tant que ce n'est pas + fait, tu ne peux pas accéder à la billetterie de la Soirée d'intégration (SDI). +

+

Va vite le compléter !

+
+ ) : !hasVssForm ? ( +
+

+ 🚫 Tu as fait trop d'erreur sur le questionnaire de sensibilisation aux VSS. Tu ne peux par + conséquent pas accéder à la billeterie de la Soirée d'intégration (SDI). +

- Reste connecté, elle ouvrira bientôt ! + Si tu penses qu'il s'agit d'une erreur, tu peux te rapprocher de la team prévention.

) : ( @@ -54,6 +103,6 @@ export const SdiSection = () => { )}
- + ); }; diff --git a/frontend/src/components/WEI_SDI_Food/weiSection.tsx b/frontend/src/components/WEI_SDI_Food/weiSection.tsx index b304371..05145e0 100644 --- a/frontend/src/components/WEI_SDI_Food/weiSection.tsx +++ b/frontend/src/components/WEI_SDI_Food/weiSection.tsx @@ -1,26 +1,50 @@ -import { useEffect, useState } from "react"; +import { useEffect, useState } from 'react'; -import { checkWEIStatus } from "../../services/requests/event.service"; -import { Card, CardContent, CardHeader, CardTitle } from "../ui/card"; +import { decodeToken, getToken } from '../../services/requests/auth.service'; +import { checkWEIStatus } from '../../services/requests/event.service'; +import { getCurrentUserOnboardingStatus } from '../../services/requests/user.service'; +import { Card, CardContent, CardHeader, CardTitle } from '../ui/card'; export const WeiSection = () => { const [isWEIOpen, setIsWEIOpen] = useState(false); + const [hasContactInformation, setHasContactInformation] = useState(true); + const [hasVssForm, setHasVssForm] = useState(true); + const [needVssForm, setNeedsVssForm] = useState(false); + const token = getToken(); + const { userPermission, userRoles = [] } = token + ? decodeToken(token) + : { userPermission: undefined, userRoles: [] }; + const roles = [userPermission, ...userRoles.map((r) => r.roleName)].filter(Boolean) as string[]; useEffect(() => { - const script = document.createElement("script"); - script.src = "https://www.billetweb.fr/js/export.js"; + const script = document.createElement('script'); + script.src = 'https://www.billetweb.fr/js/export.js'; script.async = true; document.body.appendChild(script); fetchStatus(); - }, []); + if (roles.includes('Nouveau')) { + fetchOnboardingStatus(); + } + }, [roles]); + + const fetchOnboardingStatus = async () => { + try { + const onboardingStatus = await getCurrentUserOnboardingStatus(); + setHasContactInformation(onboardingStatus.hasemergencyContactInformation); + setHasVssForm(onboardingStatus.vss_form == 'validated'); + setNeedsVssForm(onboardingStatus.needsVssForm); + } catch (error) { + console.error("Erreur lors de la récupération du statut d'onboarding :", error); + } + }; const fetchStatus = async () => { try { const status = await checkWEIStatus(); setIsWEIOpen(status); } catch { - alert("Erreur lors de la récupération du statut de WEI."); + alert('Erreur lors de la récupération du statut de WEI.'); } }; @@ -31,7 +55,8 @@ export const WeiSection = () => { 🎉 Tu es nouveau ? Participe au WEI !

- Un événement incroyable t'attend… Inscris-toi dès maintenant pour ne rien rater du Week-End d'Intégration 2025 ! + Un événement incroyable t'attend… Inscris-toi dès maintenant pour ne rien rater du Week-End + d'Intégration 2025 !

@@ -40,8 +65,32 @@ export const WeiSection = () => {

🚫 La billetterie du WEI n'est pas encore disponible.

+

Reste connecté, elle ouvrira bientôt !

+ + ) : !hasContactInformation ? ( +
+

+ 🚫 Tu n'as pas rempli le questionnaire avec tes contacts d'urgence. Tant que ce n'est pas + fait, tu ne peux pas accéder à la billetterie du Week-End d'Intégration (WEI). +

+

Va vite le compléter !

+
+ ) : needVssForm ? ( +
+

+ 🚫 Tu n'as pas rempli le questionnaire de sensibilisation aux VSS. Tant que ce n'est pas + fait, tu ne peux pas accéder à la billetterie du Week-End d'Intégration (WEI). +

+

Va vite le compléter !

+
+ ) : !hasVssForm ? ( +
+

+ 🚫 Tu as fait trop d'erreur sur le questionnaire de sensibilisation aux VSS. Tu ne peux par + conséquent pas accéder à la billeterie du Week-End d'Intégration (WEI). +

- Reste connecté, elle ouvrira bientôt ! + Si tu penses qu'il s'agit d'une erreur, tu peux te rapprocher de la team prévention.

) : ( @@ -54,6 +103,6 @@ export const WeiSection = () => { )}
- + ); }; diff --git a/frontend/src/components/auth/authForm.tsx b/frontend/src/components/auth/authForm.tsx index bf98f29..64f0f32 100644 --- a/frontend/src/components/auth/authForm.tsx +++ b/frontend/src/components/auth/authForm.tsx @@ -31,7 +31,7 @@ export const AuthForm = () => { const token = await loginUser(formData.email, formData.password); if (token) { localStorage.setItem("authToken", token); - window.location.href = "/home"; + window.location.href = "/home?login=true"; } } catch (err: any) { console.error(err); diff --git a/frontend/src/components/home/emergencyModal.tsx b/frontend/src/components/home/emergencyModal.tsx new file mode 100644 index 0000000..a9465f1 --- /dev/null +++ b/frontend/src/components/home/emergencyModal.tsx @@ -0,0 +1,156 @@ +import { useEffect, useMemo, useState } from 'react'; +import { useSearchParams } from 'react-router-dom'; + +import { decodeToken, getToken } from '../../services/requests/auth.service'; +import { createUserContactInformation, getCurrentUserOnboardingStatus } from '../../services/requests/user.service'; +import { Button } from '../ui/button'; +import { Input } from '../ui/input'; +import Modal from '../ui/modal'; +import VssModal from './vssModal'; + +type FlowStep = 'idle' | 'loading' | 'emergency' | 'vss'; + +function EmergencyModal() { + const [searchParams, setSearchParams] = useSearchParams(); + const [form, setForm] = useState({ emergency_contact_name: '', emergency_contact_phone: '' }); + const [flowStep, setFlowStep] = useState('idle'); + const [error, setError] = useState(null); + const token = getToken(); + const decodedToken = useMemo(() => (token ? decodeToken(token) : null), [token]); + const userPermission = decodedToken?.userPermission; + const roles = useMemo(() => { + const userRoles = decodedToken?.userRoles ?? []; + return [userPermission, ...userRoles.map((r) => r.roleName)].filter(Boolean) as string[]; + }, [userPermission, decodedToken?.userRoles]); + + const isLogin = searchParams.get('login') === 'true'; + const isNew = roles.includes('Nouveau'); + + useEffect(() => { + if (!isLogin) { + setFlowStep('idle'); + setForm({ emergency_contact_name: '', emergency_contact_phone: '' }); + setError(null); + return; + } + + let cancelled = false; + + const loadOnboardingStatus = async () => { + setFlowStep('loading'); + setError(null); + + try { + const status = await getCurrentUserOnboardingStatus(); + + if (cancelled) { + return; + } + + if (!status.hasemergencyContactInformation) { + setFlowStep('emergency'); + return; + } + + if (status.needsVssForm) { + setFlowStep('vss'); + return; + } + + setFlowStep('idle'); + setSearchParams({}); + } catch { + if (!cancelled) { + setFlowStep('emergency'); + setError('Impossible de récupérer le statut du formulaire.'); + } + } + }; + + if (isNew) { + loadOnboardingStatus(); + } + + return () => { + cancelled = true; + }; + }, [isLogin, isNew, setSearchParams]); + + const closeFlow = () => { + setSearchParams({}); + setFlowStep('idle'); + setForm({ emergency_contact_name: '', emergency_contact_phone: '' }); + setError(null); + }; + + const handleContactSubmit = async () => { + setError(null); + + try { + await createUserContactInformation(form); + window.dispatchEvent(new Event('user-onboarding-updated')); + setFlowStep('vss'); + } catch { + setError("Impossible d'enregistrer les informations d'urgence."); + } + }; + + return ( + <> + +
+

Bienvenue sur le site de l'intégration !

+

Nous sommes ravis de t'accueillir parmi nous à l'UTT.

+

+ Durant ta première semaine à l'UTT, tu pourras participer aux activités d'intégration. Afin que + celle-ci se déroule dans les meilleures conditions, nous avons besoin que tu répondes à deux + formulaires. +

+

+ Dans ce premier formulaire, nous te demandons simplement de renseigner un contact d'urgence, au + cas où le moindre problème surviendrait durant cette semaine. +

+

+ Tu peux quitter ce formulaire à tout moment et le compléter plus tard. Cependant, il est + obligatoire pour participer à certaines activités. +

+ {error && ( +

+ {error} +

+ )} + setForm({ ...form, emergency_contact_name: e.target.value })} + /> + setForm({ ...form, emergency_contact_phone: e.target.value })} + /> +
+ + +
+
+
+ + { + window.dispatchEvent(new Event('user-onboarding-updated')); + }} + /> + + ); +} + +export default EmergencyModal; diff --git a/frontend/src/components/home/vssModal.tsx b/frontend/src/components/home/vssModal.tsx new file mode 100644 index 0000000..e5d7642 --- /dev/null +++ b/frontend/src/components/home/vssModal.tsx @@ -0,0 +1,276 @@ +import { useEffect, useState } from 'react'; + +import type { VssQuestionnaireQuestion, VssSubmissionResponse } from '../../interfaces/user.interface'; +import { getVssQuestionnaire, submitVssQuestionnaire } from '../../services/requests/user.service'; +import { Button } from '../ui/button'; +import Modal from '../ui/modal'; + +interface VssModalProps { + visible: boolean; + onCancel: () => void; + onSubmitted?: (result: VssSubmissionResponse) => void; +} + +const getAnswerClassName = (selected: boolean) => + `rounded-xl border px-4 py-3 text-left transition ${ + selected + ? 'border-blue-500 bg-blue-50 text-blue-900 shadow-sm dark:border-blue-400 dark:bg-blue-950/40 dark:text-blue-100' + : 'border-border/60 bg-white hover:border-blue-300 hover:bg-blue-50/60 dark:border-white/10 dark:bg-neutral-900 dark:hover:border-blue-500/60 dark:hover:bg-blue-950/20' + }`; + +const VssQuestionBlock = ({ + question, + selectedAnswerIds, + onSelect, +}: { + question: VssQuestionnaireQuestion; + selectedAnswerIds: number[]; + onSelect: (questionId: number, answerId: number, type: VssQuestionnaireQuestion['type']) => void; +}) => { + return ( +
+
+
+

Question {question.id}

+

+ {question.question} +

+
+ + {question.points} point{question.points > 1 ? 's' : ''} + +
+ +
+ {question.answers.map((answer) => { + const isSelected = selectedAnswerIds.includes(answer.id); + + return ( + + ); + })} +
+

+ {question.type === 'single_choice' + ? 'Choisis une seule réponse.' + : 'Tu peux sélectionner plusieurs réponses.'} +

+
+ ); +}; + +function VssModal({ visible, onCancel, onSubmitted }: VssModalProps) { + const [questions, setQuestions] = useState([]); + const [selectedAnswers, setSelectedAnswers] = useState>({}); + const [submitting, setSubmitting] = useState(false); + const [error, setError] = useState(null); + const [result, setResult] = useState(null); + + useEffect(() => { + if (!visible) { + setQuestions([]); + setSelectedAnswers({}); + setError(null); + setResult(null); + setSubmitting(false); + return; + } + + let cancelled = false; + + const loadQuestionnaire = async () => { + setError(null); + setResult(null); + + try { + const questionnaire = await getVssQuestionnaire(); + + if (cancelled) { + return; + } + + setQuestions(questionnaire); + setSelectedAnswers({}); + } catch { + if (!cancelled) { + setError('Impossible de charger le questionnaire VSS.'); + } + } + }; + + loadQuestionnaire(); + + return () => { + cancelled = true; + }; + }, [visible]); + + const handleSelectAnswer = (questionId: number, answerId: number, type: VssQuestionnaireQuestion['type']) => { + setSelectedAnswers((currentAnswers) => { + const existingAnswers = currentAnswers[questionId] ?? []; + + if (type === 'single_choice') { + return { + ...currentAnswers, + [questionId]: [answerId], + }; + } + + const hasAnswer = existingAnswers.includes(answerId); + return { + ...currentAnswers, + [questionId]: hasAnswer + ? existingAnswers.filter((currentAnswerId) => currentAnswerId !== answerId) + : [...existingAnswers, answerId], + }; + }); + }; + + const handleSubmit = async () => { + setError(null); + setSubmitting(true); + + try { + const unansweredQuestions = questions.filter((question) => { + const currentAnswers = selectedAnswers[question.id] ?? []; + return currentAnswers.length === 0; + }); + + if (unansweredQuestions.length > 0) { + setError("Réponds à toutes les questions avant d'envoyer le questionnaire."); + return; + } + + const result = await submitVssQuestionnaire({ + answers: questions.map((question) => ({ + questionId: question.id, + answerIds: selectedAnswers[question.id] ?? [], + })), + }); + + setResult(result); + onSubmitted?.(result); + } catch { + setError('Impossible d’envoyer le questionnaire VSS.'); + } finally { + setSubmitting(false); + } + }; + + const answeredCount = questions.filter((question) => (selectedAnswers[question.id] ?? []).length > 0).length; + const totalQuestions = questions.length; + + const statusMessage = + result?.status === 'validated' + ? 'Questionnaire validé. Tu peux fermer cette fenêtre.' + : result?.status === 'toretry' + ? 'Le résultat nécessite une seconde tentative. Tu pourras retenter plus tard.' + : result?.status === 'rejected' + ? 'Le nombre de tentatives autorisées est atteint.' + : null; + + return ( + +
+

+ Dans ce questionnaire, tu devras répondre aux questions ci-dessous à propos des Violences Sexistes + et Sexuelles (VSS). +

+

+ La note est sur 14 et tu disposes de deux essais pour obtenir au moins 7 points. Cette + sensibilisation est très importante pour nous afin de nous assurer que l'intégration se déroule dans + les meilleures conditions pour tout le monde. +

+

+ Si tu n'arrives pas à obtenir la moyenne après deux tentatives, nous serons malheureusement + contraints de te refuser l'accès à la Soirée et au Week-end d'intégration, car ce sont les moments + où la majorité des situations de VSS se produisent. +

+

+ Tu peux quitter ce questionnaire à tout moment et le compléter plus tard. Cependant, il est + obligatoire pour participer à certaines activités. +

+ + {error && ( +
+ {error} +
+ )} + + {result && ( +
+

{statusMessage}

+ {result.maxScore > 0 && ( +

+ Score obtenu : {result.score}/{result.maxScore} +

+ )} +
+ )} + + {questions.length > 0 && ( +
+
+ + {answeredCount}/{totalQuestions} questions répondues + + + {questions.reduce((total, question) => total + question.points, 0)} points possibles + +
+ + {questions.map((question) => ( + + ))} +
+ )} + + {questions.length === 0 && !error && ( +
+ Aucun questionnaire disponible pour le moment. +
+ )} + +
+ {result ? ( + + ) : ( + <> + + + + )} +
+
+
+ ); +} + +export default VssModal; diff --git a/frontend/src/components/navbar.tsx b/frontend/src/components/navbar.tsx index c22cba5..b109bd6 100644 --- a/frontend/src/components/navbar.tsx +++ b/frontend/src/components/navbar.tsx @@ -2,9 +2,11 @@ import { Bars4Icon, CogIcon, HomeIcon, UsersIcon } from '@heroicons/react/24/out import { XMarkIcon } from '@heroicons/react/24/solid'; import { AnimatePresence, motion } from 'framer-motion'; import { Fragment, useEffect, useState } from 'react'; -import { NavLink, useLocation } from 'react-router-dom'; +import { NavLink, useLocation, useSearchParams } from 'react-router-dom'; import { decodeToken, getToken } from '../services/requests/auth.service'; +import { getCurrentUserOnboardingStatus } from '../services/requests/user.service'; +import { Button } from './ui/button'; interface NavItem { label: string; @@ -27,6 +29,9 @@ export const Navbar = () => { ? decodeToken(token) : { userPermission: undefined, userRoles: [] }; const roles = [userPermission, ...userRoles.map((r) => r.roleName)].filter(Boolean) as string[]; + const [hasContactInformation, setHasContactInformation] = useState(false); + const [needsVssForm, setNeedsVssForm] = useState(false); + const [, setSearchParams] = useSearchParams(); const handleLogout = () => { localStorage.removeItem('authToken'); @@ -35,7 +40,33 @@ export const Navbar = () => { useEffect(() => { setMenuOpen(false); - }, [pathname]); + + const fetchOnboardingStatus = async () => { + try { + const onboardingStatus = await getCurrentUserOnboardingStatus(); + setHasContactInformation(onboardingStatus.hasemergencyContactInformation); + setNeedsVssForm(onboardingStatus.needsVssForm); + } catch (error) { + console.error("Erreur lors de la récupération du statut d'onboarding :", error); + } + }; + + if (isAuthenticated) { + fetchOnboardingStatus(); + } + + const handleOnboardingUpdate = () => { + if (isAuthenticated) { + fetchOnboardingStatus(); + } + }; + + window.addEventListener('user-onboarding-updated', handleOnboardingUpdate); + + return () => { + window.removeEventListener('user-onboarding-updated', handleOnboardingUpdate); + }; + }, [isAuthenticated, pathname]); const navItems: NavItem[] = [ { label: 'Home', to: '/home', icon: HomeIcon }, @@ -127,66 +158,110 @@ export const Navbar = () => { }; return ( - + + {isAuthenticated && (hasContactInformation === false || needsVssForm) && roles.includes('Nouveau') && ( +
+

+ {hasContactInformation === false + ? "ATTENTION : Tu n'as pas complété le formulaire VSS ainsi que tes informations d'urgence. Merci de le faire au plus vite !" + : "ATTENTION : Tu n'as pas encore complété le questionnaire VSS. Merci de le faire au plus vite !"} +

+ +
+ )} + ); }; diff --git a/frontend/src/components/tent/tentSection.tsx b/frontend/src/components/tent/tentSection.tsx index cbcfbc4..ba9e27f 100644 --- a/frontend/src/components/tent/tentSection.tsx +++ b/frontend/src/components/tent/tentSection.tsx @@ -1,21 +1,27 @@ -import { useEffect, useState } from "react"; -import Select from "react-select"; -import Swal from "sweetalert2"; - -import { type Tent } from "../../interfaces/tent.interface"; -import { type User } from "../../interfaces/user.interface"; -import { decodeToken, getToken } from "../../services/requests/auth.service"; -import { checkWEIStatus } from "../../services/requests/event.service"; -import { cancelTent, createTent, getUserTent } from "../../services/requests/tent.service"; -import { getUsers } from "../../services/requests/user.service"; -import { Button } from "../ui/button"; -import { Card, CardContent, CardHeader, CardTitle } from "../ui/card"; +import { useEffect, useState } from 'react'; +import Select from 'react-select'; +import Swal from 'sweetalert2'; + +import { type Tent } from '../../interfaces/tent.interface'; +import { type User } from '../../interfaces/user.interface'; +import { decodeToken, getToken } from '../../services/requests/auth.service'; +import { checkWEIStatus } from '../../services/requests/event.service'; +import { cancelTent, createTent, getUserTent } from '../../services/requests/tent.service'; +import { getCurrentUserOnboardingStatus, getUsers } from '../../services/requests/user.service'; +import { Button } from '../ui/button'; +import { Card, CardContent, CardHeader, CardTitle } from '../ui/card'; export const TentPublic = () => { const [userId2, setUserId2] = useState(null); const [tentInfo, setTentInfo] = useState(null); const [users, setUsers] = useState([]); const [isWEIOpen, setIsWEIOpen] = useState(false); + const [canBook, setCanBook] = useState(true); + const token = getToken(); + const { userPermission, userRoles = [] } = token + ? decodeToken(token) + : { userPermission: undefined, userRoles: [] }; + const roles = [userPermission, ...userRoles.map((r) => r.roleName)].filter(Boolean) as string[]; useEffect(() => { const fetchUsers = async () => { @@ -23,7 +29,7 @@ export const TentPublic = () => { const result = await getUsers(); setUsers(result); } catch { - Swal.fire("Erreur", "Impossible de charger les utilisateurs", "error"); + Swal.fire('Erreur', 'Impossible de charger les utilisateurs', 'error'); } }; @@ -34,7 +40,7 @@ export const TentPublic = () => { setTentInfo(result.data[0]); } } catch { - Swal.fire("Erreur", "Impossible de récupérer la tente", "error"); + Swal.fire('Erreur', 'Impossible de récupérer la tente', 'error'); } }; @@ -43,51 +49,62 @@ export const TentPublic = () => { const status = await checkWEIStatus(); setIsWEIOpen(status); } catch { - Swal.fire("Erreur", "Impossible de récupérer le statut du WEI", "error"); + Swal.fire('Erreur', 'Impossible de récupérer le statut du WEI', 'error'); } }; fetchUsers(); fetchTent(); fetchWEIStatus(); - }, []); + if (roles.includes('Nouveau')) { + fetchOnboardingStatus(); + } + }, [roles]); + + const fetchOnboardingStatus = async () => { + try { + const onboardingStatus = await getCurrentUserOnboardingStatus(); + setCanBook(onboardingStatus.vss_form == 'validated'); + } catch (error) { + console.error("Erreur lors de la récupération du statut d'onboarding :", error); + } + }; - const token = getToken(); if (!token) return null; const { userId } = decodeToken(token); const handleCreate = async () => { if (!userId2) { - return Swal.fire("Erreur", "Sélectionne ton binôme", "error"); + return Swal.fire('Erreur', 'Sélectionne ton binôme', 'error'); } try { Swal.fire({ - title: "Création...", - text: "Ta tente est en cours de création", + title: 'Création...', + text: 'Ta tente est en cours de création', allowOutsideClick: false, didOpen: () => Swal.showLoading(), }); await createTent(userId2); - Swal.fire("✅ Succès", "Ta tente a été créée avec succès !", "success"); + Swal.fire('✅ Succès', 'Ta tente a été créée avec succès !', 'success'); handleGetTent(); } catch (err: any) { - Swal.fire("Erreur", err.message || "Impossible de créer la tente", "error"); + Swal.fire('Erreur', err.message || 'Impossible de créer la tente', 'error'); } }; const handleCancel = async () => { const confirm = await Swal.fire({ - title: "Annuler la tente ?", - text: "Tu ne pourras pas revenir en arrière.", - icon: "warning", + title: 'Annuler la tente ?', + text: 'Tu ne pourras pas revenir en arrière.', + icon: 'warning', showCancelButton: true, - confirmButtonColor: "#d33", - cancelButtonColor: "#3085d6", - confirmButtonText: "Oui, annuler", - cancelButtonText: "Non, garder", + confirmButtonColor: '#d33', + cancelButtonColor: '#3085d6', + confirmButtonText: 'Oui, annuler', + cancelButtonText: 'Non, garder', }); if (!confirm.isConfirmed) return; @@ -96,9 +113,9 @@ export const TentPublic = () => { await cancelTent(); setTentInfo(null); setUserId2(null); - Swal.fire("🛑 Annulée", "Ta tente a bien été annulée", "success"); + Swal.fire('🛑 Annulée', 'Ta tente a bien été annulée', 'success'); } catch { - Swal.fire("Erreur", "Impossible d'annuler la tente", "error"); + Swal.fire('Erreur', "Impossible d'annuler la tente", 'error'); } }; @@ -109,7 +126,7 @@ export const TentPublic = () => { setTentInfo(result.data[0]); } } catch { - Swal.fire("Erreur", "Impossible de récupérer la tente", "error"); + Swal.fire('Erreur', 'Impossible de récupérer la tente', 'error'); } }; @@ -123,85 +140,105 @@ export const TentPublic = () => { {isWEIOpen ? ( - !tentInfo ? ( - <> -
- - user.userId !== userId) + .map((user: User) => ({ + value: user.userId, + label: `${user.firstName} ${user.lastName}`, + }))} + value={ + userId2 + ? { + value: userId2, + label: `${users.find((u) => u.userId === userId2)?.firstName || ''} ${ + users.find((u) => u.userId === userId2)?.lastName || '' + }`, + } + : null + } + onChange={(option) => setUserId2(option?.value || null)} + isClearable + className="shadow-sm" + /> +
-
- -
- - ) : ( -
-

🎫 Ta tente

-

- Binôme avec {" "} - - { - users.find( - (user) => - user.userId === - (tentInfo.user_id_1 === userId ? tentInfo.user_id_2 : tentInfo.user_id_1) - )?.firstName - } {" "} - { - users.find( - (user) => - user.userId === - (tentInfo.user_id_1 === userId ? tentInfo.user_id_2 : tentInfo.user_id_1) - )?.lastName - } - -

+
+ +
+ + ) : ( +
+

🎫 Ta tente

+

+ Binôme avec{' '} + + { + users.find( + (user) => + user.userId === + (tentInfo.user_id_1 === userId + ? tentInfo.user_id_2 + : tentInfo.user_id_1), + )?.firstName + }{' '} + { + users.find( + (user) => + user.userId === + (tentInfo.user_id_1 === userId + ? tentInfo.user_id_2 + : tentInfo.user_id_1), + )?.lastName + } + +

-
- {tentInfo.confirmed ? ( -

✅ Ta tente est confirmée !

- ) : ( -

- ⏳ En attente de confirmation - tu recevras un mail bientôt. -

- )} -
+
+ {tentInfo.confirmed ? ( +

✅ Ta tente est confirmée !

+ ) : ( +

+ ⏳ En attente de confirmation - tu recevras un mail bientôt. +

+ )} +
-
- +
+ +
+ ) + ) : ( +
+

+ 🚫 Vous ne pouvez pas accéder à cette section +

+

+ Vous n'avez pas encore complété vos informations d'urgence et le questionnaire VSS. +
+ Faites le au plus vite ! +

) ) : ( @@ -215,7 +252,7 @@ export const TentPublic = () => {
)} - + ); }; diff --git a/frontend/src/components/ui/modal.tsx b/frontend/src/components/ui/modal.tsx new file mode 100644 index 0000000..3aeed16 --- /dev/null +++ b/frontend/src/components/ui/modal.tsx @@ -0,0 +1,129 @@ +'use client'; +import { type ReactNode, useEffect } from 'react'; + +import { Button } from '../ui/button'; + +/** + * Displays a modal window. + */ +const Modal = ({ + title = '', + children = '', + buttons = '', + visible = false, + closable = true, + closeOnOutsideClick = false, + onCancel = () => {}, + onOk = () => {}, + className = '', + containerClassName = '', + modalButtonsClassName = '', +}: { + /** Modal window title */ + title?: ReactNode; + /** Modal window content */ + children?: ReactNode; + /** Modal window buttons. + * Pass `null` to hide the footer entirely. + * Pass `""` (default) to get the default Annuler/Ok buttons. */ + buttons?: ReactNode | null; + /** Whether the modal window is visible or not */ + visible: boolean; + /** Whether the modal window is closable or not */ + closable?: boolean; + /** Whether clicking outside the modal closes it */ + closeOnOutsideClick?: boolean; + /** Function called when the user clicks on "Annuler" default button, + * or on the close button, or presses Escape */ + onCancel: () => void; + /** Function called when the user clicks on "Ok" default button */ + onOk?: () => void; + /** An optional class name to add to the modal */ + className?: string; + /** An optional class name to add to the modal container */ + containerClassName?: string; + /** An optional class name to add to the modal buttons container */ + modalButtonsClassName?: string; +}) => { + const buttonsContent = + buttons === null ? null : buttons !== '' ? ( + buttons + ) : ( + <> + + + + ); + + useEffect(() => { + const listener = (e: KeyboardEvent) => { + if (e.key === 'Escape') { + onCancel(); + } + }; + + if (visible) { + window.addEventListener('keydown', listener); + } + + return () => { + window.removeEventListener('keydown', listener); + }; + }, [onCancel, visible]); + + return ( +
+
+ + )} +
+ +
{children}
+ + {/* Render footer only if buttonsContent is not null */} + {buttonsContent && ( +
+ {buttonsContent} +
+ )} +
+
+ + ); +}; + +export default Modal; diff --git a/frontend/src/interfaces/user.interface.ts b/frontend/src/interfaces/user.interface.ts index 75c6a7f..b53abce 100644 --- a/frontend/src/interfaces/user.interface.ts +++ b/frontend/src/interfaces/user.interface.ts @@ -8,6 +8,52 @@ export interface User { branch: string; contact: string; discord_id: string; + vss_form?: 'pending' | 'toretry' | 'validated' | 'rejected'; +} + +export interface UserContactInformation { + userId: number; + emergency_contact_name: string; + emergency_contact_phone: string; +} + +export interface CreateUserContactInformationRequest { + emergency_contact_name: string; + emergency_contact_phone: string; +} + +export interface UserOnboardingStatus { + hasemergencyContactInformation: boolean; + vss_form: 'pending' | 'toretry' | 'validated' | 'rejected'; + needsVssForm: boolean; +} + +export interface VssQuestionnaireAnswer { + id: number; + answer: string; +} + +export interface VssQuestionnaireQuestion { + id: number; + question: string; + points: number; + type: 'single_choice' | 'multiple_choice'; + answers: VssQuestionnaireAnswer[]; +} + +export interface VssSubmissionAnswer { + questionId: number; + answerIds: number[]; +} + +export interface VssSubmissionRequest { + answers: VssSubmissionAnswer[]; +} + +export interface VssSubmissionResponse { + score: number; + maxScore: number; + status: 'pending' | 'toretry' | 'validated' | 'rejected'; } export interface NewUser { diff --git a/frontend/src/pages/challenge.tsx b/frontend/src/pages/challenge.tsx index 1869c38..cfd8499 100644 --- a/frontend/src/pages/challenge.tsx +++ b/frontend/src/pages/challenge.tsx @@ -1,10 +1,12 @@ -import { UserChallengeList } from "../components/challenge/challengeList"; -import { Footer } from "../components/footer"; -import { Navbar } from "../components/navbar"; +import { UserChallengeList } from '../components/challenge/challengeList'; +import { Footer } from '../components/footer'; +import EmergencyModal from '../components/home/emergencyModal'; +import { Navbar } from '../components/navbar'; const ChallPage = () => (
+
diff --git a/frontend/src/pages/food.tsx b/frontend/src/pages/food.tsx index 2c760ce..fda6fec 100644 --- a/frontend/src/pages/food.tsx +++ b/frontend/src/pages/food.tsx @@ -1,10 +1,12 @@ import { Footer } from "../components/footer"; +import EmergencyModal from "../components/home/emergencyModal"; import { Navbar } from "../components/navbar"; import { FoodSection } from "../components/WEI_SDI_Food/foodSection"; const FoodPage = () => (
+
diff --git a/frontend/src/pages/games.tsx b/frontend/src/pages/games.tsx index a518fcd..197079d 100644 --- a/frontend/src/pages/games.tsx +++ b/frontend/src/pages/games.tsx @@ -1,10 +1,12 @@ -import { Footer } from "../components/footer"; -import { RoleLeaderboard } from "../components/Games/roleLeaderboard"; -import { Navbar } from "../components/navbar"; +import { Footer } from '../components/footer'; +import { RoleLeaderboard } from '../components/Games/roleLeaderboard'; +import EmergencyModal from '../components/home/emergencyModal'; +import { Navbar } from '../components/navbar'; const GamesPage = () => (
+
diff --git a/frontend/src/pages/home.tsx b/frontend/src/pages/home.tsx index 23e7c93..4b3c02c 100644 --- a/frontend/src/pages/home.tsx +++ b/frontend/src/pages/home.tsx @@ -1,4 +1,5 @@ import { Footer } from '../components/footer'; +import EmergencyModal from '../components/home/emergencyModal'; import { Infos } from '../components/home/infosSection'; import { SocialLinks } from '../components/home/socialSection'; import { Navbar } from '../components/navbar'; @@ -7,8 +8,8 @@ const HomePage = () => (
+ -
); diff --git a/frontend/src/pages/legals.tsx b/frontend/src/pages/legals.tsx index c7744ac..53ba002 100644 --- a/frontend/src/pages/legals.tsx +++ b/frontend/src/pages/legals.tsx @@ -1,10 +1,12 @@ -import { Footer } from "../components/footer"; -import { LegalsSection } from "../components/legals/legalsSection"; -import { Navbar } from "../components/navbar"; +import { Footer } from '../components/footer'; +import EmergencyModal from '../components/home/emergencyModal'; +import { LegalsSection } from '../components/legals/legalsSection'; +import { Navbar } from '../components/navbar'; const LegalsPage = () => (
+
diff --git a/frontend/src/pages/news.tsx b/frontend/src/pages/news.tsx index 1db1c16..4a9487d 100644 --- a/frontend/src/pages/news.tsx +++ b/frontend/src/pages/news.tsx @@ -1,10 +1,12 @@ -import { Footer } from "../components/footer"; -import { Navbar } from "../components/navbar"; -import { MyNews } from "../components/news/newsSection"; +import { Footer } from '../components/footer'; +import EmergencyModal from '../components/home/emergencyModal'; +import { Navbar } from '../components/navbar'; +import { MyNews } from '../components/news/newsSection'; const NewsPage = () => (
+
diff --git a/frontend/src/pages/notFound.tsx b/frontend/src/pages/notFound.tsx index baa3f19..072a11b 100644 --- a/frontend/src/pages/notFound.tsx +++ b/frontend/src/pages/notFound.tsx @@ -1,9 +1,10 @@ -import { Link } from "react-router-dom"; +import { Link } from 'react-router-dom'; -import { Footer } from "../components/footer"; -import { Navbar } from "../components/navbar"; -import { Card, CardContent, CardHeader, CardTitle } from "../components/ui/card"; -import { getToken } from "../services/requests/auth.service"; +import { Footer } from '../components/footer'; +import EmergencyModal from '../components/home/emergencyModal'; +import { Navbar } from '../components/navbar'; +import { Card, CardContent, CardHeader, CardTitle } from '../components/ui/card'; +import { getToken } from '../services/requests/auth.service'; const NotFoundPage = () => { const isAuthenticated = Boolean(getToken()); @@ -12,39 +13,33 @@ const NotFoundPage = () => {
+ +
+ style={{ backgroundImage: "url('img/bg_25.jpg')" }}>

Erreur 404

- - Page introuvable - + Page introuvable
-

- Oups, la page que tu cherches n'existe pas ou a été déplacée. -

+

Oups, la page que tu cherches n'existe pas ou a été déplacée.

- {isAuthenticated ? ( + className="rounded-md bg-blue-600 px-5 py-2.5 font-medium text-white transition hover:bg-blue-700"> Retour à l'accueil ) : ( + className="rounded-md bg-blue-600 px-5 py-2.5 font-medium text-white transition hover:bg-blue-700"> Se connecter )} diff --git a/frontend/src/pages/parrainage.tsx b/frontend/src/pages/parrainage.tsx index afc6835..af0b67b 100644 --- a/frontend/src/pages/parrainage.tsx +++ b/frontend/src/pages/parrainage.tsx @@ -1,6 +1,7 @@ import { useNavigate } from 'react-router-dom'; import { Footer } from '../components/footer'; +import EmergencyModal from '../components/home/emergencyModal'; import { Navbar } from '../components/navbar'; import { ParrainageNewStudent, ParrainageStudent } from '../components/Parrainnage/parrainageForm'; import { getPermission } from '../services/requests/user.service'; @@ -17,6 +18,7 @@ const ParrainagePage = () => { return (
+
{(permission === 'Nouveau' || permission === 'Admin') && } diff --git a/frontend/src/pages/perm.tsx b/frontend/src/pages/perm.tsx index 4f83399..c93f633 100644 --- a/frontend/src/pages/perm.tsx +++ b/frontend/src/pages/perm.tsx @@ -1,22 +1,23 @@ -import { useEffect, useState } from "react"; -import { Navigate } from "react-router-dom"; -import Swal from "sweetalert2"; - -import { Footer } from "../components/footer"; -import { Navbar } from "../components/navbar"; -import { RespoPresenceManagement } from "../components/permanence/appealPerm"; -import { AvailablePermanencesList } from "../components/permanence/permList"; -import { MyPermanencesList } from "../components/permanence/permUser"; -import { type Permanence } from "../interfaces/permanence.interface"; -import { type DecodedToken } from "../interfaces/token.interfaces"; -import { decodeToken, getToken } from "../services/requests/auth.service"; +import { useEffect, useState } from 'react'; +import { Navigate } from 'react-router-dom'; +import Swal from 'sweetalert2'; + +import { Footer } from '../components/footer'; +import EmergencyModal from '../components/home/emergencyModal'; +import { Navbar } from '../components/navbar'; +import { RespoPresenceManagement } from '../components/permanence/appealPerm'; +import { AvailablePermanencesList } from '../components/permanence/permList'; +import { MyPermanencesList } from '../components/permanence/permUser'; +import { type Permanence } from '../interfaces/permanence.interface'; +import { type DecodedToken } from '../interfaces/token.interfaces'; +import { decodeToken, getToken } from '../services/requests/auth.service'; import { applyToPermanence, cancelPermanence, getMyPermanences, getOpenPermanences, isUserRespo, -} from "../services/requests/permanence.service"; +} from '../services/requests/permanence.service'; export const AvailablePermanencesPage: React.FC = () => { const [permanences, setPermanences] = useState([]); @@ -29,15 +30,12 @@ export const AvailablePermanencesPage: React.FC = () => { const fetchData = async () => { try { - const [openPerms, myPerms] = await Promise.all([ - getOpenPermanences(), - getMyPermanences(), - ]); + const [openPerms, myPerms] = await Promise.all([getOpenPermanences(), getMyPermanences()]); setPermanences(openPerms); setMyPermanences(myPerms); } catch (err) { - console.error("Erreur lors du chargement des permanences", err); - Swal.fire("Erreur", "Impossible de charger les permanences.", "error"); + console.error('Erreur lors du chargement des permanences', err); + Swal.fire('Erreur', 'Impossible de charger les permanences.', 'error'); } }; @@ -47,11 +45,11 @@ export const AvailablePermanencesPage: React.FC = () => { try { const response = await applyToPermanence(permId); - await Swal.fire("Succès ✅", response.message, "success"); + await Swal.fire('Succès ✅', response.message, 'success'); fetchData(); } catch (err) { console.error("Erreur lors de l'inscription", err); - Swal.fire("Erreur", "Impossible de s'inscrire à la permanence.", "error"); + Swal.fire('Erreur', "Impossible de s'inscrire à la permanence.", 'error'); } finally { setIsSubmitting(false); } @@ -60,6 +58,7 @@ export const AvailablePermanencesPage: React.FC = () => { return (
+
{ ); }; - export const MyPermanencesPage: React.FC = () => { const [myPermanences, setMyPermanences] = useState([]); @@ -87,15 +85,15 @@ export const MyPermanencesPage: React.FC = () => { const perms = await getMyPermanences(); setMyPermanences(perms); } catch (err) { - console.error("Erreur lors du chargement", err); - Swal.fire("Erreur", "Impossible de charger vos permanences.", "error"); + console.error('Erreur lors du chargement', err); + Swal.fire('Erreur', 'Impossible de charger vos permanences.', 'error'); } }; const handleCancelPermanence = async (permId: number) => { const selectedPermanence = myPermanences.find((perm) => perm.id === permId); if (!selectedPermanence) { - Swal.fire("Erreur", "Permanence non trouvée.", "error"); + Swal.fire('Erreur', 'Permanence non trouvée.', 'error'); return; } @@ -104,49 +102,46 @@ export const MyPermanencesPage: React.FC = () => { deadline.setDate(deadline.getDate() - 1); if (now > deadline) { - Swal.fire("Info", "La désinscription n'est plus possible.", "info"); + Swal.fire('Info', "La désinscription n'est plus possible.", 'info'); return; } const result = await Swal.fire({ - title: "Êtes-vous sûr ?", - text: "Vous ne pourrez plus revenir en arrière.", - icon: "warning", + title: 'Êtes-vous sûr ?', + text: 'Vous ne pourrez plus revenir en arrière.', + icon: 'warning', showCancelButton: true, - confirmButtonText: "Oui, me désinscrire", - cancelButtonText: "Annuler", - confirmButtonColor: "#d33", - cancelButtonColor: "#3085d6", + confirmButtonText: 'Oui, me désinscrire', + cancelButtonText: 'Annuler', + confirmButtonColor: '#d33', + cancelButtonColor: '#3085d6', }); if (!result.isConfirmed) return; try { const response = await cancelPermanence(permId); - Swal.fire("Succès ✅", response.message, "success"); + Swal.fire('Succès ✅', response.message, 'success'); fetchMyPermanences(); } catch (err) { console.error("Erreur lors de l'annulation", err); - Swal.fire("Erreur", "Impossible de vous désinscrire.", "error"); + Swal.fire('Erreur', 'Impossible de vous désinscrire.', 'error'); } }; return (
+
- +
); }; - export const RespoCallPage = () => { const [isRespo, setIsRespo] = useState(null); @@ -169,7 +164,7 @@ export const RespoCallPage = () => { const result = await isUserRespo(Number(decoded.userId)); setIsRespo(result.data === true); } catch (error) { - console.error("Erreur lors de la vérification du rôle respo", error); + console.error('Erreur lors de la vérification du rôle respo', error); setIsRespo(false); } }; @@ -180,6 +175,7 @@ export const RespoCallPage = () => { return (
+
{isRespo === null ? ( diff --git a/frontend/src/pages/plannings.tsx b/frontend/src/pages/plannings.tsx index b2c2cbc..f24312d 100644 --- a/frontend/src/pages/plannings.tsx +++ b/frontend/src/pages/plannings.tsx @@ -1,10 +1,12 @@ -import { Footer } from "../components/footer"; -import { Navbar } from "../components/navbar"; -import { PlanningSection } from "../components/Plannings/planningSection"; +import { Footer } from '../components/footer'; +import EmergencyModal from '../components/home/emergencyModal'; +import { Navbar } from '../components/navbar'; +import { PlanningSection } from '../components/Plannings/planningSection'; const PlanningsPage = () => (
+
diff --git a/frontend/src/pages/privacy.tsx b/frontend/src/pages/privacy.tsx index 4f1a98d..8063e1d 100644 --- a/frontend/src/pages/privacy.tsx +++ b/frontend/src/pages/privacy.tsx @@ -1,10 +1,12 @@ -import { Footer } from "../components/footer"; -import { Navbar } from "../components/navbar"; -import { PrivacySection } from "../components/privacy/privacySection"; +import { Footer } from '../components/footer'; +import EmergencyModal from '../components/home/emergencyModal'; +import { Navbar } from '../components/navbar'; +import { PrivacySection } from '../components/privacy/privacySection'; const PrivacyPage = () => (
+
diff --git a/frontend/src/pages/profil.tsx b/frontend/src/pages/profil.tsx index c7a8f26..558b6e9 100644 --- a/frontend/src/pages/profil.tsx +++ b/frontend/src/pages/profil.tsx @@ -1,32 +1,34 @@ -import { useNavigate } from "react-router-dom"; +import { useNavigate } from 'react-router-dom'; -import { Footer } from "../components/footer"; -import { Navbar } from "../components/navbar"; -import { ProfilForm } from "../components/profil/profilForm"; -import { UserPreferences } from "../components/profil/roleForm"; -import { getPermission } from "../services/requests/user.service"; +import { Footer } from '../components/footer'; +import EmergencyModal from '../components/home/emergencyModal'; +import { Navbar } from '../components/navbar'; +import { ProfilForm } from '../components/profil/profilForm'; +import { UserPreferences } from '../components/profil/roleForm'; +import { getPermission } from '../services/requests/user.service'; const ProfilPage = () => { const navigate = useNavigate(); const permission = getPermission(); if (!permission) { - navigate("/"); + navigate('/'); return null; } return (
+
- {(permission === "Student" || permission === "Admin") && ()} + {(permission === 'Student' || permission === 'Admin') && }
); -} +}; export default ProfilPage; diff --git a/frontend/src/pages/roadbook.tsx b/frontend/src/pages/roadbook.tsx index abcc8ae..de80e67 100644 --- a/frontend/src/pages/roadbook.tsx +++ b/frontend/src/pages/roadbook.tsx @@ -1,12 +1,14 @@ -import React from "react"; +import React from 'react'; -import { Footer } from "../components/footer"; -import { Navbar } from "../components/navbar"; -import { RoadBookCard } from "../components/roadbook/roadbookCard"; +import { Footer } from '../components/footer'; +import EmergencyModal from '../components/home/emergencyModal'; +import { Navbar } from '../components/navbar'; +import { RoadBookCard } from '../components/roadbook/roadbookCard'; const Roadbook: React.FC = () => (
+
diff --git a/frontend/src/pages/sdi.tsx b/frontend/src/pages/sdi.tsx index 234f164..a3b7d2e 100644 --- a/frontend/src/pages/sdi.tsx +++ b/frontend/src/pages/sdi.tsx @@ -1,10 +1,12 @@ -import { Footer } from "../components/footer"; -import { Navbar } from "../components/navbar"; -import { SdiSection } from "../components/WEI_SDI_Food/sdiSection"; +import { Footer } from '../components/footer'; +import EmergencyModal from '../components/home/emergencyModal'; +import { Navbar } from '../components/navbar'; +import { SdiSection } from '../components/WEI_SDI_Food/sdiSection'; const SdiPage = () => (
+
diff --git a/frontend/src/pages/shotgun.tsx b/frontend/src/pages/shotgun.tsx index 91064e2..1cd9e52 100644 --- a/frontend/src/pages/shotgun.tsx +++ b/frontend/src/pages/shotgun.tsx @@ -1,15 +1,16 @@ -import { Footer } from "../components/footer"; -import { Navbar } from "../components/navbar"; -import { PreregisterCESection } from "../components/shotgun/preregisterCESection"; -import { PreregisterTeamSection } from "../components/shotgun/preregisterTeamSection"; -import { Shotgun } from "../components/shotgun/shotgunSection"; +import { Footer } from '../components/footer'; +import EmergencyModal from '../components/home/emergencyModal'; +import { Navbar } from '../components/navbar'; +import { PreregisterCESection } from '../components/shotgun/preregisterCESection'; +import { PreregisterTeamSection } from '../components/shotgun/preregisterTeamSection'; +import { Shotgun } from '../components/shotgun/shotgunSection'; const ShotgunPage = () => (
+
- diff --git a/frontend/src/pages/wei.tsx b/frontend/src/pages/wei.tsx index dca67c6..8375974 100644 --- a/frontend/src/pages/wei.tsx +++ b/frontend/src/pages/wei.tsx @@ -1,27 +1,29 @@ -import { useNavigate } from "react-router-dom"; +import { useNavigate } from 'react-router-dom'; -import { Footer } from "../components/footer"; -import { Navbar } from "../components/navbar"; -import { TentPublic } from "../components/tent/tentSection"; -import { WeiSection } from "../components/WEI_SDI_Food/weiSection"; -import { getPermission } from "../services/requests/user.service"; +import { Footer } from '../components/footer'; +import EmergencyModal from '../components/home/emergencyModal'; +import { Navbar } from '../components/navbar'; +import { TentPublic } from '../components/tent/tentSection'; +import { WeiSection } from '../components/WEI_SDI_Food/weiSection'; +import { getPermission } from '../services/requests/user.service'; const WeiPage = () => { const navigate = useNavigate(); const permission = getPermission(); if (!permission) { - navigate("/"); + navigate('/'); return null; } return (
+
- {(permission === "Nouveau" || permission === "Admin") && } + {(permission === 'Nouveau' || permission === 'Admin') && }