diff --git a/backend/.env.example b/backend/.env.example index 751c426..294ca9c 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -17,6 +17,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..92f1a6b 100644 --- a/backend/src/controllers/user.controller.ts +++ b/backend/src/controllers/user.controller.ts @@ -1,6 +1,7 @@ import type { AdminCreateUserDto, PermissionParams, ProfileBody, SyncBody, UserIdParams } from '../dto/user.dto'; import * as user_service from '../services/user.service'; import { Error, Ok } from '../utils/responses'; +import { type UserContactInformation } from '../../types/user'; import type { AppRequestHandler } from '../types/http'; export const getUsersAdmin: AppRequestHandler = async (_req, res) => { @@ -66,6 +67,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..4f826bb --- /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'emprisonnemen", 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_even_morg.sql b/backend/src/database/migrations/0024_even_morg.sql new file mode 100644 index 0000000..94a1760 --- /dev/null +++ b/backend/src/database/migrations/0024_even_morg.sql @@ -0,0 +1,8 @@ +CREATE TABLE "user_informations" ( + "user_id" integer PRIMARY KEY NOT NULL, + "urgency_contact_name" text, + "urgency_contact_phone" text, + "contact_CE" text +); +--> 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; \ No newline at end of file diff --git a/backend/src/database/migrations/0025_natural_human_cannonball.sql b/backend/src/database/migrations/0025_natural_human_cannonball.sql new file mode 100644 index 0000000..394fdcc --- /dev/null +++ b/backend/src/database/migrations/0025_natural_human_cannonball.sql @@ -0,0 +1 @@ +ALTER TABLE "user_informations" DROP COLUMN "contact_CE"; \ No newline at end of file diff --git a/backend/src/database/migrations/0026_high_wind_dancer.sql b/backend/src/database/migrations/0026_high_wind_dancer.sql new file mode 100644 index 0000000..91cbbd8 --- /dev/null +++ b/backend/src/database/migrations/0026_high_wind_dancer.sql @@ -0,0 +1,18 @@ +CREATE TYPE "public"."vss_form" AS ENUM('pending', '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 "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 "vssqcmanswer" ADD CONSTRAINT "vssqcmanswer_questionid_vssqcmquestion_id_fk" FOREIGN KEY ("questionid") REFERENCES "public"."vssqcmquestion"("id") ON DELETE cascade ON UPDATE no action; diff --git a/backend/src/database/migrations/0027_good_unus.sql b/backend/src/database/migrations/0027_good_unus.sql new file mode 100644 index 0000000..cab44f7 --- /dev/null +++ b/backend/src/database/migrations/0027_good_unus.sql @@ -0,0 +1 @@ +ALTER TYPE "public"."vss_form" ADD VALUE 'toretry' BEFORE 'validated'; \ 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..889198d 100644 --- a/backend/src/database/migrations/meta/0023_snapshot.json +++ b/backend/src/database/migrations/meta/0023_snapshot.json @@ -1314,4 +1314,4 @@ "schemas": {}, "tables": {} } -} \ No newline at end of file +} diff --git a/backend/src/database/migrations/meta/0024_snapshot.json b/backend/src/database/migrations/meta/0024_snapshot.json new file mode 100644 index 0000000..9961248 --- /dev/null +++ b/backend/src/database/migrations/meta/0024_snapshot.json @@ -0,0 +1,1199 @@ +{ + "id": "77c4b2f6-c24d-4eb9-80a9-42fb8b9ef380", + "prevId": "128e2d49-22e7-477d-a202-ae518c2e21ed", + "version": "7", + "dialect": "postgresql", + "tables": { + "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.user_informations": { + "name": "user_informations", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": true, + "notNull": true + }, + "urgency_contact_name": { + "name": "urgency_contact_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "urgency_contact_phone": { + "name": "urgency_contact_phone", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "contact_CE": { + "name": "contact_CE", + "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 + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} diff --git a/backend/src/database/migrations/meta/0025_snapshot.json b/backend/src/database/migrations/meta/0025_snapshot.json new file mode 100644 index 0000000..c58070e --- /dev/null +++ b/backend/src/database/migrations/meta/0025_snapshot.json @@ -0,0 +1,1329 @@ +{ + "id": "f2813605-996e-4461-8e87-8b135d3eefd3", + "prevId": "77c4b2f6-c24d-4eb9-80a9-42fb8b9ef380", + "version": "7", + "dialect": "postgresql", + "tables": { + "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.user_informations": { + "name": "user_informations", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": true, + "notNull": true + }, + "urgency_contact_name": { + "name": "urgency_contact_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "urgency_contact_phone": { + "name": "urgency_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 + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "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..3e69f00 --- /dev/null +++ b/backend/src/database/migrations/meta/0026_snapshot.json @@ -0,0 +1,1444 @@ +{ + "id": "c76925a0-a42e-42c9-8f72-0d914fe74da9", + "prevId": "f2813605-996e-4461-8e87-8b135d3eefd3", + "version": "7", + "dialect": "postgresql", + "tables": { + "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()" + }, + "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 + }, + "urgency_contact_name": { + "name": "urgency_contact_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "urgency_contact_phone": { + "name": "urgency_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", + "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/0027_snapshot.json b/backend/src/database/migrations/meta/0027_snapshot.json new file mode 100644 index 0000000..82f221c --- /dev/null +++ b/backend/src/database/migrations/meta/0027_snapshot.json @@ -0,0 +1,1445 @@ +{ + "id": "8ceeb5a4-a02f-426a-9b8f-5a15ac8d26cd", + "prevId": "c76925a0-a42e-42c9-8f72-0d914fe74da9", + "version": "7", + "dialect": "postgresql", + "tables": { + "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()" + }, + "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 + }, + "urgency_contact_name": { + "name": "urgency_contact_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "urgency_contact_phone": { + "name": "urgency_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 7c107c1..6bdde63 100644 --- a/backend/src/database/migrations/meta/_journal.json +++ b/backend/src/database/migrations/meta/_journal.json @@ -1,174 +1,202 @@ { - "version": "7", - "dialect": "postgresql", - "entries": [ - { - "idx": 0, - "version": "7", - "when": 1743857362115, - "tag": "0000_workable_rafael_vega", - "breakpoints": true - }, - { - "idx": 1, - "version": "7", - "when": 1743868246465, - "tag": "0001_stormy_quicksilver", - "breakpoints": true - }, - { - "idx": 2, - "version": "7", - "when": 1743897931065, - "tag": "0002_luxuriant_lockjaw", - "breakpoints": true - }, - { - "idx": 3, - "version": "7", - "when": 1743979532382, - "tag": "0003_material_lorna_dane", - "breakpoints": true - }, - { - "idx": 4, - "version": "7", - "when": 1744064732443, - "tag": "0004_careless_misty_knight", - "breakpoints": true - }, - { - "idx": 5, - "version": "7", - "when": 1744118020611, - "tag": "0005_needy_darwin", - "breakpoints": true - }, - { - "idx": 6, - "version": "7", - "when": 1744126517418, - "tag": "0006_bright_boomerang", - "breakpoints": true - }, - { - "idx": 7, - "version": "7", - "when": 1744212319949, - "tag": "0007_striped_mulholland_black", - "breakpoints": true - }, - { - "idx": 8, - "version": "7", - "when": 1744215076184, - "tag": "0008_striped_bushwacker", - "breakpoints": true - }, - { - "idx": 9, - "version": "7", - "when": 1744241202940, - "tag": "0009_previous_mystique", - "breakpoints": true - }, - { - "idx": 10, - "version": "7", - "when": 1744241242502, - "tag": "0010_fair_mulholland_black", - "breakpoints": true - }, - { - "idx": 11, - "version": "7", - "when": 1744241259194, - "tag": "0011_fearless_nextwave", - "breakpoints": true - }, - { - "idx": 12, - "version": "7", - "when": 1746040673007, - "tag": "0012_productive_giant_man", - "breakpoints": true - }, - { - "idx": 13, - "version": "7", - "when": 1752757642698, - "tag": "0013_curly_angel", - "breakpoints": true - }, - { - "idx": 14, - "version": "7", - "when": 1753638076374, - "tag": "0014_special_reaper", - "breakpoints": true - }, - { - "idx": 15, - "version": "7", - "when": 1753744481956, - "tag": "0015_greedy_genesis", - "breakpoints": true - }, - { - "idx": 16, - "version": "7", - "when": 1754903172897, - "tag": "0016_sudden_ultimatum", - "breakpoints": true - }, - { - "idx": 17, - "version": "7", - "when": 1755637642205, - "tag": "0017_melted_meggan", - "breakpoints": true - }, - { - "idx": 18, - "version": "7", - "when": 1755858317109, - "tag": "0018_puzzling_arachne", - "breakpoints": true - }, - { - "idx": 19, - "version": "7", - "when": 1755906116757, - "tag": "0019_complete_moondragon", - "breakpoints": true - }, - { - "idx": 20, - "version": "7", - "when": 1755907709198, - "tag": "0020_strange_colonel_america", - "breakpoints": true - }, - { - "idx": 21, - "version": "7", - "when": 1756063134903, - "tag": "0021_colossal_madame_web", - "breakpoints": true - }, - { - "idx": 22, - "version": "7", - "when": 1757002717384, - "tag": "0022_light_omega_red", - "breakpoints": true - }, - { - "idx": 23, - "version": "7", - "when": 1782943789540, - "tag": "0023_zippy_colonel_america", - "breakpoints": true - } - ] -} \ No newline at end of file + "version": "7", + "dialect": "postgresql", + "entries": [ + { + "idx": 0, + "version": "7", + "when": 1743857362115, + "tag": "0000_workable_rafael_vega", + "breakpoints": true + }, + { + "idx": 1, + "version": "7", + "when": 1743868246465, + "tag": "0001_stormy_quicksilver", + "breakpoints": true + }, + { + "idx": 2, + "version": "7", + "when": 1743897931065, + "tag": "0002_luxuriant_lockjaw", + "breakpoints": true + }, + { + "idx": 3, + "version": "7", + "when": 1743979532382, + "tag": "0003_material_lorna_dane", + "breakpoints": true + }, + { + "idx": 4, + "version": "7", + "when": 1744064732443, + "tag": "0004_careless_misty_knight", + "breakpoints": true + }, + { + "idx": 5, + "version": "7", + "when": 1744118020611, + "tag": "0005_needy_darwin", + "breakpoints": true + }, + { + "idx": 6, + "version": "7", + "when": 1744126517418, + "tag": "0006_bright_boomerang", + "breakpoints": true + }, + { + "idx": 7, + "version": "7", + "when": 1744212319949, + "tag": "0007_striped_mulholland_black", + "breakpoints": true + }, + { + "idx": 8, + "version": "7", + "when": 1744215076184, + "tag": "0008_striped_bushwacker", + "breakpoints": true + }, + { + "idx": 9, + "version": "7", + "when": 1744241202940, + "tag": "0009_previous_mystique", + "breakpoints": true + }, + { + "idx": 10, + "version": "7", + "when": 1744241242502, + "tag": "0010_fair_mulholland_black", + "breakpoints": true + }, + { + "idx": 11, + "version": "7", + "when": 1744241259194, + "tag": "0011_fearless_nextwave", + "breakpoints": true + }, + { + "idx": 12, + "version": "7", + "when": 1746040673007, + "tag": "0012_productive_giant_man", + "breakpoints": true + }, + { + "idx": 13, + "version": "7", + "when": 1752757642698, + "tag": "0013_curly_angel", + "breakpoints": true + }, + { + "idx": 14, + "version": "7", + "when": 1753638076374, + "tag": "0014_special_reaper", + "breakpoints": true + }, + { + "idx": 15, + "version": "7", + "when": 1753744481956, + "tag": "0015_greedy_genesis", + "breakpoints": true + }, + { + "idx": 16, + "version": "7", + "when": 1754903172897, + "tag": "0016_sudden_ultimatum", + "breakpoints": true + }, + { + "idx": 17, + "version": "7", + "when": 1755637642205, + "tag": "0017_melted_meggan", + "breakpoints": true + }, + { + "idx": 18, + "version": "7", + "when": 1755858317109, + "tag": "0018_puzzling_arachne", + "breakpoints": true + }, + { + "idx": 19, + "version": "7", + "when": 1755906116757, + "tag": "0019_complete_moondragon", + "breakpoints": true + }, + { + "idx": 20, + "version": "7", + "when": 1755907709198, + "tag": "0020_strange_colonel_america", + "breakpoints": true + }, + { + "idx": 21, + "version": "7", + "when": 1756063134903, + "tag": "0021_colossal_madame_web", + "breakpoints": true + }, + { + "idx": 22, + "version": "7", + "when": 1757002717384, + "tag": "0022_light_omega_red", + "breakpoints": true + }, + { + "idx": 23, + "version": "7", + "when": 1782943789540, + "tag": "0023_zippy_colonel_america", + "breakpoints": true + }, + { + "idx": 24, + "version": "7", + "when": 1783854058299, + "tag": "0024_even_morg", + "breakpoints": true + }, + { + "idx": 25, + "version": "7", + "when": 1784272112439, + "tag": "0025_natural_human_cannonball", + "breakpoints": true + }, + { + "idx": 26, + "version": "7", + "when": 1784298990793, + "tag": "0026_high_wind_dancer", + "breakpoints": true + }, + { + "idx": 27, + "version": "7", + "when": 1784372155559, + "tag": "0027_good_unus", + "breakpoints": true + } + ] +} 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 8ada041..c08ae93 100644 --- a/backend/src/schemas/Basic/user.schema.ts +++ b/backend/src/schemas/Basic/user.schema.ts @@ -1,17 +1,20 @@ -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 userSchema = pgTable("users", { - id: serial("id").primaryKey(), - first_name: text("first_name"), - last_name: text("last_name"), - email: text("email").unique(), - majeur: boolean("majeur"), - branch: text("branch"), - contact: text("contact"), - password: text("password"), - permission: text("permission").default("Nouveau"), // Par défaut, le rôle sera "Nouveau" - discord_id: text("discord_id"), - created_at: timestamp("created_at").defaultNow(), +export const vssFormEnum = pgEnum('vss_form', ['pending', 'toretry', 'validated', 'rejected']); + +export const userSchema = pgTable('users', { + id: serial('id').primaryKey(), + first_name: text('first_name'), + last_name: text('last_name'), + email: text('email').unique(), + majeur: boolean('majeur'), + branch: text('branch'), + contact: text('contact'), + password: text('password'), + permission: text('permission').default('Nouveau'), // Par défaut, le rôle sera "Nouveau" + discord_id: text('discord_id'), + created_at: timestamp('created_at').defaultNow(), + 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..71892ec --- /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' }), + urgency_contact_name: text('urgency_contact_name'), + urgency_contact_phone: text('urgency_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..6d9d65e 100644 --- a/backend/src/services/user.service.ts +++ b/backend/src/services/user.service.ts @@ -4,6 +4,8 @@ import * as randomstring from 'randomstring'; import { db } from '../database/db'; // Import de la connexion PostgreSQL import type { AdminCreateUserDto } 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,34 @@ 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 { type UserContactInformation } from '../../types/user'; +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[]; +}; + +export type VssSubmissionPayload = { + answers: VssSubmissionAnswer[]; +}; + // Fonction pour récupérer un utilisateur par email export const getUserByEmail = async (email: string) => { try { @@ -39,6 +66,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 +256,193 @@ export const getUsers = async () => { } }; +export const getUserContactInformation = async (userId: number) => { + try { + const user = await db + .select({ + userId: userInformationSchema.user_id, + urgency_contact_name: userInformationSchema.urgency_contact_name, + urgency_contact_phone: userInformationSchema.urgency_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: UserContactInformation) => { + try { + const newContactInfo = { + user_id: userId, + urgency_contact_name: contact.urgency_contact_name, + urgency_contact_phone: contact.urgency_contact_phone, + }; + + const result = await db + .insert(userInformationSchema) + .values(newContactInfo) + .onConflictDoUpdate({ + target: userInformationSchema.user_id, + set: { + urgency_contact_name: contact.urgency_contact_name, + urgency_contact_phone: contact.urgency_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 [contactInformation] = await db + .select({ + userId: userInformationSchema.user_id, + }) + .from(userInformationSchema) + .where(eq(userInformationSchema.user_id, userId)); + + const [user] = await db + .select({ + vss_form: userSchema.vss_form, + }) + .from(userSchema) + .where(eq(userSchema.id, userId)); + + const vssForm = user?.vss_form ?? 'pending'; + + return { + hasUrgencyContactInformation: 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 f7c51fc..83db995 100644 --- a/backend/src/utils/secret.ts +++ b/backend/src/utils/secret.ts @@ -1,33 +1,37 @@ -import dotenv from "dotenv"; +import dotenv from 'dotenv'; dotenv.config(); -export const jwtSecret = process.env.JWT_SECRET || "default"; +export const jwtSecret = process.env.JWT_SECRET || 'default'; export const server_port = process.env.SERVER_PORT; -export const cas_login_url = process.env.CAS_LOGIN_URL || "default"; -export const cas_validate_url = process.env.CAS_VALIDATE_URL || "default"; -export const service_url = process.env.SERVICE_URL || "default"; -export const dev_db_url = process.env.DATABASE_URL || "default"; -export const postgres_password = process.env.POSTGRES_PASSWORD || "default"; -export const postgres_user = process.env.POSTGRES_USER || "default"; -export const postgres_port = process.env.POSTGRES_PORT || "default"; -export const postgres_db = process.env.POSTGRES_DB || "default"; -export const postgres_host = process.env.POSTGRES_HOST || "default"; -export const google_client_id = process.env.GOOGLE_CLIENT_ID || "default"; -export const google_client_secret = process.env.GOOGLE_CLIENT_SECRET || "default"; -export const google_client_uri = process.env.GOOGLE_REDIRECT_URI || "default"; -export const spreadsheet_id = process.env.SPREADSHEET_ID || "default"; -export const api_utt_username = process.env.API_UTT_USERNAME || "default"; -export const api_utt_password = process.env.API_UTT_PASSWORD || "default"; -export const api_utt_auth_url = process.env.API_UTT_AUTH_URL || "default"; -export const api_utt_admis_url = process.env.API_UTT_ADMIS_URL || "default"; -export const api_utt_admis_url_ismajor = process.env.API_UTT_ADMIS_URL_ISMAJOR || "default"; -export const email_host = process.env.EMAIL_HOST || "default"; -export const email_user = process.env.EMAIL_USER || "default"; -export const email_password = process.env.EMAIL_PASSWORD || "default"; -export const email_from = process.env.EMAIL_FROM || "default"; -export const discord_client_id = process.env.DISCORD_CLIENT_ID || "default"; -export const discord_client_secret = process.env.DISCORD_CLIENT_SECRET || "default"; -export const discord_redirect_uri = process.env.DISCORD_REDIRECT_URI || "default"; -export const shotgun_password = process.env.SHOTGUN_PASSWORD || ""; -export const automation_token = process.env.AUTOMATION_TOKEN || ""; +export const cas_login_url = process.env.CAS_LOGIN_URL || 'default'; +export const cas_validate_url = process.env.CAS_VALIDATE_URL || 'default'; +export const service_url = process.env.SERVICE_URL || 'default'; +export const dev_db_url = process.env.DATABASE_URL || 'default'; +export const postgres_password = process.env.POSTGRES_PASSWORD || 'default'; +export const postgres_user = process.env.POSTGRES_USER || 'default'; +export const postgres_port = process.env.POSTGRES_PORT || 'default'; +export const postgres_db = process.env.POSTGRES_DB || 'default'; +export const postgres_host = process.env.POSTGRES_HOST || 'default'; +export const google_client_id = process.env.GOOGLE_CLIENT_ID || 'default'; +export const google_client_secret = process.env.GOOGLE_CLIENT_SECRET || 'default'; +export const google_client_uri = process.env.GOOGLE_REDIRECT_URI || 'default'; +export const spreadsheet_id = process.env.SPREADSHEET_ID || 'default'; +export const api_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'; +export const api_utt_admis_url = process.env.API_UTT_ADMIS_URL || 'default'; +export const api_utt_admis_url_ismajor = process.env.API_UTT_ADMIS_URL_ISMAJOR || 'default'; +export const email_host = process.env.EMAIL_HOST || 'default'; +export const email_user = process.env.EMAIL_USER || 'default'; +export const email_password = process.env.EMAIL_PASSWORD || 'default'; +export const email_from = process.env.EMAIL_FROM || 'default'; +export const discord_client_id = process.env.DISCORD_CLIENT_ID || 'default'; +export const discord_client_secret = process.env.DISCORD_CLIENT_SECRET || 'default'; +export const discord_redirect_uri = process.env.DISCORD_REDIRECT_URI || 'default'; +export const shotgun_password = process.env.SHOTGUN_PASSWORD || ''; +export const automation_token = process.env.AUTOMATION_TOKEN || ''; 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/backend/types/user.d.ts b/backend/types/user.d.ts new file mode 100644 index 0000000..6df16da --- /dev/null +++ b/backend/types/user.d.ts @@ -0,0 +1,4 @@ +export type UserContactInformation = { + urgency_contact_name: string; + urgency_contact_phone: 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..1708a17 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,type 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,132 @@ 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..bb9cc3b 100644 --- a/frontend/src/components/WEI_SDI_Food/sdiSection.tsx +++ b/frontend/src/components/WEI_SDI_Food/sdiSection.tsx @@ -1,26 +1,48 @@ -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(false); + const [hasVssForm, setHasVssForm] = useState(false); + 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(); + fetchOnboardingStatus(); }, []); + const fetchOnboardingStatus = async () => { + try { + const onboardingStatus = await getCurrentUserOnboardingStatus(); + setHasContactInformation(onboardingStatus.hasUrgencyContactInformation); + 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 +53,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 +63,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 && roles.includes('Student') ? ( +
+

+ 🚫 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 && roles.includes('Student') ? ( +
+

+ 🚫 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 && roles.includes('Student') ? ( +
+

+ 🚫 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 +101,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..2e5eb51 100644 --- a/frontend/src/components/WEI_SDI_Food/weiSection.tsx +++ b/frontend/src/components/WEI_SDI_Food/weiSection.tsx @@ -1,26 +1,42 @@ -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 { 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(false); + const [hasVssForm, setHasVssForm] = useState(false); + const [needVssForm, setNeedsVssForm] = useState(false); 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(); + fetchOnboardingStatus(); }, []); + const fetchOnboardingStatus = async () => { + try { + const onboardingStatus = await getCurrentUserOnboardingStatus(); + setHasContactInformation(onboardingStatus.hasUrgencyContactInformation); + 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 +47,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 +57,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 +95,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/urgencyModal.tsx b/frontend/src/components/home/urgencyModal.tsx new file mode 100644 index 0000000..39842b4 --- /dev/null +++ b/frontend/src/components/home/urgencyModal.tsx @@ -0,0 +1,155 @@ +import { useEffect, useState } from 'react'; +import { useSearchParams } from 'react-router-dom'; + +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' | 'urgency' | 'vss'; + +function UrgencyModal() { + const [searchParams, setSearchParams] = useSearchParams(); + const [form, setForm] = useState({ urgency_contact_name: '', urgency_contact_phone: '' }); + const [flowStep, setFlowStep] = useState('idle'); + const [error, setError] = useState(null); + + const isLogin = searchParams.get('login') === 'true'; + + useEffect(() => { + if (!isLogin) { + setFlowStep('idle'); + setForm({ urgency_contact_name: '', urgency_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.hasUrgencyContactInformation) { + setFlowStep('urgency'); + return; + } + + if (status.needsVssForm) { + setFlowStep('vss'); + return; + } + + setFlowStep('idle'); + setSearchParams({}); + } catch { + if (!cancelled) { + setFlowStep('urgency'); + setError('Impossible de récupérer le statut du formulaire.'); + } + } + }; + + loadOnboardingStatus(); + + return () => { + cancelled = true; + }; + }, [isLogin, setSearchParams]); + + const closeFlow = () => { + setSearchParams({}); + setFlowStep('idle'); + setForm({ urgency_contact_name: '', urgency_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 ( + <> + +
+

Chargement du statut du formulaire...

+
+
+ + +
+

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, urgency_contact_name: e.target.value })} + /> + setForm({ ...form, urgency_contact_phone: e.target.value })} + /> +
+ + +
+
+
+ + { + window.dispatchEvent(new Event('user-onboarding-updated')); + }} + /> + + ); +} + +export default UrgencyModal; diff --git a/frontend/src/components/home/vssModal.tsx b/frontend/src/components/home/vssModal.tsx new file mode 100644 index 0000000..3f0b922 --- /dev/null +++ b/frontend/src/components/home/vssModal.tsx @@ -0,0 +1,276 @@ +import { useEffect, useState } from 'react'; + +import { type VssQuestionnaireQuestion, type 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..eefab42 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.hasUrgencyContactInformation); + 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('Student') && ( +
+

+ {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/ui/modal.tsx b/frontend/src/components/ui/modal.tsx new file mode 100644 index 0000000..a2f92cf --- /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..cd0ab46 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; + urgency_contact_name: string; + urgency_contact_phone: string; +} + +export interface CreateUserContactInformationRequest { + urgency_contact_name: string; + urgency_contact_phone: string; +} + +export interface UserOnboardingStatus { + hasUrgencyContactInformation: 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/home.tsx b/frontend/src/pages/home.tsx index 54d9601..44a9259 100644 --- a/frontend/src/pages/home.tsx +++ b/frontend/src/pages/home.tsx @@ -1,14 +1,15 @@ -import { Footer } from "../components/footer"; -import { Infos } from "../components/home/infosSection"; -import { SocialLinks } from "../components/home/socialSection"; -import { Navbar } from "../components/navbar"; +import { Footer } from '../components/footer'; +import { Infos } from '../components/home/infosSection'; +import { SocialLinks } from '../components/home/socialSection'; +import UrgencyModal from '../components/home/urgencyModal'; +import { Navbar } from '../components/navbar'; const HomePage = () => (
+ -
); diff --git a/frontend/src/services/requests/user.service.ts b/frontend/src/services/requests/user.service.ts index c154779..38b7fbe 100644 --- a/frontend/src/services/requests/user.service.ts +++ b/frontend/src/services/requests/user.service.ts @@ -1,4 +1,13 @@ -import type { NewUser, User } from '../../interfaces/user.interface'; +import { + type CreateUserContactInformationRequest, + type NewUser, + type User, + type UserContactInformation, + type UserOnboardingStatus, + type VssQuestionnaireQuestion, + type VssSubmissionRequest, + type VssSubmissionResponse, +} from '../../interfaces/user.interface'; import api from '../api'; export const getPermission = (): string | null => { @@ -41,6 +50,35 @@ export const getUsersByPermission = async () => { return users; }; +export const getUserContactInformation = async (userId: number) => { + const response = await api.get(`/user/admin/getusercontactinformation/${userId}`); + const users: UserContactInformation = response.data.data; + return users; +}; + +export const createUserContactInformation = async (data: CreateUserContactInformationRequest) => { + const response = await api.post(`/user/user/usercontactinformation`, data); + return response.data; +}; + +export const getCurrentUserOnboardingStatus = async () => { + const response = await api.get('/user/onboarding-status'); + const status: UserOnboardingStatus = response.data.data; + return status; +}; + +export const getVssQuestionnaire = async () => { + const response = await api.get('/user/vss/questionnaire'); + const questionnaire: VssQuestionnaireQuestion[] = response.data.data; + return questionnaire; +}; + +export const submitVssQuestionnaire = async (data: VssSubmissionRequest) => { + const response = await api.post('/user/vss/questionnaire', data); + const result: VssSubmissionResponse = response.data.data; + return result; +}; + export const getCurrentUser = async () => { const res = await api.get('/user/user/me'); return res.data.data;