diff --git a/backend/src/controllers/permanence.controller.ts b/backend/src/controllers/permanence.controller.ts index 630fb72..87f5336 100644 --- a/backend/src/controllers/permanence.controller.ts +++ b/backend/src/controllers/permanence.controller.ts @@ -26,7 +26,7 @@ const validatePermanenceData = (start_at: string, end_at: string) => { export const createPermanence = async (req: Request, res: Response) => { const { name, description, location, start_at, end_at, capacity, difficulty, respoId } = req.body; - if (!name || !location || !start_at || !end_at || !capacity || !difficulty || !respoId) { + if (!name || !location || !start_at || !end_at || !capacity || !difficulty) { Error(res, { msg: "Tous les champs sont requis" }); return; } @@ -59,8 +59,7 @@ export const createPermanence = async (req: Request, res: Response) => { export const updatePermanence = async (req: Request, res: Response) => { const { permId, name, description, location, start_at, end_at, capacity, difficulty, respoId } = req.body; - - if (!name || !location || !start_at || !end_at || !capacity || !difficulty || !respoId) { + if (!name || !location || !start_at || !end_at || !capacity || !difficulty) { Error(res, { msg: "Tous les champs sont requis" }); return; } diff --git a/backend/src/database/migrations/0021_colossal_madame_web.sql b/backend/src/database/migrations/0021_colossal_madame_web.sql new file mode 100644 index 0000000..123c2df --- /dev/null +++ b/backend/src/database/migrations/0021_colossal_madame_web.sql @@ -0,0 +1,10 @@ +CREATE TABLE "user_tent" ( + "user_id_1" integer, + "user_id_2" integer, + "confirmed" boolean DEFAULT false, + "created_at" timestamp DEFAULT now(), + CONSTRAINT "user_tent_user_id_1_user_id_2_pk" PRIMARY KEY("user_id_1","user_id_2") +); +--> statement-breakpoint +ALTER TABLE "user_tent" ADD CONSTRAINT "user_tent_user_id_1_users_id_fk" FOREIGN KEY ("user_id_1") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "user_tent" ADD CONSTRAINT "user_tent_user_id_2_users_id_fk" FOREIGN KEY ("user_id_2") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action; \ No newline at end of file diff --git a/backend/src/database/migrations/meta/0021_snapshot.json b/backend/src/database/migrations/meta/0021_snapshot.json new file mode 100644 index 0000000..546fbbb --- /dev/null +++ b/backend/src/database/migrations/meta/0021_snapshot.json @@ -0,0 +1,1278 @@ +{ + "id": "9a19155e-ddd0-492f-a2df-0bb9ef0d9879", + "prevId": "5570d860-0dbd-48fe-9fb5-5314cdb1a473", + "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 + } + }, + "indexes": {}, + "foreignKeys": { + "bus_attribution_user_id_users_id_fk": { + "name": "bus_attribution_user_id_users_id_fk", + "tableFrom": "bus_attribution", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.challenge_validation": { + "name": "challenge_validation", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "challenge_id": { + "name": "challenge_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "validated_by_admin_id": { + "name": "validated_by_admin_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "validated_at": { + "name": "validated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "target_user_id": { + "name": "target_user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "target_team_id": { + "name": "target_team_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "target_faction_id": { + "name": "target_faction_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "points": { + "name": "points", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "added_by_admin_id": { + "name": "added_by_admin_id", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "challenge_validation_challenge_id_challenges_id_fk": { + "name": "challenge_validation_challenge_id_challenges_id_fk", + "tableFrom": "challenge_validation", + "tableTo": "challenges", + "columnsFrom": [ + "challenge_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "challenge_validation_validated_by_admin_id_users_id_fk": { + "name": "challenge_validation_validated_by_admin_id_users_id_fk", + "tableFrom": "challenge_validation", + "tableTo": "users", + "columnsFrom": [ + "validated_by_admin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "challenge_validation_target_user_id_users_id_fk": { + "name": "challenge_validation_target_user_id_users_id_fk", + "tableFrom": "challenge_validation", + "tableTo": "users", + "columnsFrom": [ + "target_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "challenge_validation_target_team_id_teams_id_fk": { + "name": "challenge_validation_target_team_id_teams_id_fk", + "tableFrom": "challenge_validation", + "tableTo": "teams", + "columnsFrom": [ + "target_team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "challenge_validation_target_faction_id_factions_id_fk": { + "name": "challenge_validation_target_faction_id_factions_id_fk", + "tableFrom": "challenge_validation", + "tableTo": "factions", + "columnsFrom": [ + "target_faction_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "challenge_validation_added_by_admin_id_users_id_fk": { + "name": "challenge_validation_added_by_admin_id_users_id_fk", + "tableFrom": "challenge_validation", + "tableTo": "users", + "columnsFrom": [ + "added_by_admin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.registration_tokens": { + "name": "registration_tokens", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "registration_tokens_user_id_users_id_fk": { + "name": "registration_tokens_user_id_users_id_fk", + "tableFrom": "registration_tokens", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "registration_tokens_token_unique": { + "name": "registration_tokens_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.role_points": { + "name": "role_points", + "schema": "", + "columns": { + "role_points": { + "name": "role_points", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "points": { + "name": "points", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "role_points_role_points_roles_id_fk": { + "name": "role_points_role_points_roles_id_fk", + "tableFrom": "role_points", + "tableTo": "roles", + "columnsFrom": [ + "role_points" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "role_points_role_points_pk": { + "name": "role_points_role_points_pk", + "columns": [ + "role_points" + ] + } + }, + "uniqueConstraints": { + "role_points_role_points_unique": { + "name": "role_points_role_points_unique", + "nullsNotDistinct": false, + "columns": [ + "role_points" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.team_faction": { + "name": "team_faction", + "schema": "", + "columns": { + "faction_id": { + "name": "faction_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "team_id": { + "name": "team_id", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "team_faction_faction_id_factions_id_fk": { + "name": "team_faction_faction_id_factions_id_fk", + "tableFrom": "team_faction", + "tableTo": "factions", + "columnsFrom": [ + "faction_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "team_faction_team_id_teams_id_fk": { + "name": "team_faction_team_id_teams_id_fk", + "tableFrom": "team_faction", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "team_faction_faction_id_team_id_pk": { + "name": "team_faction_faction_id_team_id_pk", + "columns": [ + "faction_id", + "team_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.team_shotgun": { + "name": "team_shotgun", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "timestamp": { + "name": "timestamp", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "team_shotgun_team_id_teams_id_fk": { + "name": "team_shotgun_team_id_teams_id_fk", + "tableFrom": "team_shotgun", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.respo_permanences": { + "name": "respo_permanences", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "permanence_id": { + "name": "permanence_id", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "respo_permanences_user_id_users_id_fk": { + "name": "respo_permanences_user_id_users_id_fk", + "tableFrom": "respo_permanences", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "respo_permanences_permanence_id_permanences_id_fk": { + "name": "respo_permanences_permanence_id_permanences_id_fk", + "tableFrom": "respo_permanences", + "tableTo": "permanences", + "columnsFrom": [ + "permanence_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "respo_permanences_user_id_permanence_id_pk": { + "name": "respo_permanences_user_id_permanence_id_pk", + "columns": [ + "user_id", + "permanence_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_permanences": { + "name": "user_permanences", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "permanence_id": { + "name": "permanence_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "registered_at": { + "name": "registered_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "claimed": { + "name": "claimed", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + } + }, + "indexes": {}, + "foreignKeys": { + "user_permanences_user_id_users_id_fk": { + "name": "user_permanences_user_id_users_id_fk", + "tableFrom": "user_permanences", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_permanences_permanence_id_permanences_id_fk": { + "name": "user_permanences_permanence_id_permanences_id_fk", + "tableFrom": "user_permanences", + "tableTo": "permanences", + "columnsFrom": [ + "permanence_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "user_permanences_user_id_permanence_id_pk": { + "name": "user_permanences_user_id_permanence_id_pk", + "columns": [ + "user_id", + "permanence_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_preferences": { + "name": "user_preferences", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "role_id": { + "name": "role_id", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "user_preferences_user_id_users_id_fk": { + "name": "user_preferences_user_id_users_id_fk", + "tableFrom": "user_preferences", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_preferences_role_id_roles_id_fk": { + "name": "user_preferences_role_id_roles_id_fk", + "tableFrom": "user_preferences", + "tableTo": "roles", + "columnsFrom": [ + "role_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "user_preferences_user_id_role_id_pk": { + "name": "user_preferences_user_id_role_id_pk", + "columns": [ + "user_id", + "role_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_roles": { + "name": "user_roles", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "role_id": { + "name": "role_id", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "user_roles_user_id_users_id_fk": { + "name": "user_roles_user_id_users_id_fk", + "tableFrom": "user_roles", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_roles_role_id_roles_id_fk": { + "name": "user_roles_role_id_roles_id_fk", + "tableFrom": "user_roles", + "tableTo": "roles", + "columnsFrom": [ + "role_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_teams": { + "name": "user_teams", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "team_id": { + "name": "team_id", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "user_teams_user_id_users_id_fk": { + "name": "user_teams_user_id_users_id_fk", + "tableFrom": "user_teams", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_teams_team_id_teams_id_fk": { + "name": "user_teams_team_id_teams_id_fk", + "tableFrom": "user_teams", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "user_teams_user_id_team_id_pk": { + "name": "user_teams_user_id_team_id_pk", + "columns": [ + "user_id", + "team_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_tent": { + "name": "user_tent", + "schema": "", + "columns": { + "user_id_1": { + "name": "user_id_1", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "user_id_2": { + "name": "user_id_2", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "confirmed": { + "name": "confirmed", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "user_tent_user_id_1_users_id_fk": { + "name": "user_tent_user_id_1_users_id_fk", + "tableFrom": "user_tent", + "tableTo": "users", + "columnsFrom": [ + "user_id_1" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_tent_user_id_2_users_id_fk": { + "name": "user_tent_user_id_2_users_id_fk", + "tableFrom": "user_tent", + "tableTo": "users", + "columnsFrom": [ + "user_id_2" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "user_tent_user_id_1_user_id_2_pk": { + "name": "user_tent_user_id_1_user_id_2_pk", + "columns": [ + "user_id_1", + "user_id_2" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/backend/src/database/migrations/meta/_journal.json b/backend/src/database/migrations/meta/_journal.json index 08f9de4..71ffc61 100644 --- a/backend/src/database/migrations/meta/_journal.json +++ b/backend/src/database/migrations/meta/_journal.json @@ -148,6 +148,13 @@ "when": 1755907709198, "tag": "0020_strange_colonel_america", "breakpoints": true + }, + { + "idx": 21, + "version": "7", + "when": 1756063134903, + "tag": "0021_colossal_madame_web", + "breakpoints": true } ] } \ No newline at end of file diff --git a/backend/src/routes/user.routes.ts b/backend/src/routes/user.routes.ts index 3095ab7..e0149c6 100644 --- a/backend/src/routes/user.routes.ts +++ b/backend/src/routes/user.routes.ts @@ -1,6 +1,5 @@ import express from 'express'; import * as userController from '../controllers/user.controller'; -import { authenticateUser } from '../middlewares/auth.middleware'; import { checkRole } from '../middlewares/user.middleware'; const userRouter = express.Router(); @@ -14,9 +13,9 @@ userRouter.post('/admin/syncnewstudent', checkRole("Admin",[]), userController.s // User routes -userRouter.patch('/user/me', authenticateUser, userController.updateProfile); -userRouter.get('/user/me', authenticateUser, userController.getCurrentUser); -userRouter.get('/user/getusers', checkRole("Student",[]), userController.getUsers); +userRouter.patch('/user/me', userController.updateProfile); +userRouter.get('/user/me', userController.getCurrentUser); +userRouter.get('/user/getusers', userController.getUsers); diff --git a/backend/src/services/permanence.service.ts b/backend/src/services/permanence.service.ts index 4f619e9..f98d1a1 100644 --- a/backend/src/services/permanence.service.ts +++ b/backend/src/services/permanence.service.ts @@ -167,7 +167,7 @@ export const createPermanence = async ( .returning({ id: permanenceSchema.id }); // Étape 2 : Ajout du responsable - if (newPermanence?.id) { + if (newPermanence?.id && respoId) { await db.insert(respoPermanenceSchema).values({ user_id: respoId, permanence_id: newPermanence.id, @@ -226,10 +226,12 @@ export const updatePermanence = async ( .where(eq(respoPermanenceSchema.permanence_id, permId)); // Étape 3 : Ajout du nouveau responsable - await db.insert(respoPermanenceSchema).values({ - user_id: respoId, - permanence_id: permId, - }); + if(respoId){ + await db.insert(respoPermanenceSchema).values({ + user_id: respoId, + permanence_id: permId, + }); + } }; @@ -312,7 +314,7 @@ export const getAllPermanences = async () => { perms.map(async (perm) => { const [respo] = await db .select({ - id: userSchema.id, + userId: userSchema.id, firstName: userSchema.first_name, lastName: userSchema.last_name, email: userSchema.email, diff --git a/backend/src/services/user.service.ts b/backend/src/services/user.service.ts index 1b07f22..741783f 100644 --- a/backend/src/services/user.service.ts +++ b/backend/src/services/user.service.ts @@ -7,6 +7,7 @@ import { getTeam, getTeamFaction, getUserTeam } from './team.service'; import { getFaction } from './faction.service'; import { registrationSchema } from '../schemas/Relational/registration.schema'; import { getUserRoles } from './role.service'; +import { permission } from 'process'; // Fonction pour récupérer un utilisateur par email export const getUserByEmail = async (email: string) => { @@ -123,6 +124,7 @@ export const getUsers = async () => { userId: userSchema.id, firstName: userSchema.first_name, lastName: userSchema.last_name, + permission : userSchema.permission } ).from(userSchema); return users; diff --git a/frontend/src/components/Admin/AdminPerm/adminPermForm.tsx b/frontend/src/components/Admin/AdminPerm/adminPermForm.tsx index 48381d6..ed5f442 100644 --- a/frontend/src/components/Admin/AdminPerm/adminPermForm.tsx +++ b/frontend/src/components/Admin/AdminPerm/adminPermForm.tsx @@ -15,6 +15,7 @@ import { getUsers } from "../../../services/requests/user.service"; import { Permanence } from "../../../interfaces/permanence.interface"; import { User } from "../../../interfaces/user.interface"; +import { formatDateForInput } from "../../utils/datetime_utils"; interface PermanenceFormProps { editMode: boolean; @@ -32,8 +33,8 @@ const PermanenceForm = ({ const [name, setName] = useState(""); const [desc, setDesc] = useState(""); const [location, setLocation] = useState(""); - const [startAt, setStartAt] = useState(""); - const [endAt, setEndAt] = useState(""); + const [startAt, setStartAt] = useState(Date); + const [endAt, setEndAt] = useState(Date); const [capacity, setCapacity] = useState(0); const [difficulty, setDifficulty] = useState(0); const [respo, setRespo] = useState(); @@ -57,8 +58,8 @@ const PermanenceForm = ({ setName(editPermanence.name); setDesc(editPermanence.description); setLocation(editPermanence.location); - setStartAt(editPermanence.start_at); - setEndAt(editPermanence.end_at); + setStartAt(formatDateForInput(editPermanence.start_at)); + setEndAt(formatDateForInput(editPermanence.end_at)); setCapacity(editPermanence.capacity); setDifficulty(editPermanence.difficulty); if (editPermanence.respo) { @@ -68,11 +69,13 @@ const PermanenceForm = ({ }, [editMode, editPermanence]); const handleSubmit = async () => { - if (!name || !desc || !location || !startAt || !endAt || !capacity || !difficulty || !respo) { + if (!name || !desc || !location || !startAt || !endAt || !capacity || !difficulty) { Swal.fire("Erreur", "Veuillez remplir tous les champs", "warning"); return; } + let respoId = respo && !isNaN(Number(respo.userId)) ? Number(respo.userId) : null; + try { const payload = { name, @@ -82,7 +85,7 @@ const PermanenceForm = ({ end_at: endAt, capacity, difficulty, - respoId: respo.userId, + respoId, }; if (editMode && editPermanence) { diff --git a/frontend/src/components/Admin/adminTeam.tsx b/frontend/src/components/Admin/adminTeam.tsx index 233cc30..6b838ff 100644 --- a/frontend/src/components/Admin/adminTeam.tsx +++ b/frontend/src/components/Admin/adminTeam.tsx @@ -153,7 +153,7 @@ export const AdminTeamManagement = () => {

🎯 Créer une équipe

-
+
{ const [users, setUsers] = useState([]); @@ -64,42 +65,57 @@ export const AdminUser = () => { setFormData((prev) => ({ ...prev, [e.target.name]: e.target.value })); }; - const handlePermissionChange = (option: any) => { - setFormData((prev) => ({ ...prev, permission: option?.value || null })); - }; - - const handleMajeurChange = (option: any) => { - setFormData((prev) => ({ ...prev, mejeur: option?.value || null })); - }; - - const handleBranchChange = (option: any) => { - setFormData((prev) => ({ ...prev, branch: option?.value || null })); + const handleSelectChange = (field: keyof User) => (option: any) => { + setFormData((prev) => ({ ...prev, [field]: option?.value ?? null })); }; const handleSave = async () => { if (!selectedUser) return; const response = await updateUserByAdmin(selectedUser.userId, formData); - alert(response.message); + + Swal.fire({ + icon: "success", + title: "Utilisateur mis à jour", + text: response.message, + confirmButtonColor: "#16a34a", + }); }; const handleDelete = async () => { if (!selectedUser) return; - const confirmDelete = confirm( - `Supprimer ${selectedUser.firstName} ${selectedUser.lastName} ?` - ); - if (confirmDelete) { + + const result = await Swal.fire({ + title: `Supprimer ${selectedUser.firstName} ${selectedUser.lastName} ?`, + text: "Cette action est irréversible !", + icon: "warning", + showCancelButton: true, + confirmButtonColor: "#d33", + cancelButtonColor: "#3085d6", + confirmButtonText: "Oui, supprimer", + cancelButtonText: "Annuler", + }); + + if (result.isConfirmed) { const response = await deleteUserByAdmin(selectedUser.userId); setUsers((prev) => prev.filter((u) => u.userId !== selectedUser.userId)); setSelectedUser(null); setFormData({}); - alert(response.message); + + Swal.fire({ + icon: "success", + title: "Supprimé", + text: response.message, + confirmButtonColor: "#16a34a", + }); } }; return ( - 👤 Gérer un utilisateur + + 👤 Gérer un utilisateur + @@ -132,22 +148,29 @@ export const AdminUser = () => { disabled placeholder="Email" /> -

Attention : la donnée récupérée est à partir de la date de synchro choisie

+ +

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

+ b.value === formData.branch)} - onChange={handleBranchChange} + value={ + branchOptions.find((b) => b.value === formData.branch) || null + } + onChange={handleSelectChange("branch")} options={branchOptions} placeholder="Choisir une filière" isClearable @@ -164,19 +187,27 @@ export const AdminUser = () => { placeholder="Permission" options={permissionOptions} value={ - formData.permission - ? permissionOptions.find((opt) => opt.value === formData.permission) - : null + permissionOptions.find( + (opt) => opt.value === formData.permission + ) || null } - onChange={handlePermissionChange} + onChange={handleSelectChange("permission")} isClearable />
- -
@@ -187,46 +218,50 @@ export const AdminUser = () => { ); }; - export const AdminSyncNewStudent = () => { const [loading, setLoading] = useState(false); - const [error, setError] = useState(null); - const [message, setMessage] = useState(""); const [selectedDate, setSelectedDate] = useState(""); const handleSync = async () => { setLoading(true); - setError(null); - setMessage(""); try { - - // Conversion directe de la date : '2025-09-01' → '2025.0901' let formattedDate = ""; if (selectedDate) { const [year, month, day] = selectedDate.split("-"); formattedDate = `${year}.${month}${day}`; } - // Appel au backend avec la date sélectionnée const response = await syncnewStudent(formattedDate); - setMessage(response.message); + + Swal.fire({ + icon: "success", + title: "Synchronisation réussie", + text: response.message, + confirmButtonColor: "#2563eb", + }); } catch (error) { - console.error("Erreur de connexion à Google", error); - setError("Erreur lors de la tentative de connexion."); + Swal.fire({ + icon: "error", + title: "Erreur", + text: "Erreur lors de la tentative de connexion.", + confirmButtonColor: "#d33", + }); } finally { setLoading(false); } }; return ( -
-

- Synchro API SIEP -

+ + + + 🔄 Synchro API SIEP + + -
-
- -
- -
- {error &&
{error}
} - {message &&
{message}
} -
+
+ +
+
+
); }; - diff --git a/frontend/src/components/navbar.tsx b/frontend/src/components/navbar.tsx index 63ab172..e73fcc1 100644 --- a/frontend/src/components/navbar.tsx +++ b/frontend/src/components/navbar.tsx @@ -40,7 +40,7 @@ export const Navbar = () => { children: [ { label: "Listes des permanences", to: "/PermanencesList", rolesAllowed: ["Admin", "Student"] }, { label: "Mes permanences", to: "/MyPermanences", rolesAllowed: ["Admin", "Student"] }, - { label: "Faire l'appelle", to: "/PermanencesAppeal", rolesAllowed: ["Admin", "Student"] }, + { label: "Faire l'appel", to: "/PermanencesAppeal", rolesAllowed: ["Admin", "Student"] }, ], }, { @@ -51,7 +51,7 @@ export const Navbar = () => { { label: "WEI", to: "/Wei" }, { label: "SDI", to: "/SDI" }, { label: "Repas", to: "/Food" }, - { label: "Defis Commissions", to: "/Games", rolesAllowed: ["Admin", "Student"] }, + { label: "Defis Commissions", to: "/Games", rolesAllowed: ["Admin", "Student"] }, ], }, { label: "Mon compte", to: "/Profil", icon: UsersIcon }, @@ -79,8 +79,20 @@ export const Navbar = () => { // helper d’autorisation - const isAllowed = (item: NavItem) => - !item.rolesAllowed || item.rolesAllowed.some(r => roles.includes(r)); + const isAllowed = (item: NavItem): boolean => { + // Si l'item a une restriction directe + if (item.rolesAllowed) { + return item.rolesAllowed.some(r => roles.includes(r)); + } + + // Si l'item a des enfants, on vérifie au moins un enfant + if (item.children && item.children.length > 0) { + return item.children.some(child => isAllowed(child)); + } + + // Sinon accessible par défaut + return true; +}; const handleLogout = () => { localStorage.removeItem("authToken"); @@ -194,6 +206,7 @@ const MenuItem = ({ ); }; +// Composant Dropdown (desktop & mobile) // Composant Dropdown (desktop & mobile) const Dropdown = ({ item, @@ -204,6 +217,15 @@ const Dropdown = ({ }) => { const [open, setOpen] = useState(false); const trigger = mobile ? "p-4" : "py-2 cursor-pointer"; + + // helper pour roles + const token = getToken(); + const { userPermission, userRoles = [] } = token ? decodeToken(token) : {}; + const roles = [userPermission, ...(userRoles?.map((r: any) => r.roleName) || [])]; + + const isAllowed = (child: NavItem) => + !child.rolesAllowed || child.rolesAllowed.some(r => roles.includes(r)); + return (
- {item.children!.map(child => ( -
  • - - {child.label} - -
  • - ))} + {item.children! + .filter(child => isAllowed(child)) // ✅ filtre selon les rôles + .map(child => ( +
  • + + {child.label} + +
  • + ))} )} diff --git a/frontend/src/components/tent/tentSection.tsx b/frontend/src/components/tent/tentSection.tsx index 7ffcd67..a1c7ec2 100644 --- a/frontend/src/components/tent/tentSection.tsx +++ b/frontend/src/components/tent/tentSection.tsx @@ -3,13 +3,14 @@ import { createTent, getUserTent, cancelTent } from "../../services/requests/ten import { getUsers } from "../../services/requests/user.service"; import { Button } from "../ui/button"; import Swal from "sweetalert2"; +import Select from "react-select"; import { User } from "../../interfaces/user.interface"; import { decodeToken, getToken } from "../../services/requests/auth.service"; import { Tent } from "../../interfaces/tent.interface"; import { checkWEIStatus } from "../../services/requests/event.service"; export const TentPublic = () => { - const [userId2, setUserId2] = useState(); + const [userId2, setUserId2] = useState(null); const [tentInfo, setTentInfo] = useState(null); const [users, setUsers] = useState([]); const [isWEIOpen, setIsWEIOpen] = useState(false); @@ -92,7 +93,7 @@ export const TentPublic = () => { try { await cancelTent(); setTentInfo(null); - setUserId2(undefined); + setUserId2(null); Swal.fire("🛑 Annulée", "Ta tente a bien été annulée", "success"); } catch { Swal.fire("Erreur", "Impossible d'annuler la tente", "error"); @@ -119,20 +120,28 @@ export const TentPublic = () => { <>
    - user.userId !== userId) - .map((user: User) => ( - - ))} - + .map((user: User) => ({ + value: user.userId, + label: `${user.firstName} ${user.lastName}`, + }))} + value={ + userId2 + ? { + value: userId2, + label: `${users.find((u) => u.userId === userId2)?.firstName || ""} ${ + users.find((u) => u.userId === userId2)?.lastName || "" + }`, + } + : null + } + onChange={(option) => setUserId2(option?.value || null)} + isClearable + className="shadow-sm" + />
    @@ -149,7 +158,7 @@ export const TentPublic = () => {

    🎫 Ta tente

    - Binôme avec{" "} + Binôme avec {" "} { users.find( @@ -157,7 +166,7 @@ export const TentPublic = () => { user.userId === (tentInfo.user_id_1 === userId ? tentInfo.user_id_2 : tentInfo.user_id_1) )?.firstName - }{" "} + } {" "} { users.find( (user) => @@ -168,15 +177,12 @@ export const TentPublic = () => {

    - {/* ✅ Ajout de l'état de confirmation */}
    {tentInfo.confirmed ? ( -

    - ✅ Ta tente est confirmée ! -

    +

    ✅ Ta tente est confirmée !

    ) : (

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

    )}
    @@ -202,4 +208,4 @@ export const TentPublic = () => {

    ); -}; +}; \ No newline at end of file diff --git a/frontend/src/components/utils/datetime_utils.ts b/frontend/src/components/utils/datetime_utils.ts index 7b13dcc..c266602 100644 --- a/frontend/src/components/utils/datetime_utils.ts +++ b/frontend/src/components/utils/datetime_utils.ts @@ -1,11 +1,24 @@ -// Fonction pour formater les dates pour l'input datetime-local -export const formatDateForInput = (date: string) => { - const localDate = new Date(date); - return localDate.toISOString().slice(0, 16); // Extrait la partie yyyy-MM-ddThh:mm - }; +// Pour l'input datetime-local +export const formatDateForInput = (date?: string | null) => { + if (!date) return ""; + const localDate = new Date(date); + if (isNaN(localDate.getTime())) return ""; // date invalide + const offsetDate = new Date(localDate.getTime() - localDate.getTimezoneOffset() * 60000); + return offsetDate.toISOString().slice(0, 16); +}; -// Fonction pour afficher les dates en format local dans la liste des permanences -export const formatDateForDisplay = (date: string) => { - const localDate = new Date(date); - return `${localDate.toISOString().slice(0, 10)} Heure : ${localDate.toISOString().slice(11, 16)}`; -}; \ No newline at end of file +// Pour affichage lisible en français +export const formatDateForDisplay = (date?: string | null) => { + if (!date) return ""; + const localDate = new Date(date); + if (isNaN(localDate.getTime())) return ""; + return localDate.toLocaleString("fr-FR", { + timeZone: "Europe/Paris", // évite les surprises + weekday: "long", + year: "numeric", + month: "long", + day: "numeric", + hour: "2-digit", + minute: "2-digit", + }); +}; diff --git a/frontend/src/pages/Wei.tsx b/frontend/src/pages/Wei.tsx index 53f1640..88377e5 100644 --- a/frontend/src/pages/Wei.tsx +++ b/frontend/src/pages/Wei.tsx @@ -20,7 +20,7 @@ export const WeiPage = () => {
    - {(permission === "Nouveau" || permission === "Admin") && ()} + {(permission === "Nouveau" || permission === "Admin") && }