From 671f17a2888a123eb0494b786be5b31761af4af7 Mon Sep 17 00:00:00 2001 From: Antoine Date: Mon, 6 Jul 2026 14:48:04 +0200 Subject: [PATCH 1/7] feat(front): contact informations in admin user page --- frontend/src/components/Admin/adminUser.tsx | 229 ++++++++++-------- frontend/src/interfaces/user.interface.ts | 6 + .../src/services/requests/user.service.ts | 8 +- 3 files changed, 143 insertions(+), 100 deletions(-) diff --git a/frontend/src/components/Admin/adminUser.tsx b/frontend/src/components/Admin/adminUser.tsx index e1a7a74..79a1fa8 100644 --- a/frontend/src/components/Admin/adminUser.tsx +++ b/frontend/src/components/Admin/adminUser.tsx @@ -2,10 +2,11 @@ import { useEffect, useState } from 'react'; import Select 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 { deleteUserByAdmin, + getUserContactInformation, getUsersAdmin, syncnewStudent, updateUserByAdmin, @@ -46,6 +47,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 () => { @@ -55,11 +57,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)) }); } }; @@ -165,104 +168,132 @@ export const AdminUser = () => { }; return ( - - - - 👤 Gérer un utilisateur - - - - - - - - -

- 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 - /> - - - - ({ + 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 + /> + + + + + -
- )} -
-
+ + + + )} + ); }; diff --git a/frontend/src/interfaces/user.interface.ts b/frontend/src/interfaces/user.interface.ts index 680dd49..ea26891 100644 --- a/frontend/src/interfaces/user.interface.ts +++ b/frontend/src/interfaces/user.interface.ts @@ -9,3 +9,9 @@ export interface User { contact: string; discord_id: string; } + +export interface UserContactInformation { + userId: number; + urgency_contact_name: string; + urgency_contact_phone: number; +} \ No newline at end of file diff --git a/frontend/src/services/requests/user.service.ts b/frontend/src/services/requests/user.service.ts index 80ac24c..3198def 100644 --- a/frontend/src/services/requests/user.service.ts +++ b/frontend/src/services/requests/user.service.ts @@ -1,4 +1,4 @@ -import { type User } from '../../interfaces/user.interface'; +import { type User, type UserContactInformation } from '../../interfaces/user.interface'; import api from '../api'; export const getPermission = (): string | null => { @@ -46,6 +46,12 @@ export const getCurrentUser = async () => { return res.data.data; }; +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 updateCurrentUser = async (data: Partial) => { const response = await api.patch("/user/user/me", data); return response.data From 7ad8db8e50f45de505ab5f194c2fe3f426f8e87a Mon Sep 17 00:00:00 2001 From: Antoine Date: Mon, 6 Jul 2026 15:12:24 +0200 Subject: [PATCH 2/7] feat(back): add endpoint and service for retrieving user contact information --- backend/src/controllers/user.controller.ts | 11 +++++++++++ backend/src/routes/user.routes.ts | 1 + .../schemas/Relational/userinformation.schema.ts | 10 ++++++++++ backend/src/services/user.service.ts | 16 ++++++++++++++++ 4 files changed, 38 insertions(+) create mode 100644 backend/src/schemas/Relational/userinformation.schema.ts diff --git a/backend/src/controllers/user.controller.ts b/backend/src/controllers/user.controller.ts index d7008dd..d85e247 100644 --- a/backend/src/controllers/user.controller.ts +++ b/backend/src/controllers/user.controller.ts @@ -87,6 +87,17 @@ export const getCurrentUser = async (req: Request, res: Response) => { } }; +export const getUserContactInformation = async (req: Request, res: Response) => { + 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 updateProfile = async (req: Request, res: Response) => { const userId = req.user?.userId; const { branch, contact } = req.body; diff --git a/backend/src/routes/user.routes.ts b/backend/src/routes/user.routes.ts index ae2f9d2..c83d3fa 100644 --- a/backend/src/routes/user.routes.ts +++ b/backend/src/routes/user.routes.ts @@ -8,6 +8,7 @@ const userRouter = express.Router(); userRouter.get('/admin/getusersbypermission', checkRole("Admin", []), userController.getUsersByPermission); 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.get('/admin/getusers', checkRole("Admin", ["Respo CE"]), userController.getUsersAdmin); userRouter.post('/admin/syncnewstudent', checkRole("Admin", []), userController.syncNewstudent); diff --git a/backend/src/schemas/Relational/userinformation.schema.ts b/backend/src/schemas/Relational/userinformation.schema.ts new file mode 100644 index 0000000..056bc55 --- /dev/null +++ b/backend/src/schemas/Relational/userinformation.schema.ts @@ -0,0 +1,10 @@ +import { pgTable, integer, text } from "drizzle-orm/pg-core"; +import { userSchema } from "../Basic/user.schema"; + +export const userinformationSchema = pgTable("userinformations", { + user_id: integer("user_id").primaryKey().references(() => userSchema.id, { onDelete: "cascade" }), + urgency_contact_name: text("urgency_contact_name"), + urgency_contact_phone: integer("urgency_contact_phone"), +}); + +export type UserInformation = typeof userinformationSchema.$inferSelect; \ No newline at end of file diff --git a/backend/src/services/user.service.ts b/backend/src/services/user.service.ts index c457be9..1729606 100644 --- a/backend/src/services/user.service.ts +++ b/backend/src/services/user.service.ts @@ -133,6 +133,22 @@ export const getUsers = async () => { } }; +export const getUserContactInformation = async (userId: number) => { + try { + const user = await db.select( + { + userId: userSchema.id, + urgency_contact_name: userSchema.contact, + urgency_contact_phone: userSchema.contact + } + ).from(userSchema).where(eq(userSchema.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 getUsersAll = async () => { try { const users = await db.select().from(userSchema); From 3e152d3eb763a33d7d9ea09284fb250f5b87f528 Mon Sep 17 00:00:00 2001 From: Antoine Date: Mon, 6 Jul 2026 15:17:23 +0200 Subject: [PATCH 3/7] fix(back): wrong table --- backend/src/schemas/Relational/userinformation.schema.ts | 4 ++-- backend/src/services/user.service.ts | 9 +++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/backend/src/schemas/Relational/userinformation.schema.ts b/backend/src/schemas/Relational/userinformation.schema.ts index 056bc55..084db9a 100644 --- a/backend/src/schemas/Relational/userinformation.schema.ts +++ b/backend/src/schemas/Relational/userinformation.schema.ts @@ -1,10 +1,10 @@ import { pgTable, integer, text } from "drizzle-orm/pg-core"; import { userSchema } from "../Basic/user.schema"; -export const userinformationSchema = pgTable("userinformations", { +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: integer("urgency_contact_phone"), }); -export type UserInformation = typeof userinformationSchema.$inferSelect; \ No newline at end of file +export type UserInformation = typeof userInformationSchema.$inferSelect; \ No newline at end of file diff --git a/backend/src/services/user.service.ts b/backend/src/services/user.service.ts index 1729606..bed7eb5 100644 --- a/backend/src/services/user.service.ts +++ b/backend/src/services/user.service.ts @@ -6,6 +6,7 @@ import { registrationSchema } from '../schemas/Relational/registration.schema'; import { getFaction } from './faction.service'; import { getUserRoles } from './role.service'; import { getTeam, getTeamFaction, getUserTeam } from './team.service'; +import { userInformationSchema } from '../schemas/Relational/userinformation.schema'; // Fonction pour récupérer un utilisateur par email export const getUserByEmail = async (email: string) => { @@ -137,11 +138,11 @@ export const getUserContactInformation = async (userId: number) => { try { const user = await db.select( { - userId: userSchema.id, - urgency_contact_name: userSchema.contact, - urgency_contact_phone: userSchema.contact + userId: userInformationSchema.user_id, + urgency_contact_name: userInformationSchema.urgency_contact_name, + urgency_contact_phone: userInformationSchema.urgency_contact_phone } - ).from(userSchema).where(eq(userSchema.id, userId)); + ).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); From 4571f0be8426f1b8203e24bfcf7403a15f91e7c1 Mon Sep 17 00:00:00 2001 From: Antoine Date: Mon, 6 Jul 2026 15:46:07 +0200 Subject: [PATCH 4/7] feat(front): Add header when form isn't complete --- frontend/src/components/navbar.tsx | 138 ++++++++++-------- .../src/services/requests/user.service.ts | 10 +- 2 files changed, 88 insertions(+), 60 deletions(-) diff --git a/frontend/src/components/navbar.tsx b/frontend/src/components/navbar.tsx index f0975ba..fe5da5a 100644 --- a/frontend/src/components/navbar.tsx +++ b/frontend/src/components/navbar.tsx @@ -5,6 +5,7 @@ import { Fragment, useEffect, useState } from "react"; import { NavLink, useLocation } from "react-router-dom"; import { decodeToken, getToken } from "../services/requests/auth.service"; +import { getCurrentUserContactInformation } from "../services/requests/user.service"; interface NavItem { label: string; @@ -25,6 +26,7 @@ export const Navbar = () => { const isAuthenticated = Boolean(token); const { userPermission, userRoles = [] } = token ? decodeToken(token) : { userPermission: undefined, userRoles: [] }; const roles = [userPermission, ...userRoles.map(r => r.roleName)].filter(Boolean) as string[]; + const [hasContactInformation, setHasContactInformation] = useState(false); const handleLogout = () => { localStorage.removeItem("authToken"); @@ -33,6 +35,18 @@ export const Navbar = () => { useEffect(() => { setMenuOpen(false); + const fetchContactInformation = async () => { + try { + const contactInfo = await getCurrentUserContactInformation(); + setHasContactInformation(contactInfo.urgency_contact_phone !== null); + } catch (error) { + console.error("Erreur lors de la récupération des informations de contact :", error); + } + }; + + if (isAuthenticated) { + fetchContactInformation(); + } }, [pathname]); const navItems: NavItem[] = [ @@ -126,72 +140,80 @@ export const Navbar = () => { }; return ( - + + {/* Contact Information Warning */ hasContactInformation === false && isAuthenticated && ( +
+

ATTENTION : Tu n'as pas complété le formulaire VSS ainsi que tes informations d'urgence. Merci de le faire au plus vite !

+
+ )} + ); }; diff --git a/frontend/src/services/requests/user.service.ts b/frontend/src/services/requests/user.service.ts index 3198def..1134c17 100644 --- a/frontend/src/services/requests/user.service.ts +++ b/frontend/src/services/requests/user.service.ts @@ -41,13 +41,19 @@ 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 getCurrentUser = async () => { const res = await api.get("/user/user/me"); return res.data.data; }; -export const getUserContactInformation = async (userId: number) => { - const response = await api.get(`/user/admin/getusercontactinformation/${userId}`); +export const getCurrentUserContactInformation = async () => { + const response = await api.get(`/user/user/getusercontactinformation`); const users: UserContactInformation = response.data.data; return users; }; From 4e53913af818988a2dc5f9fc5b7db8cf96e6ecc7 Mon Sep 17 00:00:00 2001 From: Antoine Date: Sat, 11 Jul 2026 19:16:44 +0200 Subject: [PATCH 5/7] feat(front): add urgency modal (empty) and header button --- .../Relational/userinformation.schema.ts | 1 + backend/src/services/user.service.ts | 3 +- frontend/src/components/auth/authForm.tsx | 2 +- frontend/src/components/home/urgencyModal.tsx | 20 ++ frontend/src/components/navbar.tsx | 241 ++++++++---------- frontend/src/components/ui/modal.tsx | 129 ++++++++++ frontend/src/pages/home.tsx | 11 +- 7 files changed, 268 insertions(+), 139 deletions(-) create mode 100644 frontend/src/components/home/urgencyModal.tsx create mode 100644 frontend/src/components/ui/modal.tsx diff --git a/backend/src/schemas/Relational/userinformation.schema.ts b/backend/src/schemas/Relational/userinformation.schema.ts index 084db9a..bfdceea 100644 --- a/backend/src/schemas/Relational/userinformation.schema.ts +++ b/backend/src/schemas/Relational/userinformation.schema.ts @@ -5,6 +5,7 @@ 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: integer("urgency_contact_phone"), + contact_CE: text("contact_CE"), }); export type UserInformation = typeof userInformationSchema.$inferSelect; \ No newline at end of file diff --git a/backend/src/services/user.service.ts b/backend/src/services/user.service.ts index bed7eb5..5ba7bcb 100644 --- a/backend/src/services/user.service.ts +++ b/backend/src/services/user.service.ts @@ -140,7 +140,8 @@ export const getUserContactInformation = async (userId: number) => { { userId: userInformationSchema.user_id, urgency_contact_name: userInformationSchema.urgency_contact_name, - urgency_contact_phone: userInformationSchema.urgency_contact_phone + urgency_contact_phone: userInformationSchema.urgency_contact_phone, + contact_CE: userInformationSchema.contact_CE } ).from(userInformationSchema).where(eq(userInformationSchema.user_id, userId)); return user[0]; 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..343fe2a --- /dev/null +++ b/frontend/src/components/home/urgencyModal.tsx @@ -0,0 +1,20 @@ +import { useSearchParams } from 'react-router-dom'; + +import Modal from '../ui/modal'; + +function UrgencyModal() { + const [searchParams, setSearchParams] = useSearchParams(); + + const isLogin = searchParams.get('login') === 'true'; + + return ( + setSearchParams({})} + buttons={null} + /> + ); +} + +export default UrgencyModal; diff --git a/frontend/src/components/navbar.tsx b/frontend/src/components/navbar.tsx index fe5da5a..12a6e1c 100644 --- a/frontend/src/components/navbar.tsx +++ b/frontend/src/components/navbar.tsx @@ -1,11 +1,12 @@ -import { Bars4Icon, CogIcon, HomeIcon, UsersIcon } from "@heroicons/react/24/outline"; -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 { Bars4Icon, CogIcon, HomeIcon, UsersIcon } from '@heroicons/react/24/outline'; +import { XMarkIcon } from '@heroicons/react/24/solid'; +import { AnimatePresence, motion } from 'framer-motion'; +import { Fragment, useEffect, useState } from 'react'; +import { NavLink, useLocation, useSearchParams } from 'react-router-dom'; -import { decodeToken, getToken } from "../services/requests/auth.service"; -import { getCurrentUserContactInformation } from "../services/requests/user.service"; +import { decodeToken, getToken } from '../services/requests/auth.service'; +import { getCurrentUserContactInformation } from '../services/requests/user.service'; +import { Button } from './ui/button'; interface NavItem { label: string; @@ -13,10 +14,10 @@ interface NavItem { icon?: React.ComponentType>; rolesAllowed?: string[]; // ["Admin", "Respo CE", ...] public?: boolean; - showWhen?: "always" | "auth" | "guest"; - kind?: "link" | "action"; + showWhen?: 'always' | 'auth' | 'guest'; + kind?: 'link' | 'action'; onClick?: () => void; - children?: NavItem[]; // pour dropdown + children?: NavItem[]; // pour dropdown } export const Navbar = () => { @@ -24,13 +25,16 @@ export const Navbar = () => { const token = getToken(); const [menuOpen, setMenuOpen] = useState(false); const isAuthenticated = Boolean(token); - const { userPermission, userRoles = [] } = token ? decodeToken(token) : { userPermission: undefined, userRoles: [] }; - const roles = [userPermission, ...userRoles.map(r => r.roleName)].filter(Boolean) as string[]; + const { userPermission, userRoles = [] } = token + ? decodeToken(token) + : { userPermission: undefined, userRoles: [] }; + const roles = [userPermission, ...userRoles.map((r) => r.roleName)].filter(Boolean) as string[]; const [hasContactInformation, setHasContactInformation] = useState(false); + const [, setSearchParams] = useSearchParams(); const handleLogout = () => { - localStorage.removeItem("authToken"); - window.location.href = "/"; + localStorage.removeItem('authToken'); + window.location.href = '/'; }; useEffect(() => { @@ -40,7 +44,7 @@ export const Navbar = () => { const contactInfo = await getCurrentUserContactInformation(); setHasContactInformation(contactInfo.urgency_contact_phone !== null); } catch (error) { - console.error("Erreur lors de la récupération des informations de contact :", error); + console.error('Erreur lors de la récupération des informations de contact :', error); } }; @@ -50,90 +54,88 @@ export const Navbar = () => { }, [pathname]); const navItems: NavItem[] = [ - { label: "Home", to: "/home", icon: HomeIcon }, - { label: "Plannings", to: "/plannings" }, - { label: "Parrainage", to: "/parrainage" }, - { label: "Challenges", to: "/challenges" }, - { label: "Mes Actus", to: "/news" }, + { label: 'Home', to: '/home', icon: HomeIcon }, + { label: 'Plannings', to: '/plannings' }, + { label: 'Parrainage', to: '/parrainage' }, + { label: 'Challenges', to: '/challenges' }, + { label: 'Mes Actus', to: '/news' }, { - label: "Permanences", - to: "#", + label: 'Permanences', + to: '#', children: [ - { label: "Listes des permanences", to: "/permanenceslist", rolesAllowed: ["Admin", "Student"] }, - { label: "Mes permanences", to: "/mypermanences", rolesAllowed: ["Admin", "Student"] }, - { label: "Faire l'appel", to: "/permanencesappeal", rolesAllowed: ["Admin", "Student"] }, + { label: 'Listes des permanences', to: '/permanenceslist', rolesAllowed: ['Admin', 'Student'] }, + { label: 'Mes permanences', to: '/mypermanences', rolesAllowed: ['Admin', 'Student'] }, + { label: "Faire l'appel", to: '/permanencesappeal', rolesAllowed: ['Admin', 'Student'] }, ], }, { - label: "Events", - to: "#", + label: 'Events', + to: '#', children: [ - { label: "Shotgun", to: "/shotgun", rolesAllowed: ["Admin", "Student"] }, - { label: "WEI", to: "/wei" }, - { label: "SDI", to: "/sdi" }, - { label: "Repas", to: "/food" }, - { label: "Defis Commissions", to: "/games", rolesAllowed: ["Admin", "Student"] }, + { label: 'Shotgun', to: '/shotgun', rolesAllowed: ['Admin', 'Student'] }, + { label: 'WEI', to: '/wei' }, + { label: 'SDI', to: '/sdi' }, + { label: 'Repas', to: '/food' }, + { label: 'Defis Commissions', to: '/games', rolesAllowed: ['Admin', 'Student'] }, ], }, - { label: "Mon compte", to: "/profil", icon: UsersIcon }, + { label: 'Mon compte', to: '/profil', icon: UsersIcon }, { - label: "Admin", - to: "#", + label: 'Admin', + to: '#', icon: CogIcon, children: [ - { label: "Bus", to: "/admin/bus", rolesAllowed: ["Admin"] }, - { label: "Challenge", to: "/admin/challenge", rolesAllowed: ["Admin", "Arbitre"] }, - { label: "Email", to: "/admin/email", rolesAllowed: ["Admin"] }, - { label: "Events", to: "/admin/events", rolesAllowed: ["Admin"] }, - { label: "Export / Import", to: "/admin/export-import", rolesAllowed: ["Admin"] }, - { label: "Factions", to: "/admin/factions", rolesAllowed: ["Admin", "Respo CE"] }, - { label: "Games", to: "/admin/games", rolesAllowed: ["Admin"] }, - { label: "News", to: "/admin/news", rolesAllowed: ["Admin", "Communication"] }, - { label: "Permanences", to: "/admin/permanences", rolesAllowed: ["Admin", "Respo CE"] }, - { label: "Roles", to: "/admin/roles", rolesAllowed: ["Admin"] }, - { label: "Shotgun", to: "/admin/shotgun", rolesAllowed: ["Admin", "Respo CE"] }, - { label: "Teams", to: "/admin/teams", rolesAllowed: ["Admin", "Respo CE"] }, - { label: "Tentes", to: "/admin/tent", rolesAllowed: ["Admin"] }, - { label: "Users", to: "/admin/users", rolesAllowed: ["Admin"] }, + { label: 'Bus', to: '/admin/bus', rolesAllowed: ['Admin'] }, + { label: 'Challenge', to: '/admin/challenge', rolesAllowed: ['Admin', 'Arbitre'] }, + { label: 'Email', to: '/admin/email', rolesAllowed: ['Admin'] }, + { label: 'Events', to: '/admin/events', rolesAllowed: ['Admin'] }, + { label: 'Export / Import', to: '/admin/export-import', rolesAllowed: ['Admin'] }, + { label: 'Factions', to: '/admin/factions', rolesAllowed: ['Admin', 'Respo CE'] }, + { label: 'Games', to: '/admin/games', rolesAllowed: ['Admin'] }, + { label: 'News', to: '/admin/news', rolesAllowed: ['Admin', 'Communication'] }, + { label: 'Permanences', to: '/admin/permanences', rolesAllowed: ['Admin', 'Respo CE'] }, + { label: 'Roles', to: '/admin/roles', rolesAllowed: ['Admin'] }, + { label: 'Shotgun', to: '/admin/shotgun', rolesAllowed: ['Admin', 'Respo CE'] }, + { label: 'Teams', to: '/admin/teams', rolesAllowed: ['Admin', 'Respo CE'] }, + { label: 'Tentes', to: '/admin/tent', rolesAllowed: ['Admin'] }, + { label: 'Users', to: '/admin/users', rolesAllowed: ['Admin'] }, ], }, { - label: "Déconnexion", - to: "/", - kind: "action", - showWhen: "auth", + label: 'Déconnexion', + to: '/', + kind: 'action', + showWhen: 'auth', onClick: handleLogout, }, { - label: "Se connecter", - to: "/", + label: 'Se connecter', + to: '/', public: true, - showWhen: "guest", + showWhen: 'guest', }, - ]; - const canShowItem = (item: NavItem): boolean => { - if (item.showWhen === "auth") return isAuthenticated; - if (item.showWhen === "guest") return !isAuthenticated; + if (item.showWhen === 'auth') return isAuthenticated; + if (item.showWhen === 'guest') return !isAuthenticated; if (!isAuthenticated) { if (item.public) return true; if (item.children && item.children.length > 0) { - return item.children.some(child => canShowItem(child)); + return item.children.some((child) => canShowItem(child)); } return false; } if (item.rolesAllowed) { - return item.rolesAllowed.some(r => roles.includes(r)); + return item.rolesAllowed.some((r) => roles.includes(r)); } if (item.children && item.children.length > 0) { - return item.children.some(child => canShowItem(child)); + return item.children.some((child) => canShowItem(child)); } return true; @@ -152,29 +154,24 @@ export const Navbar = () => { {/* Menu desktop */}
    - {navItems.map(item => + {navItems.map((item) => canShowItem(item) ? (
  • {item.children ? ( - ) : item.kind === "action" ? ( + ) : item.kind === 'action' ? ( ) : ( )}
  • - ) : null + ) : null, )}
@@ -184,15 +181,14 @@ export const Navbar = () => { {menuOpen && ( - {navItems.map(item => + className="lg:hidden bg-blue-700 overflow-hidden"> + {navItems.map((item) => canShowItem(item) ? ( {!item.children ? ( - item.kind === "action" ? ( + item.kind === 'action' ? ( ) : ( @@ -201,67 +197,50 @@ export const Navbar = () => { )} - ) : null + ) : null, )} )} - {/* Contact Information Warning */ hasContactInformation === false && isAuthenticated && ( -
-

ATTENTION : Tu n'as pas complété le formulaire VSS ainsi que tes informations d'urgence. Merci de le faire au plus vite !

-
- )} + { + /* Contact Information Warning */ hasContactInformation === false && isAuthenticated && ( +
+

+ ATTENTION : Tu n'as pas complété le formulaire VSS ainsi que tes informations d'urgence. + Merci de le faire au plus vite ! +

+ +
+ ) + } ); }; -const NavActionItem = ({ - item, - mobile = false, -}: { - item: NavItem; - mobile?: boolean; -}) => { - const base = mobile ? "block py-2 px-4 text-left w-full" : "inline-flex items-center py-2"; +const NavActionItem = ({ item, mobile = false }: { item: NavItem; mobile?: boolean }) => { + const base = mobile ? 'block py-2 px-4 text-left w-full' : 'inline-flex items-center py-2'; return ( - ); }; // Composant MenuItem -const MenuItem = ({ - item, - active = false, - mobile = false, -}: { - item: NavItem; - active?: boolean; - mobile?: boolean; -}) => { - const base = mobile ? "block py-2 px-4" : "inline-flex items-center py-2"; - const activeClass = active - ? "text-yellow-300 font-semibold border-b-2 border-yellow-300" - : "hover:text-yellow-200"; +const MenuItem = ({ item, active = false, mobile = false }: { item: NavItem; active?: boolean; mobile?: boolean }) => { + const base = mobile ? 'block py-2 px-4' : 'inline-flex items-center py-2'; + const activeClass = active ? 'text-yellow-300 font-semibold border-b-2 border-yellow-300' : 'hover:text-yellow-200'; return ( - - {item.icon && ( - - )} + + {item.icon && } {item.label} ); @@ -278,7 +257,7 @@ const Dropdown = ({ canShowItem: (item: NavItem) => boolean; }) => { const [open, setOpen] = useState(false); - const trigger = mobile ? "p-4" : "py-2 cursor-pointer"; + const trigger = mobile ? 'p-4' : 'py-2 cursor-pointer'; return (
@@ -288,8 +267,7 @@ const Dropdown = ({ className={`${trigger} flex items-center justify-between`} aria-expanded={open} aria-controls={`submenu-${item.label}`} - aria-haspopup="menu" - > + aria-haspopup="menu"> {item.icon && } {item.label} â–ľ @@ -300,19 +278,18 @@ const Dropdown = ({ initial={{ opacity: 0, y: -10 }} animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0, y: -10 }} - className={`absolute bg-white text-black rounded shadow-md z-50 ${mobile ? "static mt-2" : "top-full left-0 mt-1" - }`} - role="menu" - > - {item.children! - .filter(child => canShowItem(child)) - .map(child => ( + className={`absolute bg-white text-black rounded shadow-md z-50 ${ + mobile ? 'static mt-2' : 'top-full left-0 mt-1' + }`} + role="menu"> + {item + .children!.filter((child) => canShowItem(child)) + .map((child) => (
  • + role="menuitem"> {child.label}
  • 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/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 = () => (
    + -
    ); From 5c34228cfb48ec55e14cdc3a4695eab2be1b7843 Mon Sep 17 00:00:00 2001 From: Antoine Date: Sun, 12 Jul 2026 11:54:40 +0200 Subject: [PATCH 6/7] feat(back): add user contact information creation endpoints --- backend/src/controllers/user.controller.ts | 15 ++- backend/src/routes/user.routes.ts | 17 ++- backend/src/services/user.service.ts | 142 +++++++++++---------- backend/types/express.d.ts | 4 +- backend/types/user.d.ts | 5 + 5 files changed, 110 insertions(+), 73 deletions(-) create mode 100644 backend/types/user.d.ts diff --git a/backend/src/controllers/user.controller.ts b/backend/src/controllers/user.controller.ts index d85e247..ff104cf 100644 --- a/backend/src/controllers/user.controller.ts +++ b/backend/src/controllers/user.controller.ts @@ -6,6 +6,7 @@ import * as user_service from '../services/user.service'; import { noSyncEmails } from '../utils/no_sync_list'; import { Error, Ok } from '../utils/responses'; import * as SIEP_Utils from '../utils/siep'; +import { type UserContactInformation } from '../../types/user'; export const getUsersAdmin = async (req: Request, res: Response) => { try { @@ -94,7 +95,19 @@ export const getUserContactInformation = async (req: Request, res: Response) => 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.' }); + Error(res, { msg: "Erreur lors de la récupération des informations de contact de l'utilisateur." }); + } +}; + +export const createUserContactInformation = async (req: Request, res: Response) => { + const userId = req.user?.userId; + const { contact }: { contact: UserContactInformation } = req.body; + + try { + const result = await user_service.createUserContactInformation(parseInt(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.' }); } }; diff --git a/backend/src/routes/user.routes.ts b/backend/src/routes/user.routes.ts index c83d3fa..05f0bd0 100644 --- a/backend/src/routes/user.routes.ts +++ b/backend/src/routes/user.routes.ts @@ -5,16 +5,21 @@ import { checkRole } from '../middlewares/user.middleware'; const userRouter = express.Router(); // Admin routes -userRouter.get('/admin/getusersbypermission', checkRole("Admin", []), userController.getUsersByPermission); -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.get('/admin/getusers', checkRole("Admin", ["Respo CE"]), userController.getUsersAdmin); -userRouter.post('/admin/syncnewstudent', checkRole("Admin", []), userController.syncNewstudent); +userRouter.get('/admin/getusersbypermission', checkRole('Admin', []), userController.getUsersByPermission); +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.get('/admin/getusers', checkRole('Admin', ['Respo CE']), userController.getUsersAdmin); +userRouter.post('/admin/syncnewstudent', checkRole('Admin', []), userController.syncNewstudent); // User routes userRouter.patch('/user/me', userController.updateProfile); userRouter.get('/user/me', userController.getCurrentUser); userRouter.get('/user/getusers', userController.getUsers); +userRouter.post('/user/usercontactinformation', userController.createUserContactInformation); export default userRouter; diff --git a/backend/src/services/user.service.ts b/backend/src/services/user.service.ts index 5ba7bcb..2a80a9b 100644 --- a/backend/src/services/user.service.ts +++ b/backend/src/services/user.service.ts @@ -7,6 +7,7 @@ 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'; // Fonction pour récupérer un utilisateur par email export const getUserByEmail = async (email: string) => { @@ -14,15 +15,15 @@ export const getUserByEmail = async (email: string) => { const users = await db.select().from(userSchema).where(eq(userSchema.email, email)); return users[0]; } catch (err) { - console.error('Erreur lors de la récupération de l\'utilisateur par email:', err); + console.error("Erreur lors de la récupération de l'utilisateur par email:", err); throw new Error('Erreur de base de données'); } }; export const getUserById = async (userId: number) => { try { - const user = await db.select( - { + const user = await db + .select({ userId: userSchema.id, firstName: userSchema.first_name, lastName: userSchema.last_name, @@ -31,12 +32,13 @@ export const getUserById = async (userId: number) => { branch: userSchema.branch, contact: userSchema.contact, permission: userSchema.permission, - discord_id: userSchema.discord_id - } - ).from(userSchema).where(eq(userSchema.id, userId)); + discord_id: userSchema.discord_id, + }) + .from(userSchema) + .where(eq(userSchema.id, userId)); return user[0]; } catch (err) { - console.error('Erreur lors de la récupération de l\'utilisateur par email:', err); + console.error("Erreur lors de la récupération de l'utilisateur par email:", err); throw new Error('Erreur de base de données'); } }; @@ -49,7 +51,8 @@ export const createUser = async ( majeur: boolean, permission: string, branch: string, - password: string) => { + password: string, +) => { try { // Hacher le mot de passe const hashedPassword = await bcrypt.hash(password, 10); @@ -58,17 +61,17 @@ export const createUser = async ( first_name: firstName, last_name: lastName, email: email, - branch: branch === "CV_ING" ? "RI" : branch, + branch: branch === 'CV_ING' ? 'RI' : branch, majeur: majeur, password: hashedPassword, - permission: permission + permission: permission, }; // Insérer un nouvel utilisateur dans la base de données - const result = await db.insert(userSchema).values(newUser).returning() + const result = await db.insert(userSchema).values(newUser).returning(); return result[0]; } catch (err) { - console.error('Erreur lors de la création de l\'utilisateur:', err); + console.error("Erreur lors de la création de l'utilisateur:", err); throw new Error('Erreur de base de données'); } }; @@ -80,24 +83,25 @@ export const comparePassword = async (enteredPassword: string, storedPassword: s export const updateUserStudent = async (firstName: string, lastName: string, email: string) => { try { - const result = await db.update(userSchema) + const result = await db + .update(userSchema) .set({ first_name: firstName, - last_name: lastName + last_name: lastName, }) .where(eq(userSchema.email, email)); return result.rows[0]; } catch (err) { - console.error('Erreur lors de la récupération et de l\'update de l\'utilisateur par email:', err); + console.error("Erreur lors de la récupération et de l'update de l'utilisateur par email:", err); throw new Error('Erreur de base de données'); } -} +}; export const getUsersAdmin = async () => { try { - const users = await db.select( - { + const users = await db + .select({ userId: userSchema.id, firstName: userSchema.first_name, lastName: userSchema.last_name, @@ -106,9 +110,9 @@ export const getUsersAdmin = async () => { branch: userSchema.branch, contact: userSchema.contact, permission: userSchema.permission, - discord_id: userSchema.discord_id - } - ).from(userSchema); + discord_id: userSchema.discord_id, + }) + .from(userSchema); return users; } catch (err) { console.error('Erreur lors de la récupération des utilisateurs ', err); @@ -118,15 +122,15 @@ export const getUsersAdmin = async () => { export const getUsers = async () => { try { - const users = await db.select( - { + const users = await db + .select({ userId: userSchema.id, firstName: userSchema.first_name, lastName: userSchema.last_name, permission: userSchema.permission, - email: userSchema.email - } - ).from(userSchema); + email: userSchema.email, + }) + .from(userSchema); return users; } catch (err) { console.error('Erreur lors de la récupération des utilisateurs ', err); @@ -136,17 +140,35 @@ export const getUsers = async () => { export const getUserContactInformation = async (userId: number) => { try { - const user = await db.select( - { + const user = await db + .select({ userId: userInformationSchema.user_id, urgency_contact_name: userInformationSchema.urgency_contact_name, urgency_contact_phone: userInformationSchema.urgency_contact_phone, - contact_CE: userInformationSchema.contact_CE - } - ).from(userInformationSchema).where(eq(userInformationSchema.user_id, userId)); + contact_CE: userInformationSchema.contact_CE, + }) + .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); + 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.UrgencyContactName, + urgency_contact_phone: contact.UrgencyContactPhone, + contact_CE: contact.ContactCE, + }; + + const result = await db.insert(userInformationSchema).values(newContactInfo).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'); } }; @@ -182,10 +204,9 @@ export const getUsersAll = async () => { factionName, roles, }; - }) + }), ); - return userWithTeam; } catch (err) { console.error('Erreur lors de la récupération des utilisateurs ', err); @@ -195,15 +216,16 @@ export const getUsersAll = async () => { export const getUsersbyPermission = async (permission: string) => { try { - const users = await db.select( - { + const users = await db + .select({ userId: userSchema.id, firstName: userSchema.first_name, lastName: userSchema.last_name, email: userSchema.email, - branch: userSchema.branch - } - ).from(userSchema).where(eq(userSchema.permission, permission)); + branch: userSchema.branch, + }) + .from(userSchema) + .where(eq(userSchema.permission, permission)); return users; } catch (err) { console.error('Erreur lors de la récupération des utilisateurs ', err); @@ -213,29 +235,27 @@ export const getUsersbyPermission = async (permission: string) => { export const updateUserPassword = async (userId: number, password: string) => { try { - const result = await db.update(userSchema) + const result = await db + .update(userSchema) .set({ - password: password + password: password, }) .where(eq(userSchema.id, userId)); return result.rows[0]; } catch (err) { - console.error('Erreur lors de la récupération et de l\'update de l\'utilisateur par email:', err); + console.error("Erreur lors de la récupération et de l'update de l'utilisateur par email:", err); throw new Error('Erreur de base de données'); } -} +}; -export const updateUserInfoByUserId = async ( - userId: number, - branch?: string, - contact?: string -) => { +export const updateUserInfoByUserId = async (userId: number, branch?: string, contact?: string) => { try { - const result = await db.update(userSchema) + const result = await db + .update(userSchema) .set({ branch: branch, - contact: contact + contact: contact, }) .where(eq(userSchema.id, userId)); @@ -246,33 +266,27 @@ export const updateUserInfoByUserId = async ( } }; -export const updateUserByAdmin = async ( - userId: number, - updates: Partial -) => { +export const updateUserByAdmin = async (userId: number, updates: Partial) => { try { - if (Object.keys(updates).length === 0) { throw new Error('Aucune donnée à mettre à jour'); } - const result = await db.update(userSchema) - .set( - updates - ) - .where(eq(userSchema.id, userId)); + const result = await db.update(userSchema).set(updates).where(eq(userSchema.id, userId)); return result; } catch (err) { - console.error('Erreur lors de la mise à jour par l\'admin:', err); + console.error("Erreur lors de la mise à jour par l'admin:", err); throw new Error('Erreur de base de données'); } }; export const deleteUserById = async (userId: number) => { try { - - const user_registration_token = await db.select({ user_id: registrationSchema.user_id }).from(registrationSchema).where(eq(registrationSchema.user_id, userId)); + const user_registration_token = await db + .select({ user_id: registrationSchema.user_id }) + .from(registrationSchema) + .where(eq(registrationSchema.user_id, userId)); if (user_registration_token.length > 0) { await db.delete(registrationSchema).where(eq(registrationSchema.user_id, userId)); @@ -281,7 +295,7 @@ export const deleteUserById = async (userId: number) => { const result = await db.delete(userSchema).where(eq(userSchema.id, userId)); return result; } catch (err) { - console.error('Erreur lors de la suppression de l\'utilisateur:', err); + console.error("Erreur lors de la suppression de l'utilisateur:", err); throw new Error('Erreur de base de données'); } }; diff --git a/backend/types/express.d.ts b/backend/types/express.d.ts index 7e446c6..c4e0efe 100644 --- a/backend/types/express.d.ts +++ b/backend/types/express.d.ts @@ -1,6 +1,6 @@ -import { type JwtPayload } from "jsonwebtoken"; +import { type JwtPayload } from 'jsonwebtoken'; -declare module "express-serve-static-core" { +declare module 'express-serve-static-core' { interface Request { user?: JwtPayload | string; permission?: JwtPayload | string; diff --git a/backend/types/user.d.ts b/backend/types/user.d.ts new file mode 100644 index 0000000..72089f3 --- /dev/null +++ b/backend/types/user.d.ts @@ -0,0 +1,5 @@ +export type UserContactInformation = { + ContactCE: string; + UrgencyContactName: string; + UrgencyContactPhone: number; +}; From 4e3e1a378b0e2e019aacf122ed915678b6f286b1 Mon Sep 17 00:00:00 2001 From: Antoine Date: Sun, 12 Jul 2026 13:09:11 +0200 Subject: [PATCH 7/7] feat: add form with answer sending --- backend/src/controllers/user.controller.ts | 13 +- .../database/migrations/0023_even_morg.sql | 8 + .../migrations/meta/0023_snapshot.json | 1335 +++++++++++++++++ .../database/migrations/meta/_journal.json | 7 + backend/src/routes/user.routes.ts | 1 + .../Relational/userinformation.schema.ts | 18 +- backend/src/services/user.service.ts | 6 +- backend/types/user.d.ts | 6 +- frontend/src/components/home/urgencyModal.tsx | 38 +- frontend/src/components/navbar.tsx | 1 + frontend/src/interfaces/user.interface.ts | 11 +- .../src/services/requests/user.service.ts | 39 +- 12 files changed, 1445 insertions(+), 38 deletions(-) create mode 100644 backend/src/database/migrations/0023_even_morg.sql create mode 100644 backend/src/database/migrations/meta/0023_snapshot.json diff --git a/backend/src/controllers/user.controller.ts b/backend/src/controllers/user.controller.ts index ff104cf..d78589e 100644 --- a/backend/src/controllers/user.controller.ts +++ b/backend/src/controllers/user.controller.ts @@ -99,9 +99,20 @@ export const getUserContactInformation = async (req: Request, res: Response) => } }; +export const getCurrentUserContactInformation = async (req: Request, res: Response) => { + const userId = req.user?.userId; + + 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 = async (req: Request, res: Response) => { const userId = req.user?.userId; - const { contact }: { contact: UserContactInformation } = req.body; + const contact: UserContactInformation = req.body; try { const result = await user_service.createUserContactInformation(parseInt(userId), contact); diff --git a/backend/src/database/migrations/0023_even_morg.sql b/backend/src/database/migrations/0023_even_morg.sql new file mode 100644 index 0000000..94a1760 --- /dev/null +++ b/backend/src/database/migrations/0023_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/meta/0023_snapshot.json b/backend/src/database/migrations/meta/0023_snapshot.json new file mode 100644 index 0000000..1cbadb9 --- /dev/null +++ b/backend/src/database/migrations/meta/0023_snapshot.json @@ -0,0 +1,1335 @@ +{ + "id": "77c4b2f6-c24d-4eb9-80a9-42fb8b9ef380", + "prevId": "93925edf-9622-4ce2-80bc-26050abbce0b", + "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": {} + } +} \ 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 6924d8e..ea074cf 100644 --- a/backend/src/database/migrations/meta/_journal.json +++ b/backend/src/database/migrations/meta/_journal.json @@ -162,6 +162,13 @@ "when": 1757002717384, "tag": "0022_light_omega_red", "breakpoints": true + }, + { + "idx": 23, + "version": "7", + "when": 1783854058299, + "tag": "0023_even_morg", + "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 05f0bd0..56bade2 100644 --- a/backend/src/routes/user.routes.ts +++ b/backend/src/routes/user.routes.ts @@ -21,5 +21,6 @@ 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('/user/getusercontactinformation', userController.getCurrentUserContactInformation); export default userRouter; diff --git a/backend/src/schemas/Relational/userinformation.schema.ts b/backend/src/schemas/Relational/userinformation.schema.ts index bfdceea..feae1a7 100644 --- a/backend/src/schemas/Relational/userinformation.schema.ts +++ b/backend/src/schemas/Relational/userinformation.schema.ts @@ -1,11 +1,13 @@ -import { pgTable, integer, text } from "drizzle-orm/pg-core"; -import { userSchema } from "../Basic/user.schema"; +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: integer("urgency_contact_phone"), - contact_CE: text("contact_CE"), +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'), + contact_CE: text('contact_CE'), }); -export type UserInformation = typeof userInformationSchema.$inferSelect; \ No newline at end of file +export type UserInformation = typeof userInformationSchema.$inferSelect; diff --git a/backend/src/services/user.service.ts b/backend/src/services/user.service.ts index 2a80a9b..bbb1589 100644 --- a/backend/src/services/user.service.ts +++ b/backend/src/services/user.service.ts @@ -160,9 +160,9 @@ export const createUserContactInformation = async (userId: number, contact: User try { const newContactInfo = { user_id: userId, - urgency_contact_name: contact.UrgencyContactName, - urgency_contact_phone: contact.UrgencyContactPhone, - contact_CE: contact.ContactCE, + urgency_contact_name: contact.urgency_contact_name, + urgency_contact_phone: contact.urgency_contact_phone, + contact_CE: contact.contact_CE, }; const result = await db.insert(userInformationSchema).values(newContactInfo).returning(); diff --git a/backend/types/user.d.ts b/backend/types/user.d.ts index 72089f3..928f2e3 100644 --- a/backend/types/user.d.ts +++ b/backend/types/user.d.ts @@ -1,5 +1,5 @@ export type UserContactInformation = { - ContactCE: string; - UrgencyContactName: string; - UrgencyContactPhone: number; + urgency_contact_name: string; + urgency_contact_phone: string; + contact_CE: string; }; diff --git a/frontend/src/components/home/urgencyModal.tsx b/frontend/src/components/home/urgencyModal.tsx index 343fe2a..d0ac867 100644 --- a/frontend/src/components/home/urgencyModal.tsx +++ b/frontend/src/components/home/urgencyModal.tsx @@ -1,19 +1,45 @@ +import { useState } from 'react'; import { useSearchParams } from 'react-router-dom'; +import { createUserContactInformation } from '../../services/requests/user.service'; +import { Button } from '../ui/button'; +import { Input } from '../ui/input'; import Modal from '../ui/modal'; function UrgencyModal() { const [searchParams, setSearchParams] = useSearchParams(); + const [form, setForm] = useState({ contact_CE: '', urgency_contact_name: '', urgency_contact_phone: '' }); const isLogin = searchParams.get('login') === 'true'; return ( - setSearchParams({})} - buttons={null} - /> + setSearchParams({})} buttons={null}> +
    +

    Bienvenu sur le site de l'intégration, blablabla faut que tu completes le formulaire.

    + setForm({ ...form, contact_CE: e.target.value })} + /> + setForm({ ...form, urgency_contact_name: e.target.value })} + /> + setForm({ ...form, urgency_contact_phone: e.target.value })} + /> + +
    +
    ); } diff --git a/frontend/src/components/navbar.tsx b/frontend/src/components/navbar.tsx index 12a6e1c..a2e67c6 100644 --- a/frontend/src/components/navbar.tsx +++ b/frontend/src/components/navbar.tsx @@ -43,6 +43,7 @@ export const Navbar = () => { try { const contactInfo = await getCurrentUserContactInformation(); setHasContactInformation(contactInfo.urgency_contact_phone !== null); + console.log('Contact Information:', contactInfo); } catch (error) { console.error('Erreur lors de la récupération des informations de contact :', error); } diff --git a/frontend/src/interfaces/user.interface.ts b/frontend/src/interfaces/user.interface.ts index ea26891..775a65b 100644 --- a/frontend/src/interfaces/user.interface.ts +++ b/frontend/src/interfaces/user.interface.ts @@ -13,5 +13,12 @@ export interface User { export interface UserContactInformation { userId: number; urgency_contact_name: string; - urgency_contact_phone: number; -} \ No newline at end of file + urgency_contact_phone: string; + contact_CE: string; +} + +export interface CreateUserContactInformationRequest { + urgency_contact_name: string; + urgency_contact_phone: string; + contact_CE: string; +} diff --git a/frontend/src/services/requests/user.service.ts b/frontend/src/services/requests/user.service.ts index 1134c17..23d9428 100644 --- a/frontend/src/services/requests/user.service.ts +++ b/frontend/src/services/requests/user.service.ts @@ -1,4 +1,8 @@ -import { type User, type UserContactInformation } from '../../interfaces/user.interface'; +import { + type CreateUserContactInformationRequest, + type User, + type UserContactInformation, +} from '../../interfaces/user.interface'; import api from '../api'; export const getPermission = (): string | null => { @@ -24,22 +28,22 @@ export const isConnected = (): boolean => { }; export const getUsers = async () => { - const response = await api.get("/user/user/getusers"); + const response = await api.get('/user/user/getusers'); const users = response.data.data; return users; -} +}; export const getUsersAdmin = async () => { - const response = await api.get("/user/admin/getusers"); + const response = await api.get('/user/admin/getusers'); const users = response.data.data; return users; -} +}; export const getUsersByPermission = async () => { - const response = await api.get("/user/admin/getusersbypermission"); + const response = await api.get('/user/admin/getusersbypermission'); const users = response.data.data; return users; -} +}; export const getUserContactInformation = async (userId: number) => { const response = await api.get(`/user/admin/getusercontactinformation/${userId}`); @@ -47,8 +51,13 @@ export const getUserContactInformation = async (userId: number) => { return users; }; +export const createUserContactInformation = async (data: CreateUserContactInformationRequest) => { + const response = await api.post(`/user/user/usercontactinformation`, data); + return response.data; +}; + export const getCurrentUser = async () => { - const res = await api.get("/user/user/me"); + const res = await api.get('/user/user/me'); return res.data.data; }; @@ -59,26 +68,26 @@ export const getCurrentUserContactInformation = async () => { }; export const updateCurrentUser = async (data: Partial) => { - const response = await api.patch("/user/user/me", data); - return response.data + const response = await api.patch('/user/user/me', data); + return response.data; }; export const updateUserByAdmin = async (id: number, data: Partial) => { const response = await api.patch(`/user/admin/user/${id}`, data); - return response.data + return response.data; }; export const deleteUserByAdmin = async (id: number) => { const response = await api.delete(`/user/admin/user/${id}`); - return response.data + return response.data; }; export const syncnewStudent = async (date: string) => { const response = await api.post(`/user/admin/syncnewstudent/`, { date }); - return response.data + return response.data; }; export const syncDiscordUser = async (code: string) => { const response = await api.post(`/discord/user/callback/`, { code }); - return response.data -} + return response.data; +};