From ea1965ef86fe3e1f34762159eed345f8974fc1c9 Mon Sep 17 00:00:00 2001
From: Arthur Dodin
Date: Sun, 17 Aug 2025 20:41:51 +0200
Subject: [PATCH 01/10] feat: roadbook redirection
---
frontend/package-lock.json | 18 ++++++++---------
frontend/package.json | 4 ++--
frontend/src/App.tsx | 26 ++++++++++++++-----------
frontend/src/pages/RoadbookRedirect.tsx | 8 ++++++++
frontend/src/vite-env.d.ts | 1 +
5 files changed, 35 insertions(+), 22 deletions(-)
create mode 100644 frontend/src/pages/RoadbookRedirect.tsx
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 47a0acb..a11e448 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -22,9 +22,9 @@
"framer-motion": "^12.7.4",
"jwt-decode": "^4.0.0",
"lucide-react": "^0.488.0",
- "react": "^19.1.0",
+ "react": "^19.1.1",
"react-day-picker": "^8.10.1",
- "react-dom": "^19.0.0",
+ "react-dom": "^19.1.1",
"react-hook-form": "^7.55.0",
"react-icons": "^5.5.0",
"react-router-dom": "^7.5.0",
@@ -4963,9 +4963,9 @@
"license": "MIT"
},
"node_modules/react": {
- "version": "19.1.0",
- "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz",
- "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==",
+ "version": "19.1.1",
+ "resolved": "https://registry.npmjs.org/react/-/react-19.1.1.tgz",
+ "integrity": "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
@@ -4986,15 +4986,15 @@
}
},
"node_modules/react-dom": {
- "version": "19.1.0",
- "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz",
- "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==",
+ "version": "19.1.1",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.1.tgz",
+ "integrity": "sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==",
"license": "MIT",
"dependencies": {
"scheduler": "^0.26.0"
},
"peerDependencies": {
- "react": "^19.1.0"
+ "react": "^19.1.1"
}
},
"node_modules/react-hook-form": {
diff --git a/frontend/package.json b/frontend/package.json
index 8c85ac7..aad90f4 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -24,9 +24,9 @@
"framer-motion": "^12.7.4",
"jwt-decode": "^4.0.0",
"lucide-react": "^0.488.0",
- "react": "^19.1.0",
+ "react": "^19.1.1",
"react-day-picker": "^8.10.1",
- "react-dom": "^19.0.0",
+ "react-dom": "^19.1.1",
"react-hook-form": "^7.55.0",
"react-icons": "^5.5.0",
"react-router-dom": "^7.5.0",
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index 8f6d4cc..88c1e79 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -6,16 +6,18 @@ import LoginPage from './pages/Auth';
import { HomePage } from './pages/Home';
import { ProfilPage } from './pages/Profil';
import { ShotgunPage } from './pages/Shotgun';
-import { AdminPageRole,
- AdminPageTeam,
- AdminPageEvents,
- AdminPageExport,
- AdminPageFaction,
- AdminPagePerm,
- AdminPageChall,
- AdminPageEmail,
- AdminPageUser,
- AdminPageNews } from './pages/Admin';
+import {
+ AdminPageRole,
+ AdminPageTeam,
+ AdminPageEvents,
+ AdminPageExport,
+ AdminPageFaction,
+ AdminPagePerm,
+ AdminPageChall,
+ AdminPageEmail,
+ AdminPageUser,
+ AdminPageNews
+} from './pages/Admin';
import ProtectedRoute from './components/utils/protectedroute';
import AdminRoute from './components/utils/adminroute';
@@ -27,7 +29,8 @@ import { ResetPasswordPage } from './pages/ResetPassword'
import { WeiPage } from './pages/Wei';
import { SdiPage } from './pages/Sdi';
import { NewsPage } from './pages/News';
-import {DiscordPage} from './pages/Discord';
+import { DiscordPage } from './pages/Discord';
+import { RoadbookRedirect } from './pages/RoadbookRedirect';
import PrivateRoute from './components/utils/privateroute';
@@ -54,6 +57,7 @@ const App: React.FC = () => {
} />
} />
} />
+ } />
{/* Utilisateurs connectés */}
} />
diff --git a/frontend/src/pages/RoadbookRedirect.tsx b/frontend/src/pages/RoadbookRedirect.tsx
new file mode 100644
index 0000000..08991c2
--- /dev/null
+++ b/frontend/src/pages/RoadbookRedirect.tsx
@@ -0,0 +1,8 @@
+import React from "react";
+
+export const RoadbookRedirect: React.FC = () => {
+ React.useEffect(() => {
+ window.location.href = import.meta.env.VITE_ROADBOOK_URL;
+ }, []);
+ return null;
+};
\ No newline at end of file
diff --git a/frontend/src/vite-env.d.ts b/frontend/src/vite-env.d.ts
index c619b5f..8dd80d6 100644
--- a/frontend/src/vite-env.d.ts
+++ b/frontend/src/vite-env.d.ts
@@ -3,3 +3,4 @@ VITE_SERVICE_URL = "https://integration.utt.fr/"
VITE_CAS_LOGIN_URL = "https://cas.utt.fr/cas/login"
VITE_API_URL = "http://localhost:4001/api"
VITE_ANALYTICS_WEBSITE_ID = ""
+VITE_ROADBOOK_URL = ""
From b8e1774a780ca9f7b45bd1fe976d0106ec2870a5 Mon Sep 17 00:00:00 2001
From: Arthur Dodin
Date: Sun, 17 Aug 2025 21:43:16 +0200
Subject: [PATCH 02/10] update: add redirection text
---
frontend/src/pages/RoadbookRedirect.tsx | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/frontend/src/pages/RoadbookRedirect.tsx b/frontend/src/pages/RoadbookRedirect.tsx
index 08991c2..08e18a3 100644
--- a/frontend/src/pages/RoadbookRedirect.tsx
+++ b/frontend/src/pages/RoadbookRedirect.tsx
@@ -4,5 +4,11 @@ export const RoadbookRedirect: React.FC = () => {
React.useEffect(() => {
window.location.href = import.meta.env.VITE_ROADBOOK_URL;
}, []);
- return null;
+ return (
+
+
+ Redirection en cours...
+
+
+ );
};
\ No newline at end of file
From a64b400d9be53d8c677395f0ddda6c0eb9ef9422 Mon Sep 17 00:00:00 2001
From: Arthur Dodin
Date: Sun, 17 Aug 2025 21:43:57 +0200
Subject: [PATCH 03/10] feat: roadbook section on home page
---
frontend/src/components/home/infosSection.tsx | 16 ++++++++++++++++
1 file changed, 16 insertions(+)
diff --git a/frontend/src/components/home/infosSection.tsx b/frontend/src/components/home/infosSection.tsx
index d647065..1688e5c 100644
--- a/frontend/src/components/home/infosSection.tsx
+++ b/frontend/src/components/home/infosSection.tsx
@@ -1,5 +1,7 @@
import { Swiper, SwiperSlide } from "swiper/react";
import { Pagination, Autoplay } from "swiper/modules";
+import { Button } from "../ui/button";
+import { Link } from "react-router-dom";
import "swiper/swiper-bundle.css";
export const Infos = () => {
@@ -79,6 +81,20 @@ export const Infos = () => {
C'est ici que tu trouveras toutes les informations nécessaires au déroulement de la semaine d'inté. Par exemple, tu pourras prendre ta place pour le WEI ou regarder quelle faction est la plus proche de la victoire.
+
+
+
+ Le Roadbook de l'inté
+
+
+ Retrouve ici toutes les informations de l’intégration ! Les plannings, la prévention, les activités, les menus… Tout pour passer des moments incroyables !
+
+
+
+ Accéder au Roadbook
+
+
+
From f69b98fff4dd2e8eb79b01fb5a53d1439d4452cf Mon Sep 17 00:00:00 2001
From: Arthur Dodin
Date: Sun, 17 Aug 2025 23:01:34 +0200
Subject: [PATCH 04/10] update: add VITE_ROADBOOK_URL in ci
---
.github/workflows/ci.yaml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index fd13bbf..ab85ead 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -7,7 +7,6 @@ on:
- dev
jobs:
-
deploy-api:
runs-on: self-hosted
@@ -59,5 +58,6 @@ jobs:
VITE_API_URL=${{ github.ref == 'refs/heads/prod' && 'https://integration.utt.fr/api' || 'https://integration.dev.uttnetgroup.fr/api' }}
VITE_SERVICE_URL=${{ github.ref == 'refs/heads/prod' && 'https://integration.utt.fr/' || 'https://integration.dev.uttnetgroup.fr/' }}
VITE_ANALYTICS_WEBSITE_ID=${{ github.ref == 'refs/heads/prod' && secrets.ANALYTICS_WEBSITE_ID_PROD || secrets.ANALYTICS_WEBSITE_ID_DEV }}
+ VITE_ROADBOOK_URL=${{ secrets.ROADBOOK_URL }}
tags: |
- ${{ secrets.REGISTRY_URL }}/integration/front:${{ github.ref == 'refs/heads/prod' && 'prod' || 'dev' }}
\ No newline at end of file
+ ${{ secrets.REGISTRY_URL }}/integration/front:${{ github.ref == 'refs/heads/prod' && 'prod' || 'dev' }}
From 9c44dc0ab0e0376e8a9cf3328c8ffec10e9100eb Mon Sep 17 00:00:00 2001
From: Arthur Dodin
Date: Tue, 26 Aug 2025 18:47:03 +0200
Subject: [PATCH 05/10] update: switch to real page
---
.github/workflows/ci.yaml | 3 +-
frontend/src/App.tsx | 4 +-
.../src/components/roadbook/roadbookLinks.tsx | 39 +++++++++++++++++++
frontend/src/pages/Roadbook.tsx | 18 +++++++++
frontend/src/pages/RoadbookRedirect.tsx | 14 -------
frontend/src/vite-env.d.ts | 3 +-
6 files changed, 63 insertions(+), 18 deletions(-)
create mode 100644 frontend/src/components/roadbook/roadbookLinks.tsx
create mode 100644 frontend/src/pages/Roadbook.tsx
delete mode 100644 frontend/src/pages/RoadbookRedirect.tsx
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index ab85ead..63d15c2 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -58,6 +58,7 @@ jobs:
VITE_API_URL=${{ github.ref == 'refs/heads/prod' && 'https://integration.utt.fr/api' || 'https://integration.dev.uttnetgroup.fr/api' }}
VITE_SERVICE_URL=${{ github.ref == 'refs/heads/prod' && 'https://integration.utt.fr/' || 'https://integration.dev.uttnetgroup.fr/' }}
VITE_ANALYTICS_WEBSITE_ID=${{ github.ref == 'refs/heads/prod' && secrets.ANALYTICS_WEBSITE_ID_PROD || secrets.ANALYTICS_WEBSITE_ID_DEV }}
- VITE_ROADBOOK_URL=${{ secrets.ROADBOOK_URL }}
+ VITE_ROADBOOK_URL_FRENCH=${{ secrets.ROADBOOK_URL_FRENCH }}
+ VITE_ROADBOOK_URL_ENGLISH=${{ secrets.ROADBOOK_URL_ENGLISH }}
tags: |
${{ secrets.REGISTRY_URL }}/integration/front:${{ github.ref == 'refs/heads/prod' && 'prod' || 'dev' }}
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index a0f7712..db1fe25 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -31,7 +31,7 @@ import { WeiPage } from './pages/Wei';
import { SdiPage } from './pages/Sdi';
import { NewsPage } from './pages/News';
import { DiscordPage } from './pages/Discord';
-import { RoadbookRedirect } from './pages/RoadbookRedirect';
+import { Roadbook } from './pages/Roadbook';
import PrivateRoute from './components/utils/privateroute';
import { GamesPage } from './pages/Games';
import { FoodPage } from './pages/Food';
@@ -60,7 +60,7 @@ const App: React.FC = () => {
} />
} />
} />
- } />
+ } />
{/* Utilisateurs connectés */}
} />
diff --git a/frontend/src/components/roadbook/roadbookLinks.tsx b/frontend/src/components/roadbook/roadbookLinks.tsx
new file mode 100644
index 0000000..e4fc046
--- /dev/null
+++ b/frontend/src/components/roadbook/roadbookLinks.tsx
@@ -0,0 +1,39 @@
+import { Button } from "../ui/button";
+import { Card } from "../ui/card";
+import { Link } from "react-router-dom";
+
+export const RoadBookLinks = () => {
+
+ return (
+
+
+
+ 📖 {" "}
+ Roadbook de l'intégration {" "}
+ 🚗
+
+
+
+
+
Lorem ipsum dolor sit amet consectetur adipisicing elit. Quae veritatis, ratione eaque exercitationem laborum nisi at, neque modi vel culpa nam corporis et alias reiciendis voluptatibus ullam. Sequi, iure vero! Lorem ipsum dolor sit amet consectetur adipisicing elit. Beatae fugit iusto illo. Laboriosam modi distinctio accusamus provident ipsum esse delectus voluptatum. Illum, ab distinctio. Ut deleniti at iste cupiditate consectetur. Lorem ipsum dolor sit amet consectetur adipisicing elit. Sapiente accusamus illum dolor expedita sint deleniti sed, iure aperiam. Eligendi ipsam commodi dicta hic, modi mollitia molestias repellat quam repellendus fugit.
+
+
+
+
+
+ 🔗
+ Accéder à la version Française
+
+
+ {/*
+
+ English Version
+
+ */}
+
An english version will be available soon !
+
+
+
+ );
+};
+
diff --git a/frontend/src/pages/Roadbook.tsx b/frontend/src/pages/Roadbook.tsx
new file mode 100644
index 0000000..905678f
--- /dev/null
+++ b/frontend/src/pages/Roadbook.tsx
@@ -0,0 +1,18 @@
+import React from "react";
+import { RoadBookLinks } from "../components/roadbook/roadbookLinks";
+
+export const Roadbook: React.FC = () => {
+ return (
+
+ );
+};
\ No newline at end of file
diff --git a/frontend/src/pages/RoadbookRedirect.tsx b/frontend/src/pages/RoadbookRedirect.tsx
deleted file mode 100644
index 08e18a3..0000000
--- a/frontend/src/pages/RoadbookRedirect.tsx
+++ /dev/null
@@ -1,14 +0,0 @@
-import React from "react";
-
-export const RoadbookRedirect: React.FC = () => {
- React.useEffect(() => {
- window.location.href = import.meta.env.VITE_ROADBOOK_URL;
- }, []);
- return (
-
-
- Redirection en cours...
-
-
- );
-};
\ No newline at end of file
diff --git a/frontend/src/vite-env.d.ts b/frontend/src/vite-env.d.ts
index 8dd80d6..377343f 100644
--- a/frontend/src/vite-env.d.ts
+++ b/frontend/src/vite-env.d.ts
@@ -3,4 +3,5 @@ VITE_SERVICE_URL = "https://integration.utt.fr/"
VITE_CAS_LOGIN_URL = "https://cas.utt.fr/cas/login"
VITE_API_URL = "http://localhost:4001/api"
VITE_ANALYTICS_WEBSITE_ID = ""
-VITE_ROADBOOK_URL = ""
+VITE_ROADBOOK_URL_FRENCH = ""
+VITE_ROADBOOK_URL_ENGLISH = ""
From ea7c3a107c9626ea50b7033982876bb55b3986ac Mon Sep 17 00:00:00 2001
From: Arthur Dodin
Date: Tue, 26 Aug 2025 19:03:14 +0200
Subject: [PATCH 06/10] Merge branch 'prod' into roadbook
---
.gitignore | 1 +
backend/package-lock.json | 166 +
backend/package.json | 2 +
backend/server.ts | 10 +-
backend/src/controllers/email.controller.ts | 21 +-
backend/src/controllers/event.controller.ts | 23 +-
backend/src/controllers/export.controller.ts | 83 -
.../src/controllers/im_export.controller.ts | 168 +
.../src/controllers/permanence.controller.ts | 74 +-
backend/src/controllers/team.controller.ts | 162 +-
backend/src/controllers/tent.controller.ts | 113 +
backend/src/controllers/user.controller.ts | 8 +-
backend/src/database/db.ts | 81 +-
backend/src/database/initdb/initevent.ts | 8 +-
backend/src/database/initdb/initrole.ts | 1 +
.../migrations/0017_melted_meggan.sql | 1 +
.../migrations/0018_puzzling_arachne.sql | 1 +
.../migrations/0019_complete_moondragon.sql | 2 +
.../0020_strange_colonel_america.sql | 8 +
.../migrations/0021_colossal_madame_web.sql | 10 +
.../migrations/meta/0017_snapshot.json | 1124 +++++++
.../migrations/meta/0018_snapshot.json | 1131 +++++++
.../migrations/meta/0019_snapshot.json | 1144 +++++++
.../migrations/meta/0020_snapshot.json | 1204 +++++++
.../migrations/meta/0021_snapshot.json | 1278 ++++++++
.../database/migrations/meta/_journal.json | 35 +
backend/src/middlewares/multer.middleware.ts | 115 +-
.../src/middlewares/respoperm.middleware.ts | 29 +
backend/src/routes/event.routes.ts | 2 +
backend/src/routes/export.routes.ts | 12 -
backend/src/routes/im_export.routes.ts | 19 +
backend/src/routes/news.routes.ts | 9 +-
backend/src/routes/permanences.routes.ts | 8 +-
backend/src/routes/tent.routes.ts | 21 +
backend/src/routes/user.routes.ts | 7 +-
backend/src/schemas/Basic/event.schema.ts | 3 +-
.../src/schemas/Basic/permanence.schema.ts | 3 +-
.../Relational/userpermanences.schema.ts | 13 +-
.../src/schemas/Relational/usertent.schema.ts | 14 +
backend/src/services/event.service.ts | 6 +
backend/src/services/export.service.ts | 39 -
backend/src/services/im_export.service.ts | 87 +
backend/src/services/permanence.service.ts | 221 +-
backend/src/services/tent.service.ts | 136 +
backend/src/services/user.service.ts | 54 +-
backend/src/utils/emailtemplates.ts | 31 +
backend/src/utils/no_sync_list.ts | 4 +
frontend/Dockerfile | 2 +-
frontend/package-lock.json | 2896 ++++++++++++++++-
frontend/package.json | 12 +-
frontend/src/App.tsx | 43 +-
.../AdminChallenge/adminChalengeList.tsx | 69 +
.../adminChallengeAddPointsForm.tsx | 102 +
.../AdminChallenge/adminChallengeEditor.tsx | 110 +
.../adminChallengeValidatedList.tsx | 106 +
.../Admin/AdminPerm/adminPermAction.tsx | 87 +
.../Admin/AdminPerm/adminPermForm.tsx | 220 ++
.../Admin/AdminPerm/adminPermImport.tsx | 90 +
.../Admin/AdminPerm/adminPermList.tsx | 139 +
.../Admin/AdminPerm/adminPermMembers.tsx | 195 ++
.../src/components/Admin/adminChallenge.tsx | 483 ---
frontend/src/components/Admin/adminEvent.tsx | 270 +-
.../components/Admin/adminExportImport.tsx | 351 +-
frontend/src/components/Admin/adminNews.tsx | 4 +-
frontend/src/components/Admin/adminPerm.tsx | 382 ---
frontend/src/components/Admin/adminTeam.tsx | 2 +-
frontend/src/components/Admin/adminTent.tsx | 199 ++
frontend/src/components/Admin/adminUser.tsx | 173 +-
.../components/Plannings/planningSection.tsx | 110 +
.../components/WEI_SDI_Food/foodSection.tsx | 69 +-
.../components/challenge/challengeList.tsx | 43 +-
frontend/src/components/navbar.tsx | 429 +--
frontend/src/components/news/newsSection.tsx | 2 +-
.../src/components/permanence/appealPerm.tsx | 186 ++
.../src/components/permanence/permForm.tsx | 171 -
.../src/components/permanence/permList.tsx | 82 +
.../src/components/permanence/permUser.tsx | 85 +
frontend/src/components/tent/tentSection.tsx | 211 ++
frontend/src/components/ui/accordion.tsx | 64 +
frontend/src/components/ui/calendar.tsx | 73 -
.../src/components/utils/datetime_utils.ts | 33 +-
.../src/interfaces/permanence.interface.ts | 13 +-
frontend/src/interfaces/tent.interface.ts | 6 +
frontend/src/pages/Admin.tsx | 159 -
frontend/src/pages/Event.tsx | 0
frontend/src/pages/Perm.tsx | 17 -
frontend/src/pages/admin.tsx | 368 +++
frontend/src/pages/{Auth.tsx => auth.tsx} | 0
.../pages/{Challenge.tsx => challenge.tsx} | 4 +-
.../src/pages/{Discord.tsx => discord.tsx} | 0
frontend/src/pages/{Food.tsx => food.tsx} | 0
frontend/src/pages/{Games.tsx => games.tsx} | 0
frontend/src/pages/{Home.tsx => home.tsx} | 0
frontend/src/pages/{News.tsx => news.tsx} | 0
.../pages/{Parrainage.tsx => parrainage.tsx} | 0
frontend/src/pages/perm.tsx | 201 ++
frontend/src/pages/{Wei.tsx => plannings.tsx} | 15 +-
frontend/src/pages/{Profil.tsx => profil.tsx} | 2 +-
.../src/pages/{Register.tsx => register.tsx} | 0
.../{ResetPassword.tsx => resetPassword.tsx} | 0
frontend/src/pages/{Sdi.tsx => sdi.tsx} | 0
.../src/pages/{Shotgun.tsx => shotgun.tsx} | 0
frontend/src/pages/wei.tsx | 31 +
.../src/services/requests/event.service.ts | 15 +
.../src/services/requests/export.service.ts | 9 -
.../services/requests/im_export.service.ts | 27 +
.../services/requests/permanence.service.ts | 33 +
.../src/services/requests/tent.service.ts | 39 +
.../src/services/requests/user.service.ts | 2 +-
109 files changed, 13750 insertions(+), 2376 deletions(-)
delete mode 100644 backend/src/controllers/export.controller.ts
create mode 100644 backend/src/controllers/im_export.controller.ts
create mode 100644 backend/src/controllers/tent.controller.ts
create mode 100644 backend/src/database/migrations/0017_melted_meggan.sql
create mode 100644 backend/src/database/migrations/0018_puzzling_arachne.sql
create mode 100644 backend/src/database/migrations/0019_complete_moondragon.sql
create mode 100644 backend/src/database/migrations/0020_strange_colonel_america.sql
create mode 100644 backend/src/database/migrations/0021_colossal_madame_web.sql
create mode 100644 backend/src/database/migrations/meta/0017_snapshot.json
create mode 100644 backend/src/database/migrations/meta/0018_snapshot.json
create mode 100644 backend/src/database/migrations/meta/0019_snapshot.json
create mode 100644 backend/src/database/migrations/meta/0020_snapshot.json
create mode 100644 backend/src/database/migrations/meta/0021_snapshot.json
create mode 100644 backend/src/middlewares/respoperm.middleware.ts
delete mode 100644 backend/src/routes/export.routes.ts
create mode 100644 backend/src/routes/im_export.routes.ts
create mode 100644 backend/src/routes/tent.routes.ts
create mode 100644 backend/src/schemas/Relational/usertent.schema.ts
delete mode 100644 backend/src/services/export.service.ts
create mode 100644 backend/src/services/im_export.service.ts
create mode 100644 backend/src/services/tent.service.ts
create mode 100644 backend/src/utils/no_sync_list.ts
create mode 100644 frontend/src/components/Admin/AdminChallenge/adminChalengeList.tsx
create mode 100644 frontend/src/components/Admin/AdminChallenge/adminChallengeAddPointsForm.tsx
create mode 100644 frontend/src/components/Admin/AdminChallenge/adminChallengeEditor.tsx
create mode 100644 frontend/src/components/Admin/AdminChallenge/adminChallengeValidatedList.tsx
create mode 100644 frontend/src/components/Admin/AdminPerm/adminPermAction.tsx
create mode 100644 frontend/src/components/Admin/AdminPerm/adminPermForm.tsx
create mode 100644 frontend/src/components/Admin/AdminPerm/adminPermImport.tsx
create mode 100644 frontend/src/components/Admin/AdminPerm/adminPermList.tsx
create mode 100644 frontend/src/components/Admin/AdminPerm/adminPermMembers.tsx
delete mode 100644 frontend/src/components/Admin/adminChallenge.tsx
delete mode 100644 frontend/src/components/Admin/adminPerm.tsx
create mode 100644 frontend/src/components/Admin/adminTent.tsx
create mode 100644 frontend/src/components/Plannings/planningSection.tsx
create mode 100644 frontend/src/components/permanence/appealPerm.tsx
delete mode 100644 frontend/src/components/permanence/permForm.tsx
create mode 100644 frontend/src/components/permanence/permList.tsx
create mode 100644 frontend/src/components/permanence/permUser.tsx
create mode 100644 frontend/src/components/tent/tentSection.tsx
create mode 100644 frontend/src/components/ui/accordion.tsx
delete mode 100644 frontend/src/components/ui/calendar.tsx
create mode 100644 frontend/src/interfaces/tent.interface.ts
delete mode 100644 frontend/src/pages/Admin.tsx
delete mode 100644 frontend/src/pages/Event.tsx
delete mode 100644 frontend/src/pages/Perm.tsx
create mode 100644 frontend/src/pages/admin.tsx
rename frontend/src/pages/{Auth.tsx => auth.tsx} (100%)
rename frontend/src/pages/{Challenge.tsx => challenge.tsx} (79%)
rename frontend/src/pages/{Discord.tsx => discord.tsx} (100%)
rename frontend/src/pages/{Food.tsx => food.tsx} (100%)
rename frontend/src/pages/{Games.tsx => games.tsx} (100%)
rename frontend/src/pages/{Home.tsx => home.tsx} (100%)
rename frontend/src/pages/{News.tsx => news.tsx} (100%)
rename frontend/src/pages/{Parrainage.tsx => parrainage.tsx} (100%)
create mode 100644 frontend/src/pages/perm.tsx
rename frontend/src/pages/{Wei.tsx => plannings.tsx} (59%)
rename frontend/src/pages/{Profil.tsx => profil.tsx} (99%)
rename frontend/src/pages/{Register.tsx => register.tsx} (100%)
rename frontend/src/pages/{ResetPassword.tsx => resetPassword.tsx} (100%)
rename frontend/src/pages/{Sdi.tsx => sdi.tsx} (100%)
rename frontend/src/pages/{Shotgun.tsx => shotgun.tsx} (100%)
create mode 100644 frontend/src/pages/wei.tsx
delete mode 100644 frontend/src/services/requests/export.service.ts
create mode 100644 frontend/src/services/requests/im_export.service.ts
create mode 100644 frontend/src/services/requests/tent.service.ts
diff --git a/.gitignore b/.gitignore
index 73a69e7..9b6f36d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,6 +4,7 @@ frontend/node_modules/
# Ignorer les node_modules du backend
backend/node_modules/
backend/uploads/
+backend/exports/
# Autres fichiers à ignorer
.DS_Store
diff --git a/backend/package-lock.json b/backend/package-lock.json
index e0327f1..995d024 100644
--- a/backend/package-lock.json
+++ b/backend/package-lock.json
@@ -22,9 +22,11 @@
"esbuild": "^0.25.2",
"esbuild-register": "^3.6.0",
"express": "^5.1.0",
+ "file-type": "^21.0.0",
"googleapis": "^148.0.0",
"handlebars": "^4.7.8",
"jsdom": "^26.1.0",
+ "json2csv": "^6.0.0-alpha.2",
"jsonwebtoken": "^9.0.2",
"multer": "^1.4.5-lts.2",
"nodemailer": "^6.10.1",
@@ -61,6 +63,16 @@
"lru-cache": "^10.4.3"
}
},
+ "node_modules/@borewit/text-codec": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/@borewit/text-codec/-/text-codec-0.1.1.tgz",
+ "integrity": "sha512-5L/uBxmjaCIX5h8Z+uu+kA9BQLkc/Wl06UGR5ajNRxu+/XjonB5i8JpgFMrPj3LXTCPA0pv8yxUvbUi+QthGGA==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/Borewit"
+ }
+ },
"node_modules/@cspotcode/source-map-support": {
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
@@ -1057,6 +1069,36 @@
"integrity": "sha512-VgffxawQde93xKxT3qap3OH+meZf7VaSB5Sqd4Rqc+FP5alWbpOyan/7tRbOAvynjpG3GpdtAuGU/NdhQpmrog==",
"license": "MIT"
},
+ "node_modules/@streamparser/json": {
+ "version": "0.0.6",
+ "resolved": "https://registry.npmjs.org/@streamparser/json/-/json-0.0.6.tgz",
+ "integrity": "sha512-vL9EVn/v+OhZ+Wcs6O4iKE9EUpwHUqHmCtNUMWjqp+6dr85+XPOSGTEsqYNq1Vn04uk9SWlOVmx9J48ggJVT2Q==",
+ "license": "MIT"
+ },
+ "node_modules/@tokenizer/inflate": {
+ "version": "0.2.7",
+ "resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.2.7.tgz",
+ "integrity": "sha512-MADQgmZT1eKjp06jpI2yozxaU9uVs4GzzgSL+uEq7bVcJ9V1ZXQkeGNql1fsSI0gMy1vhvNTNbUqrx+pZfJVmg==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.4.0",
+ "fflate": "^0.8.2",
+ "token-types": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/Borewit"
+ }
+ },
+ "node_modules/@tokenizer/token": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz",
+ "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==",
+ "license": "MIT"
+ },
"node_modules/@tsconfig/node10": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz",
@@ -1532,6 +1574,15 @@
"node": ">= 0.8"
}
},
+ "node_modules/commander": {
+ "version": "6.2.1",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz",
+ "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@@ -2562,6 +2613,30 @@
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
"license": "MIT"
},
+ "node_modules/fflate": {
+ "version": "0.8.2",
+ "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz",
+ "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==",
+ "license": "MIT"
+ },
+ "node_modules/file-type": {
+ "version": "21.0.0",
+ "resolved": "https://registry.npmjs.org/file-type/-/file-type-21.0.0.tgz",
+ "integrity": "sha512-ek5xNX2YBYlXhiUXui3D/BXa3LdqPmoLJ7rqEx2bKJ7EAUEfmXgW0Das7Dc6Nr9MvqaOnIqiPV0mZk/r/UpNAg==",
+ "license": "MIT",
+ "dependencies": {
+ "@tokenizer/inflate": "^0.2.7",
+ "strtok3": "^10.2.2",
+ "token-types": "^6.0.0",
+ "uint8array-extras": "^1.4.0"
+ },
+ "engines": {
+ "node": ">=20"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/file-type?sponsor=1"
+ }
+ },
"node_modules/fill-range": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
@@ -3060,6 +3135,26 @@
"node": ">=0.10.0"
}
},
+ "node_modules/ieee754": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
+ "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "BSD-3-Clause"
+ },
"node_modules/ignore-by-default": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz",
@@ -3224,6 +3319,24 @@
"bignumber.js": "^9.0.0"
}
},
+ "node_modules/json2csv": {
+ "version": "6.0.0-alpha.2",
+ "resolved": "https://registry.npmjs.org/json2csv/-/json2csv-6.0.0-alpha.2.tgz",
+ "integrity": "sha512-nJ3oP6QxN8z69IT1HmrJdfVxhU1kLTBVgMfRnNZc37YEY+jZ4nU27rBGxT4vaqM/KUCavLRhntmTuBFqZLBUcA==",
+ "license": "MIT",
+ "dependencies": {
+ "@streamparser/json": "^0.0.6",
+ "commander": "^6.2.0",
+ "lodash.get": "^4.4.2"
+ },
+ "bin": {
+ "json2csv": "bin/json2csv.js"
+ },
+ "engines": {
+ "node": ">= 12",
+ "npm": ">= 6.13.0"
+ }
+ },
"node_modules/jsonwebtoken": {
"version": "9.0.2",
"resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",
@@ -3288,6 +3401,13 @@
"safe-buffer": "^5.0.1"
}
},
+ "node_modules/lodash.get": {
+ "version": "4.4.2",
+ "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
+ "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==",
+ "deprecated": "This package is deprecated. Use the optional chaining (?.) operator instead.",
+ "license": "MIT"
+ },
"node_modules/lodash.includes": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
@@ -4398,6 +4518,22 @@
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
"license": "MIT"
},
+ "node_modules/strtok3": {
+ "version": "10.3.4",
+ "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-10.3.4.tgz",
+ "integrity": "sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg==",
+ "license": "MIT",
+ "dependencies": {
+ "@tokenizer/token": "^0.3.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/Borewit"
+ }
+ },
"node_modules/supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
@@ -4457,6 +4593,24 @@
"node": ">=0.6"
}
},
+ "node_modules/token-types": {
+ "version": "6.1.1",
+ "resolved": "https://registry.npmjs.org/token-types/-/token-types-6.1.1.tgz",
+ "integrity": "sha512-kh9LVIWH5CnL63Ipf0jhlBIy0UsrMj/NJDfpsy1SqOXlLKEVyXXYrnFxFT1yOOYVGBSApeVnjPw/sBz5BfEjAQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@borewit/text-codec": "^0.1.0",
+ "@tokenizer/token": "^0.3.0",
+ "ieee754": "^1.2.1"
+ },
+ "engines": {
+ "node": ">=14.16"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/Borewit"
+ }
+ },
"node_modules/touch": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz",
@@ -4602,6 +4756,18 @@
"node": ">=0.8.0"
}
},
+ "node_modules/uint8array-extras": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.4.1.tgz",
+ "integrity": "sha512-+NWHrac9dvilNgme+gP4YrBSumsaMZP0fNBtXXFIf33RLLKEcBUKaQZ7ULUbS0sBfcjxIZ4V96OTRkCbM7hxpw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/undefsafe": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz",
diff --git a/backend/package.json b/backend/package.json
index fa1aa0b..91fb766 100644
--- a/backend/package.json
+++ b/backend/package.json
@@ -31,9 +31,11 @@
"esbuild": "^0.25.2",
"esbuild-register": "^3.6.0",
"express": "^5.1.0",
+ "file-type": "^21.0.0",
"googleapis": "^148.0.0",
"handlebars": "^4.7.8",
"jsdom": "^26.1.0",
+ "json2csv": "^6.0.0-alpha.2",
"jsonwebtoken": "^9.0.2",
"multer": "^1.4.5-lts.2",
"nodemailer": "^6.10.1",
diff --git a/backend/server.ts b/backend/server.ts
index dbf65a4..0bea7a9 100644
--- a/backend/server.ts
+++ b/backend/server.ts
@@ -11,19 +11,19 @@ import userRoutes from './src/routes/user.routes';
import teamRoutes from './src/routes/team.routes';
import eventRoutes from './src/routes/event.routes';
import factionRoutes from './src/routes/faction.routes';
-import exportRoutes from './src/routes/export.routes';
+import imexportRouter from './src/routes/im_export.routes';
import permanenceRoutes from './src/routes/permanences.routes';
import challengeRoutes from './src/routes/challenge.routes';
import emailRoutes from './src/routes/email.routes';
import newsRoutes from './src/routes/news.routes';
import discordRoutes from './src/routes/discord.routes';
+import tentRoutes from './src/routes/tent.routes';
import { server_port } from './src/utils/secret';
import { initUser } from './src/database/initdb/initUser'
import { initRoles } from './src/database/initdb/initrole'
import {initEvent} from './src/database/initdb/initevent'
import {initChallenge} from './src/database/initdb/initChallenge'
import { authenticateUser } from './src/middlewares/auth.middleware';
-//import { initDB } from './src/database/init';
dotenv.config();
@@ -51,13 +51,17 @@ async function startServer() {
app.use('/api/team',authenticateUser, teamRoutes);
app.use('/api/event',authenticateUser, eventRoutes);
app.use('/api/faction',authenticateUser, factionRoutes);
- app.use('/api/export',authenticateUser, exportRoutes);
+ app.use('/api/imexport', authenticateUser, imexportRouter)
app.use('/api/permanence',authenticateUser, permanenceRoutes);
app.use('/api/challenge',authenticateUser, challengeRoutes);
app.use('/api/email',authenticateUser, emailRoutes);
app.use('/api/news',authenticateUser, newsRoutes);
app.use('/api/discord',authenticateUser, discordRoutes);
+ app.use('/api/tent',authenticateUser, tentRoutes);
app.use("/api/uploads/imgnews", express.static(path.join(__dirname, "/uploads/imgnews")));
+ app.use("/api/uploads/foodmenu", express.static(path.join(__dirname, "/uploads/foodmenu")));
+ app.use("/api/uploads/plannings", express.static(path.join(__dirname, "/uploads/plannings")));
+ app.use("/api/exports/bus", express.static(path.join(__dirname, "/exports/bus")));
// Démarrage du serveur
app.listen(server_port, () => {
diff --git a/backend/src/controllers/email.controller.ts b/backend/src/controllers/email.controller.ts
index 90dfc13..919d61e 100644
--- a/backend/src/controllers/email.controller.ts
+++ b/backend/src/controllers/email.controller.ts
@@ -17,16 +17,29 @@ export interface EmailOptions {
}
// Fonction pour générer l'HTML à partir du template
-export const generateEmailHtml = (templateName: string, data: any) => {
+export const generateEmailHtml = (templateName: string, data: any) => {
switch (templateName) {
case 'templateNotebook':
- return template.compileTemplate({ notebook: data.notebook }, template.templateNotebook);
+ return template.compileTemplate({ notebook: data.notebook }, template.templateNotebook);
+
case 'templateAttributionBus':
- return template.compileTemplate({ bus: data.bus, time: data.tim }, template.templateAttributionBus);
+ return template.compileTemplate({ bus: data.bus, time: data.time }, template.templateAttributionBus);
+
case 'templateWelcome':
return template.compileTemplate({ token: data.token }, template.templateWelcome);
+
case 'templateNotifyNews':
- return template.compileTemplate({title: data.title, description: data.description,}, template.templateNotifyNews);
+ return template.compileTemplate(
+ { title: data.title, description: data.description },
+ template.templateNotifyNews
+ );
+
+ case 'templateNotifyTentConfirmation':
+ return template.compileTemplate(
+ { user1: data.user1, user2: data.user2, confirmed: data.confirmed },
+ template.templateNotifyTentConfirmation
+ );
+
default:
return null;
}
diff --git a/backend/src/controllers/event.controller.ts b/backend/src/controllers/event.controller.ts
index b957d0b..72399f4 100644
--- a/backend/src/controllers/event.controller.ts
+++ b/backend/src/controllers/event.controller.ts
@@ -51,7 +51,17 @@ export const checkFoodStatus = async (req: Request, res: Response) => {
Ok(res, ({data: status?.food_open}));
}catch(error){
- Error(res, {msg :"Error while catching WEI status :" + error})
+ Error(res, {msg :"Error while catching Food status :" + error})
+ }
+};
+
+export const checkChallStatus = async (req: Request, res: Response) => {
+ try{
+ const status = await event_service.getEventsStatus();
+ Ok(res, ({data: status?.chall_open}));
+
+ }catch(error){
+ Error(res, {msg :"Error while catching Challenge status :" + error})
}
};
@@ -142,4 +152,15 @@ export const toggleFood = async (req: Request, res: Response) => {
} catch (error) {
Error(res,{ msg: "Erreur lors de la mise à jour." });
}
+};
+
+export const toggleChall = async (req: Request, res: Response) => {
+ const { challOpen } = req.body;
+
+ try {
+ const result = await event_service.updateChallStatus(challOpen);
+ Ok(res,{ msg: "Paramètres mis à jour.", data: result });
+ } catch (error) {
+ Error(res,{ msg: "Erreur lors de la mise à jour." });
+ }
};
\ No newline at end of file
diff --git a/backend/src/controllers/export.controller.ts b/backend/src/controllers/export.controller.ts
deleted file mode 100644
index 5e15cea..0000000
--- a/backend/src/controllers/export.controller.ts
+++ /dev/null
@@ -1,83 +0,0 @@
-import { Request, Response } from "express";
-import * as export_service from "../services/export.service";
-import * as user_service from '../services/user.service';
-import * as team_service from "../services/team.service";
-import * as permanence_service from "../services/permanence.service";
-import * as event_service from "../services/event.service";
-import { spreadsheet_id } from "../utils/secret";
-import { Ok, Error } from "../utils/responses";
-
-export const exportAllDataToSheets = async (req: Request, res: Response) => {
- try {
- // 1. Récupération depuis la DB
- const userList = await user_service.getUsersAll();
- const teamList = await team_service.getTeamsAll();
- const permanenceList = await permanence_service.getAllPermanencesWithUsers();
- const shotgunList = await event_service.getAllTeamShotguns();
-
-
- // 2. Mapping -> format pour Google Sheets (array de array)
- const usersValues = [
- ["ID", "Prénom", "Nom", "Email", "Branche", "Permission", "Majeur", "Contact","Discord", "Team", "Faction"],
- ...userList.map(u => [
- u.id ?? 0,
- u.first_name ?? "No first name",
- u.last_name ?? "No last name",
- u.email ?? "No email",
- u.branch ?? "No branch",
- u.permission ?? "No permissions",
- u.majeur ?? "Pas de données",
- u.contact ?? "No contact",
- u.discord_id ?? "No discord ID",
- u.teamName ?? "No Team",
- u.factionName ?? "No faction"
- ])
- ];
-
- const teamsValues = [
- ["ID", "Nom", "Type", "Faction"],
- ...teamList.map(t => [
- t.id,
- t.name ?? "No name",
- t.type ?? "No type",
- t.teamFaction?.name ?? "No faction"
- ])
- ];
-
- const permanenceValues = [
- ["ID", "Nom", "Début", "Fin", "Lieu", "Inscrits (noms)","Inscrits (emails)"],
- ...permanenceList.map(p => [
- p.id,
- p.name ?? "Sans nom",
- p.start_at?.toISOString() ?? "N/A",
- p.end_at?.toISOString() ?? "N/A",
- p.location ?? "Sans lieu",
- p.users.map(u => `${u.first_name} ${u.last_name}`).join(" ; ") || "Aucun inscrit",
- p.users.map(u => (u.email)).join(" ; ") || "Aucun inscrit"
- ])
- ];
-
-
- const shotgunValues = [
- ["ID", "Nom de l'équipe", "Type", "Horodatage"],
- ...shotgunList.map(s => [
- s.id,
- s.teamName ?? "No name",
- s.teamType ?? "No type",
- s.timestamp?.toISOString() ?? "No timestamp"
- ])
- ];
- // 3. Envoi vers les feuilles
- await export_service.writeToGoogleSheet(spreadsheet_id, "USER!A1", usersValues);
- await export_service.writeToGoogleSheet(spreadsheet_id, "TEAM!A1", teamsValues);
- await export_service.writeToGoogleSheet(spreadsheet_id, "PERMANENCES!A1", permanenceValues);
- await export_service.writeToGoogleSheet(spreadsheet_id, "SHOTGUN!A1", shotgunValues);
-
-
- Ok(res, { msg: "Export réalisé avec succès !" });
-
- } catch (error) {
- console.error(error);
- Error(res, { msg: "Erreur lors de l'export vers Google Sheets" });
- }
-};
diff --git a/backend/src/controllers/im_export.controller.ts b/backend/src/controllers/im_export.controller.ts
new file mode 100644
index 0000000..a220073
--- /dev/null
+++ b/backend/src/controllers/im_export.controller.ts
@@ -0,0 +1,168 @@
+import { Request, Response } from "express";
+import * as export_service from "../services/im_export.service";
+import * as user_service from '../services/user.service';
+import * as team_service from "../services/team.service";
+import * as permanence_service from "../services/permanence.service";
+import * as event_service from "../services/event.service";
+import { spreadsheet_id } from "../utils/secret";
+import { Ok, Error } from "../utils/responses";
+import path from "path";
+import fs from "fs";
+
+export const exportAllDataToSheets = async (req: Request, res: Response) => {
+ try {
+ // 1. Récupération depuis la DB
+ const userList = await user_service.getUsersAll();
+ const teamList = await team_service.getTeamsAll();
+ const permanenceList = await permanence_service.getAllPermanencesWithUsers();
+ const shotgunList = await event_service.getAllTeamShotguns();
+
+
+ // 2. Mapping -> format pour Google Sheets (array de array)
+ const usersValues = [
+ ["ID", "Prénom", "Nom", "Email", "Branche", "Permission", "Majeur", "Contact","Discord", "Team", "Faction"],
+ ...userList.map(u => [
+ u.id ?? 0,
+ u.first_name ?? "No first name",
+ u.last_name ?? "No last name",
+ u.email ?? "No email",
+ u.branch ?? "No branch",
+ u.permission ?? "No permissions",
+ u.majeur ?? "Pas de données",
+ u.contact ?? "No contact",
+ u.discord_id ?? "No discord ID",
+ u.teamName ?? "No Team",
+ u.factionName ?? "No faction"
+ ])
+ ];
+
+ const teamsValues = [
+ ["ID", "Nom", "Type", "Faction"],
+ ...teamList.map(t => [
+ t.id,
+ t.name ?? "No name",
+ t.type ?? "No type",
+ t.teamFaction?.name ?? "No faction"
+ ])
+ ];
+
+const permanenceValues = [
+ [
+ "ID",
+ "Nom",
+ "Début",
+ "Fin",
+ "Lieu",
+ "Responsables",
+ "Inscrits (noms)",
+ "Inscrits (emails)",
+ "Présents",
+ "Absents"
+ ],
+ ...permanenceList.map((p) => {
+ const respoNames = p.respo ? p.respo.firstName + " " + p.respo.lastName : "Aucun";
+ const userNames = p.users?.map((u) => `${u.first_name} ${u.last_name}`)?.join(" ; ") || "Aucun inscrit";
+ const userEmails = p.users?.map((u) => u.email)?.join(" ; ") || "Aucun inscrit";
+
+ const claimedUsers = p.users?.filter((u) => u.claimed)?.map((u) => `${u.first_name} ${u.last_name}`)?.join(" ; ") || "Aucun";
+ const unclaimedUsers = p.users?.filter((u) => !u.claimed)?.map((u) => `${u.first_name} ${u.last_name}`)?.join(" ; ") || "Aucun";
+
+ return [
+ p.id,
+ p.name ?? "Sans nom",
+ p.start_at ? new Date(p.start_at).toLocaleString("fr-FR") : "N/A",
+ p.end_at ? new Date(p.end_at).toLocaleString("fr-FR") : "N/A",
+ p.location ?? "Sans lieu",
+ respoNames,
+ userNames,
+ userEmails,
+ claimedUsers,
+ unclaimedUsers
+ ];
+ })
+];
+
+
+ const shotgunValues = [
+ ["ID", "Nom de l'équipe", "Type", "Horodatage"],
+ ...shotgunList.map(s => [
+ s.id,
+ s.teamName ?? "No name",
+ s.teamType ?? "No type",
+ s.timestamp?.toISOString() ?? "No timestamp"
+ ])
+ ];
+ // 3. Envoi vers les feuilles
+ await export_service.writeToGoogleSheet(spreadsheet_id, "USER!A1", usersValues);
+ await export_service.writeToGoogleSheet(spreadsheet_id, "TEAM!A1", teamsValues);
+ await export_service.writeToGoogleSheet(spreadsheet_id, "PERMANENCES!A1", permanenceValues);
+ await export_service.writeToGoogleSheet(spreadsheet_id, "SHOTGUN!A1", shotgunValues);
+
+
+ Ok(res, { msg: "Export réalisé avec succès !" });
+
+ } catch (error) {
+ console.error(error);
+ Error(res, { msg: "Erreur lors de l'export vers Google Sheets" });
+ }
+};
+
+
+export const updateFoodMenu = async (req: Request, res: Response) => {
+
+ const file = req.file;
+
+ try {
+
+ // Supprimer l'ancien Menu si un nouveau est uploadé
+ if (file) {
+ const targetDir = path.join(__dirname, "../../foodmenu");
+
+ if (fs.existsSync(targetDir)) {
+ fs.rmSync(targetDir, { recursive: true, force: true });
+ fs.mkdirSync(targetDir);
+ }
+ }
+
+ Ok(res, { msg: "Menu mis à jour avec succès" });
+ return;
+ } catch (err) {
+ console.error(err);
+ Error(res, { msg: "Erreur lors de la mise à jour du Menu" });
+ }
+};
+
+export const updatePlannings = async (req: Request, res: Response) => {
+
+ const file = req.file;
+
+ try {
+
+ // Supprimer l'ancien Planning si un nouveau est uploadé
+ if (file) {
+ const targetDir = path.join(__dirname, "../../plannings");
+
+ if (fs.existsSync(targetDir)) {
+ fs.rmSync(targetDir, { recursive: true, force: true });
+ fs.mkdirSync(targetDir);
+ }
+ }
+
+ Ok(res, { msg: "Planning mis à jour avec succès" });
+ return;
+ } catch (err) {
+ console.error(err);
+ Error(res, { msg: "Erreur lors de la mise à jour du Planning" });
+ }
+};
+
+
+export const exportUsersCSV = async (req: Request, res: Response) => {
+ try {
+ const filePath = await export_service.exportUsersToCSV();
+ Ok(res, {msg : 'CSV des bus généré'});
+ } catch (error) {
+ console.error(error);
+ Error(res, { msg: "Erreur lors de l'export CSV" });
+ }
+};
\ No newline at end of file
diff --git a/backend/src/controllers/permanence.controller.ts b/backend/src/controllers/permanence.controller.ts
index f1479f7..87f5336 100644
--- a/backend/src/controllers/permanence.controller.ts
+++ b/backend/src/controllers/permanence.controller.ts
@@ -24,9 +24,9 @@ const validatePermanenceData = (start_at: string, end_at: string) => {
// ➕ Créer une permanence
export const createPermanence = async (req: Request, res: Response) => {
- const { name, description, location, start_at, end_at, capacity } = req.body;
+ const { name, description, location, start_at, end_at, capacity, difficulty, respoId } = req.body;
- if (!name || !location || !start_at || !end_at || !capacity) {
+ if (!name || !location || !start_at || !end_at || !capacity || !difficulty) {
Error(res, { msg: "Tous les champs sont requis" });
return;
}
@@ -44,7 +44,9 @@ export const createPermanence = async (req: Request, res: Response) => {
location,
new Date(start_at),
new Date(end_at),
- Number(capacity)
+ Number(capacity),
+ Number(difficulty),
+ Number(respoId),
);
Ok(res, { msg: "Permanence créée avec succès" });
return;
@@ -56,9 +58,8 @@ 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 } = req.body;
-
- if (!name || !location || !start_at || !end_at || !capacity) {
+ const { permId, name, description, location, start_at, end_at, capacity, difficulty, respoId } = req.body;
+ if (!name || !location || !start_at || !end_at || !capacity || !difficulty) {
Error(res, { msg: "Tous les champs sont requis" });
return;
}
@@ -77,7 +78,9 @@ export const updatePermanence = async (req: Request, res: Response) => {
location,
new Date(start_at),
new Date(end_at),
- Number(capacity)
+ Number(capacity),
+ Number(difficulty),
+ Number(respoId)
);
Ok(res, { msg: "Permanence mis à jour avec succès" });
} catch (err) {
@@ -314,4 +317,61 @@ export const uploadPermanencesCSV = async (req: MulterRequest, res: Response) =>
}
};
+export const isUserRespo = async (req: Request, res: Response) => {
+ const { userId } = req.query;
+
+ if (!userId) {
+ Error(res, { msg: "userId est requis" });
+ return;
+ }
+
+ try {
+ const isRespo = await permanence_service.isUserRespoOfPermanence(
+ Number(userId)
+ );
+ Ok(res, { data: isRespo });
+ } catch (err) {
+ console.error(err);
+ Error(res, { msg: "Erreur lors de la vérification du responsable" });
+ }
+};
+
+export const getRespoPermanencesWithMembers = async (req: Request, res: Response) => {
+ const respoId = req.user?.userId;
+
+ if (!respoId) {
+ Error(res, { msg: "respoId est requis" });
+ return;
+ }
+
+ try {
+ const data = await permanence_service.getPermanenceDetailsForRespo(Number(respoId));
+ Ok(res, { data });
+ } catch (err) {
+ console.error(err);
+ Error(res, { msg: "Erreur lors de la récupération des permanences du responsable" });
+ }
+};
+
+export const claimMember = async (req: Request, res: Response) => {
+ const { userId, permId, claimed } = req.body;
+
+ if (userId === undefined || permId === undefined || claimed === undefined) {
+ Error(res, { msg: "userId, permId et claimed sont requis" });
+ return;
+ }
+
+ try {
+ await permanence_service.claimMember(Number(userId), Number(permId), Boolean(claimed));
+ Ok(res, {
+ msg: `Statut mis à jour avec succès (claimed = ${claimed})`,
+ });
+ } catch (err) {
+ console.error(err);
+ Error(res, { msg: "Erreur lors de la mise à jour du statut du membre" });
+ }
+};
+
+
+
diff --git a/backend/src/controllers/team.controller.ts b/backend/src/controllers/team.controller.ts
index 9adcb3c..06e8064 100644
--- a/backend/src/controllers/team.controller.ts
+++ b/backend/src/controllers/team.controller.ts
@@ -11,7 +11,7 @@ export const createNewTeam = async (req: Request, res: Response) => {
try {
- if(!teamName){
+ if (!teamName) {
Error(res, { msg: "Il n'y a pas de nom d'équipe" });
return;
}
@@ -20,11 +20,11 @@ export const createNewTeam = async (req: Request, res: Response) => {
Error(res, { msg: "L'enregistrement d'équipe est fermé." });
return;
}
- if(members.length < 4){
+ if (members.length < 4) {
Error(res, { msg: "Il n'y a pas assez de membre dans l'équipe." });
return;
}
- if(members.length > 5){
+ if (members.length > 5) {
Error(res, { msg: "Il y a trop de membre dans l'équipe." });
return;
}
@@ -52,8 +52,8 @@ export const createNewTeamLight = async (req: Request, res: Response) => {
const { teamName, factionId } = req.body;
try {
- const newTeamLigth = await team_service.createTeamLight(teamName,factionId);
- Ok(res, {msg: "Equipe créée !"});
+ const newTeamLigth = await team_service.createTeamLight(teamName, factionId);
+ Ok(res, { msg: "Equipe créée !" });
} catch (error) {
Error(res, { msg: "Erreur lors de la création de l'équipe." });
@@ -92,7 +92,7 @@ export const modifyTeam = async (req: Request, res: Response) => {
Error(res, { msg: "teamID est requis pour la mise à jour." });
}
- const updatedTeam = await team_service.modifyTeam(teamID, teamMembers, factionID, teamName, type );
+ const updatedTeam = await team_service.modifyTeam(teamID, teamMembers, factionID, teamName, type);
Ok(res, { data: updatedTeam });
} catch (error) {
@@ -103,16 +103,16 @@ export const modifyTeam = async (req: Request, res: Response) => {
export const getTeamUsers = async (req: Request, res: Response) => {
- const {teamId} = req.query;
+ const { teamId } = req.query;
try {
const teamUsers = await team_service.getTeamUsers(teamId);
- Ok(res,{ data: teamUsers });
+ Ok(res, { data: teamUsers });
return;
- } catch (error) {
+ } catch (error) {
console.error(error);
- Error(res,{ msg: "Erreur interne lors de la récupération des utilisateurs avec leurs rôles." });
+ Error(res, { msg: "Erreur interne lors de la récupération des utilisateurs avec leurs rôles." });
return;
-
+
}
}
@@ -121,28 +121,28 @@ export const getAllTeamsWithUsers = async (req: Request, res: Response) => {
try {
const teamUsers = await team_service.getAllTeamsWithUsers();
- Ok(res,{ data: teamUsers });
+ Ok(res, { data: teamUsers });
return;
- } catch (error) {
+ } catch (error) {
console.error(error);
- Error(res,{ msg: "Erreur interne lors de la récupération des utilisateurs avec leurs rôles." });
+ Error(res, { msg: "Erreur interne lors de la récupération des utilisateurs avec leurs rôles." });
return;
-
+
}
}
export const getTeamFaction = async (req: Request, res: Response) => {
- const {teamId} = req.query;
+ const { teamId } = req.query;
try {
const factionId = await team_service.getTeamFaction(teamId);
const teamFaction = await faction_service.getFaction(factionId);
- Ok(res,{ data: teamFaction });
+ Ok(res, { data: teamFaction });
return;
} catch (error) {
console.error(error);
- Error(res,{ msg: "Erreur interne lors de la récupération des utilisateurs avec leurs rôles." });
+ Error(res, { msg: "Erreur interne lors de la récupération des utilisateurs avec leurs rôles." });
return;
}
@@ -151,15 +151,15 @@ export const getTeamFaction = async (req: Request, res: Response) => {
export const deleteTeam = async (req: Request, res: Response) => {
try {
- const { teamID } = req.query; // Assumes the teamID is passed as a parameter
-
- if (!teamID) {
- Error(res, { msg: "teamID est requis." });
- }
-
+ const { teamID } = req.query; // Assumes the teamID is passed as a parameter
+
+ if (!teamID) {
+ Error(res, { msg: "teamID est requis." });
+ }
+
const deletedTeam = await team_service.deleteTeam(Number(teamID));
Ok(res, { msg: "Équipe supprimée avec succès.", data: deletedTeam });
-
+
} catch (error) {
console.error(error);
Error(res, { msg: "Erreur lors de la suppression de l'équipe." });
@@ -168,86 +168,88 @@ export const deleteTeam = async (req: Request, res: Response) => {
export const teamDistribution = async (req: Request, res: Response) => {
try {
-
+
const newStudents = await user_service.getUsersbyPermission("Nouveau");
- const userswithteams = (await team_service.getUsersWithTeam()).map((entry: any) => entry.userId);
+ const userswithteams = (await team_service.getUsersWithTeam()).map((entry: any) => entry.userId);
const teams = await team_service.getTeams();
- // Filtrer les étudiants qui ne sont pas dans la liste RI et qui ne sont pas déjà assignés à une équipe
- const filteredStudents = newStudents
- .filter((student: any) => student.branch !== "RI") //RI
- .filter((student : any) => !userswithteams.includes(student.userId));
+ // Filtrer les étudiants qui ne sont pas déjà assignés à une équipe
+ const filteredStudents = newStudents
+ // .filter((student: any) => student.branch !== "RI") // A decommenter pour ignorer les RI dans la répartition automatique
+ .filter((student: any) => !userswithteams.includes(student.userId));
// Filtrer les utilisateurs en fonction de la spécialité
const tcStudents = filteredStudents
- .filter((student: any) => student.branch === "TC")
- .map((student: any) => ({
- id: student.userId,
- email: student.email,
- branch: student.branch
- }));
-
+ .filter((student: any) => student.branch === "TC")
+ .map((student: any) => ({
+ id: student.userId,
+ email: student.email,
+ branch: student.branch
+ }));
+
const otherStudents = filteredStudents
- .filter((student: any) => student.branch !== "TC" && student.branch !== "RI" && student.branch !== "MM")
- .map((student: any) => ({
- id: student.userId,
- email: student.email,
- branch: student.branch
- }));
+ // .filter((student: any) => student.branch !== "TC" && student.branch !== "RI" && student.branch !== "MM") A decommenter pour ignorer les RI dans la répartition automatique
+ .filter((student: any) => student.branch !== "TC" && student.branch !== "MM")
+ .map((student: any) => ({
+ id: student.userId,
+ email: student.email,
+ branch: student.branch
+ }));
const PMOMStudents = filteredStudents
- .filter((student: any) => student.branch == "MM")
- .map((student: any) => ({
- id: student.userId,
- email: student.email,
- branch: student.branch
- }));
-
+ .filter((student: any) => student.branch == "MM")
+ .map((student: any) => ({
+ id: student.userId,
+ email: student.email,
+ branch: student.branch
+ }));
+
// Filtrer les équipes en fonction de leur type
const tcTeams = teams.filter(team => team.type === "TC");
const PMOMTeams = teams.filter(team => team.type === "MM");
- const otherTeams = teams.filter(team => team.type !== "TC" && team.type !== "RI" && team.type !== "MM");
-
+ // const otherTeams = teams.filter(team => team.type !== "TC" && team.type !== "RI" && team.type !== "MM"); A decommenter pour ignorer les RI dans la répartition automatique
+ const otherTeams = teams.filter(team => team.type !== "TC" && team.type !== "MM");
+
// Fonction pour assigner les utilisateurs à des équipes équilibrées
async function assignUsersToTeams(users: any, teams: any) {
- // Calculer la taille actuelle des équipes
- const teamSizes = await Promise.all(teams.map(async (team: any) => {
- const members = await team_service.getTeamUsers(team.teamId);
- return {
- teamId: team.teamId,
- size: members.length
- };
- }));
-
- // Trier les équipes par taille (ascendant)
- teamSizes.sort((a: any, b: any) => a.size - b.size);
-
- for (const user of users) {
- // Assigner l'utilisateur à l'équipe avec le moins de membres
- const smallestTeam = teamSizes[0];
- await team_service.addTeamMember(smallestTeam.teamId, user.id);
-
- // Mettre à jour la taille de l'équipe après l'ajout
- smallestTeam.size += 1;
-
- // Réordonner les équipes pour garder la plus petite en premier
+ // Calculer la taille actuelle des équipes
+ const teamSizes = await Promise.all(teams.map(async (team: any) => {
+ const members = await team_service.getTeamUsers(team.teamId);
+ return {
+ teamId: team.teamId,
+ size: members.length
+ };
+ }));
+
+ // Trier les équipes par taille (ascendant)
teamSizes.sort((a: any, b: any) => a.size - b.size);
- }
+
+ for (const user of users) {
+ // Assigner l'utilisateur à l'équipe avec le moins de membres
+ const smallestTeam = teamSizes[0];
+ await team_service.addTeamMember(smallestTeam.teamId, user.id);
+
+ // Mettre à jour la taille de l'équipe après l'ajout
+ smallestTeam.size += 1;
+
+ // Réordonner les équipes pour garder la plus petite en premier
+ teamSizes.sort((a: any, b: any) => a.size - b.size);
+ }
}
// Assigner les utilisateurs TC aux équipes TC
- if(tcStudents && tcTeams){
+ if (tcStudents && tcTeams) {
await assignUsersToTeams(tcStudents, tcTeams);
}
-
+
// Assigner les autres utilisateurs aux équipes non-TC
- if(otherStudents && otherTeams){
+ if (otherStudents && otherTeams) {
await assignUsersToTeams(otherStudents, otherTeams);
}
//Assigner les utilisateurs MM aux équipes MM
- if(PMOMStudents && PMOMTeams){
+ if (PMOMStudents && PMOMTeams) {
await assignUsersToTeams(PMOMStudents, PMOMTeams);
}
@@ -257,4 +259,4 @@ export const teamDistribution = async (req: Request, res: Response) => {
return
}
}
-
+
diff --git a/backend/src/controllers/tent.controller.ts b/backend/src/controllers/tent.controller.ts
new file mode 100644
index 0000000..b9dfb39
--- /dev/null
+++ b/backend/src/controllers/tent.controller.ts
@@ -0,0 +1,113 @@
+import { Request, Response } from "express";
+import * as tent_service from "../services/tent.service";
+import { Error, Ok } from "../utils/responses";
+import { sendEmail } from "../services/email.service";
+import { getUserById } from "../services/user.service";
+import { generateEmailHtml } from "./email.controller";
+
+export const createTent = async (req: Request, res: Response) => {
+ const { userId2 } = req.body;
+ const userId1 = req.user?.userId; // Créateur = utilisateur connecté
+
+ if (!userId1 || !userId2) {
+ return Error(res, { msg: "Identifiants utilisateurs manquants." });
+ }
+
+ try {
+ await tent_service.createTent(userId1, userId2);
+ Ok(res, { msg: "Tente réservée avec succès." });
+ } catch (err: any) {
+ Error(res, { msg: err.message || "Erreur lors de la création de la tente." });
+ }
+};
+
+export const cancelTent = async (req: Request, res: Response) => {
+
+ const userId1 = req.user?.userId;
+
+ if (!userId1) {
+ return Error(res, { msg: "Identifiants utilisateurs manquants." });
+ }
+
+ try {
+ await tent_service.cancelTent(userId1);
+ Ok(res, { msg: "Tente annulée." });
+ } catch (err) {
+ Error(res, { msg: "Erreur lors de l'annulation." });
+ }
+};
+
+export const getUserTent = async (req: Request, res: Response) => {
+ const userId = req.user?.userId;
+ if (!userId) return Error(res, { msg: "Utilisateur non authentifié." });
+
+ try {
+ const tent = await tent_service.getTentByUser(userId);
+ Ok(res, { data: tent });
+ } catch (err) {
+ Error(res, { msg: "Erreur lors de la récupération." });
+ }
+};
+
+export const getAllTentPairs = async (req: Request, res: Response) => {
+ try {
+ const tents = await tent_service.getAllTents();
+ Ok(res, { data: tents });
+ } catch (err) {
+ Error(res, { msg: "Erreur lors de la récupération des binômes." });
+ }
+};
+
+export const toggleTentConfirmation = async (req: Request, res: Response) => {
+
+ const { userId1, userId2, confirmed } = req.body;
+
+ if (!userId1 || !userId2 || typeof confirmed !== "boolean") {
+ return Error(res, { msg: "Paramètres manquants ou invalides." });
+ }
+
+ try {
+ // Mise à jour de la tente
+ await tent_service.toggleTentConfirmation(userId1, userId2, confirmed);
+
+ // Récupération des infos utilisateurs
+ const user1 = await getUserById(userId1);
+ const user2 = await getUserById(userId2);
+
+ if (!user1 || !user2) {
+ return Error(res, { msg: "Impossible de récupérer les utilisateurs." });
+ }
+
+ // Génération du contenu HTML
+ const htmlEmail = generateEmailHtml("templateNotifyTentConfirmation", {
+ user1: `${user1.firstName} ${user1.lastName}`,
+ user2: `${user2.firstName} ${user2.lastName}`,
+ confirmed,
+ });
+
+ // Options d’email
+ const emailOptions = {
+ from: "integration@utt.fr",
+ to: [user1.email, user2.email],
+ subject: confirmed
+ ? "🎉 Votre tente a été validée !"
+ : "⛺ Votre tente a été dévalidée",
+ text: "", // optionnel
+ html: htmlEmail,
+ };
+
+ // Envoi
+ await sendEmail(emailOptions);
+
+ Ok(res, {
+ msg: confirmed
+ ? "Tente validée et email envoyé."
+ : "Tente dévalidée et email envoyé.",
+ });
+ } catch (err: any) {
+ console.error(err);
+ Error(res, {
+ msg: err.message || "Erreur lors de la mise à jour ou de l'envoi d'email.",
+ });
+ }
+};
diff --git a/backend/src/controllers/user.controller.ts b/backend/src/controllers/user.controller.ts
index afdc535..6fc783a 100644
--- a/backend/src/controllers/user.controller.ts
+++ b/backend/src/controllers/user.controller.ts
@@ -5,6 +5,7 @@ import { Request, Response } from "express";
import bcrypt from 'bcryptjs';
import * as randomstring from 'randomstring';
import * as auth_service from "../services/auth.service"
+import { noSyncEmails } from '../utils/no_sync_list';
export const getUsersAdmin = async (req: Request, res: Response) => {
try {
@@ -53,9 +54,9 @@ export const syncNewstudent = async (req: Request, res: Response) => {
const token = await SIEP_Utils.getTokenUTTAPI();
const newStudents = await SIEP_Utils.getNewStudentsFromUTTAPI_NOPAGE(token, date);
- //const newStudentfiltered = newStudents.filter((student : any) => !noSyncEmails.includes(student.email));
+ const newStudentfiltered = newStudents.filter((student : any) => !noSyncEmails.includes(student.email));//Nouveau à ne pas sync (Démissionnaires, etc)
- for (const element of newStudents) {
+ for (const element of newStudentfiltered) {
let userInDb = await user_service.getUserByEmail(element.email.toLowerCase());
if(userInDb === undefined){
@@ -129,6 +130,3 @@ export const adminDeleteUser = async (req: Request, res: Response) => {
Error(res, { msg: "Erreur lors de la suppression de l'utilisateur." });
}
};
-
-
-
diff --git a/backend/src/database/db.ts b/backend/src/database/db.ts
index 2efdac7..5d63c0b 100644
--- a/backend/src/database/db.ts
+++ b/backend/src/database/db.ts
@@ -3,24 +3,46 @@ import { Client } from "pg";
import { postgres_user, postgres_db, postgres_host, postgres_password, postgres_port } from '../utils/secret';
// ✅ Import de tous tes schémas ici
-import * as user from '../schemas/Basic/user.schema';
-import * as team from '../schemas/Basic/team.schema';
-import * as perm from '../schemas/Basic/permanence.schema';
-import * as event from '../schemas/Basic/event.schema';
-import * as faction from '../schemas/Basic/faction.schema';
-import * as role from '../schemas/Basic/role.schema';
-import * as challenge from '../schemas/Basic/challenge.schema';
-import * as permanence from '../schemas/Basic/permanence.schema';
-
-import * as userTeam from '../schemas/Relational/userteams.schema';
-import * as teamFaction from '../schemas/Relational/teamfaction.schema';
-import * as teamShotgun from '../schemas/Relational/teamshotgun.schema';
-import * as userPermanence from '../schemas/Relational/userpermanences.schema';
-import * as userRole from '../schemas/Relational/userroles.schema';
-import * as challengValidation from '../schemas/Relational/challengevalidation.schema';
-import * as busattribution from "../schemas/Relational/busattribution.schema";
-import * as registration from "../schemas/Relational/registration.schema";
-
+import * as user from '../schemas/Basic/user.schema';
+import * as team from '../schemas/Basic/team.schema';
+import * as perm from '../schemas/Basic/permanence.schema';
+import * as event from '../schemas/Basic/event.schema';
+import * as faction from '../schemas/Basic/faction.schema';
+import * as role from '../schemas/Basic/role.schema';
+import * as challenge from '../schemas/Basic/challenge.schema';
+import * as permanence from '../schemas/Basic/permanence.schema';
+import * as userTeam from '../schemas/Relational/userteams.schema';
+import * as teamFaction from '../schemas/Relational/teamfaction.schema';
+import * as teamShotgun from '../schemas/Relational/teamshotgun.schema';
+import * as userPermanence from '../schemas/Relational/userpermanences.schema';
+import * as userRole from '../schemas/Relational/userroles.schema';
+import * as challengValidation from '../schemas/Relational/challengevalidation.schema';
+import * as busattribution from "../schemas/Relational/busattribution.schema";
+import * as registration from "../schemas/Relational/registration.schema";
+import * as tent from "../schemas/Relational/usertent.schema";
+import * as rolepoints from "../schemas/Relational/rolepoints.schema";
+
+
+const schema = {
+ ...user,
+ ...team,
+ ...perm,
+ ...event,
+ ...faction,
+ ...role,
+ ...challenge,
+ ...permanence,
+ ...userTeam,
+ ...teamFaction,
+ ...teamShotgun,
+ ...userPermanence,
+ ...userRole,
+ ...challengValidation,
+ ...busattribution,
+ ...registration,
+ ...tent,
+ ...rolepoints
+};
const client = new Client({
connectionString: `postgresql://${postgres_user}:${postgres_password}@${postgres_host}:${postgres_port}/${postgres_db}`,
@@ -28,25 +50,4 @@ const client = new Client({
client.connect();
-export const db = drizzle(client, {
- schema: {
- ...user,
- ...team,
- ...event,
- ...faction,
- ...role,
- ...perm,
- ...challenge,
- ...permanence,
-
- ...userTeam,
- ...teamFaction,
- ...teamShotgun,
- ...userPermanence,
- ...userRole,
- ...challengValidation,
- ...busattribution,
- ...registration
-
- },
-});
+export const db = drizzle(client, {schema: schema});
diff --git a/backend/src/database/initdb/initevent.ts b/backend/src/database/initdb/initevent.ts
index 96acc77..46164e8 100644
--- a/backend/src/database/initdb/initevent.ts
+++ b/backend/src/database/initdb/initevent.ts
@@ -6,6 +6,12 @@ export const initEvent = async () => {
// Si il n'y a pas de ligne existante, insérer une nouvelle ligne
if (existingEvent.length === 0) {
- await db.insert(eventSchema).values({ pre_registration_open: false, shotgun_open: false, sdi_open: false, wei_open: false, food_open : false }).onConflictDoNothing();
+ await db.insert(eventSchema).values({
+ pre_registration_open: false,
+ shotgun_open: false,
+ sdi_open: false,
+ wei_open: false,
+ food_open : false,
+ chall_open : false }).onConflictDoNothing();
}
};
\ No newline at end of file
diff --git a/backend/src/database/initdb/initrole.ts b/backend/src/database/initdb/initrole.ts
index 3bcdb7c..8c92ec4 100644
--- a/backend/src/database/initdb/initrole.ts
+++ b/backend/src/database/initdb/initrole.ts
@@ -11,6 +11,7 @@ const roles = [
{ name: "Argentique", description: "Couvrir les événements de l’intégration, prendre des photos" },
{ name: "Bouffe", description: "Prévoir, organiser et coordonner tous les repas de l’inté. La bouffe c’est sacré !" },
{ name: "Bar", description: "Prévoir, organiser et coordonner toutes les boissons de l’inté !" },
+ { name: "Bénévole", description: "Deviens bénévole et participe à différentes activités de l’inté !" },
{ name: "Cahier de vacances", description: "Élaborer le futur cahier de vacances des nouveaux avec des petits exercices et blagues." },
{ name: "Chasse au trésor", description: "Elaborer une chasse au trésor dans toute la capitale Troyenne." },
{ name: "Communication", description: "Préparer et gérer toute la communication de l’intégration" },
diff --git a/backend/src/database/migrations/0017_melted_meggan.sql b/backend/src/database/migrations/0017_melted_meggan.sql
new file mode 100644
index 0000000..06b4bc5
--- /dev/null
+++ b/backend/src/database/migrations/0017_melted_meggan.sql
@@ -0,0 +1 @@
+ALTER TABLE "events" ADD COLUMN "food_open" boolean DEFAULT false;
\ No newline at end of file
diff --git a/backend/src/database/migrations/0018_puzzling_arachne.sql b/backend/src/database/migrations/0018_puzzling_arachne.sql
new file mode 100644
index 0000000..c366235
--- /dev/null
+++ b/backend/src/database/migrations/0018_puzzling_arachne.sql
@@ -0,0 +1 @@
+ALTER TABLE "events" ADD COLUMN "chall_open" boolean DEFAULT false;
\ No newline at end of file
diff --git a/backend/src/database/migrations/0019_complete_moondragon.sql b/backend/src/database/migrations/0019_complete_moondragon.sql
new file mode 100644
index 0000000..8301a75
--- /dev/null
+++ b/backend/src/database/migrations/0019_complete_moondragon.sql
@@ -0,0 +1,2 @@
+ALTER TABLE "permanences" ADD COLUMN "difficulty" integer;--> statement-breakpoint
+ALTER TABLE "user_permanences" ADD COLUMN "claimed" boolean DEFAULT false;
\ No newline at end of file
diff --git a/backend/src/database/migrations/0020_strange_colonel_america.sql b/backend/src/database/migrations/0020_strange_colonel_america.sql
new file mode 100644
index 0000000..6551b54
--- /dev/null
+++ b/backend/src/database/migrations/0020_strange_colonel_america.sql
@@ -0,0 +1,8 @@
+CREATE TABLE "respo_permanences" (
+ "user_id" integer,
+ "permanence_id" integer,
+ CONSTRAINT "respo_permanences_user_id_permanence_id_pk" PRIMARY KEY("user_id","permanence_id")
+);
+--> statement-breakpoint
+ALTER TABLE "respo_permanences" ADD CONSTRAINT "respo_permanences_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "respo_permanences" ADD CONSTRAINT "respo_permanences_permanence_id_permanences_id_fk" FOREIGN KEY ("permanence_id") REFERENCES "public"."permanences"("id") ON DELETE cascade ON UPDATE no action;
\ No newline at end of file
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/0017_snapshot.json b/backend/src/database/migrations/meta/0017_snapshot.json
new file mode 100644
index 0000000..2ee6959
--- /dev/null
+++ b/backend/src/database/migrations/meta/0017_snapshot.json
@@ -0,0 +1,1124 @@
+{
+ "id": "81f84527-e2a4-45b5-9ba1-b3cf61c50ccd",
+ "prevId": "3fddc2ef-4b10-4316-ad1b-2a003214f9ef",
+ "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
+ }
+ },
+ "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
+ }
+ },
+ "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.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()"
+ }
+ },
+ "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
+ }
+ },
+ "enums": {},
+ "schemas": {},
+ "sequences": {},
+ "roles": {},
+ "policies": {},
+ "views": {},
+ "_meta": {
+ "columns": {},
+ "schemas": {},
+ "tables": {}
+ }
+}
\ No newline at end of file
diff --git a/backend/src/database/migrations/meta/0018_snapshot.json b/backend/src/database/migrations/meta/0018_snapshot.json
new file mode 100644
index 0000000..f0917ee
--- /dev/null
+++ b/backend/src/database/migrations/meta/0018_snapshot.json
@@ -0,0 +1,1131 @@
+{
+ "id": "4858ad67-d3ef-412a-850f-0b486d91795c",
+ "prevId": "81f84527-e2a4-45b5-9ba1-b3cf61c50ccd",
+ "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
+ }
+ },
+ "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.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()"
+ }
+ },
+ "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
+ }
+ },
+ "enums": {},
+ "schemas": {},
+ "sequences": {},
+ "roles": {},
+ "policies": {},
+ "views": {},
+ "_meta": {
+ "columns": {},
+ "schemas": {},
+ "tables": {}
+ }
+}
\ No newline at end of file
diff --git a/backend/src/database/migrations/meta/0019_snapshot.json b/backend/src/database/migrations/meta/0019_snapshot.json
new file mode 100644
index 0000000..adbcfb7
--- /dev/null
+++ b/backend/src/database/migrations/meta/0019_snapshot.json
@@ -0,0 +1,1144 @@
+{
+ "id": "e74fe262-0a2d-4bf3-9c11-62d66f3b7aa5",
+ "prevId": "4858ad67-d3ef-412a-850f-0b486d91795c",
+ "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.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
+ }
+ },
+ "enums": {},
+ "schemas": {},
+ "sequences": {},
+ "roles": {},
+ "policies": {},
+ "views": {},
+ "_meta": {
+ "columns": {},
+ "schemas": {},
+ "tables": {}
+ }
+}
\ No newline at end of file
diff --git a/backend/src/database/migrations/meta/0020_snapshot.json b/backend/src/database/migrations/meta/0020_snapshot.json
new file mode 100644
index 0000000..1fe21cb
--- /dev/null
+++ b/backend/src/database/migrations/meta/0020_snapshot.json
@@ -0,0 +1,1204 @@
+{
+ "id": "5570d860-0dbd-48fe-9fb5-5314cdb1a473",
+ "prevId": "e74fe262-0a2d-4bf3-9c11-62d66f3b7aa5",
+ "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
+ }
+ },
+ "enums": {},
+ "schemas": {},
+ "sequences": {},
+ "roles": {},
+ "policies": {},
+ "views": {},
+ "_meta": {
+ "columns": {},
+ "schemas": {},
+ "tables": {}
+ }
+}
\ 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 fc20490..71ffc61 100644
--- a/backend/src/database/migrations/meta/_journal.json
+++ b/backend/src/database/migrations/meta/_journal.json
@@ -120,6 +120,41 @@
"when": 1754903172897,
"tag": "0016_sudden_ultimatum",
"breakpoints": true
+ },
+ {
+ "idx": 17,
+ "version": "7",
+ "when": 1755637642205,
+ "tag": "0017_melted_meggan",
+ "breakpoints": true
+ },
+ {
+ "idx": 18,
+ "version": "7",
+ "when": 1755858317109,
+ "tag": "0018_puzzling_arachne",
+ "breakpoints": true
+ },
+ {
+ "idx": 19,
+ "version": "7",
+ "when": 1755906116757,
+ "tag": "0019_complete_moondragon",
+ "breakpoints": true
+ },
+ {
+ "idx": 20,
+ "version": "7",
+ "when": 1755907709198,
+ "tag": "0020_strange_colonel_america",
+ "breakpoints": true
+ },
+ {
+ "idx": 21,
+ "version": "7",
+ "when": 1756063134903,
+ "tag": "0021_colossal_madame_web",
+ "breakpoints": true
}
]
}
\ No newline at end of file
diff --git a/backend/src/middlewares/multer.middleware.ts b/backend/src/middlewares/multer.middleware.ts
index 7ad120f..184b4d4 100644
--- a/backend/src/middlewares/multer.middleware.ts
+++ b/backend/src/middlewares/multer.middleware.ts
@@ -1,45 +1,78 @@
import multer from "multer";
import path from "path";
-import fs from "fs";
-
-// Dossier de destination
-const uploadPath = path.join(__dirname, "../../uploads/imgnews");
-
-// Crée le dossier s’il n’existe pas
-if (!fs.existsSync(uploadPath)) {
- fs.mkdirSync(uploadPath, { recursive: true });
-}
-
-// Configuration du storage
-const storage = multer.diskStorage({
- destination: (req, file, cb) => {
- cb(null, uploadPath);
- },
- filename: (req, file, cb) => {
- const timestamp = Date.now();
- const ext = path.extname(file.originalname);
- const baseName = path.basename(file.originalname, ext);
- cb(null, `${baseName}-${timestamp}${ext}`);
- },
-});
-
-// Filtrer les types de fichiers (optionnel)
-const fileFilter = (req: any, file: Express.Multer.File, cb: multer.FileFilterCallback) => {
-
- if (file.mimetype.startsWith("image/")) {
- cb(null, true);
- } else {
- cb(new Error("Seules les images sont autorisées"));
- }
-};
+import fs from "fs/promises";
+import { Request, Response, NextFunction } from "express";
+import { fileTypeFromBuffer } from "file-type";
+import { Error } from "../utils/responses";
+
+export const createUploadMiddleware = (
+ relativeUploadDir: string,
+ modifiedName: boolean = true
+ ) => {
+ const uploadPath = path.resolve(process.cwd(), relativeUploadDir);
+
+ // On stocke d'abord en mémoire pour vérifier le type réel
+ const storage = multer.memoryStorage();
+
+ const multerUpload = multer({
+ storage,
+ limits: {
+ fileSize: 5 * 1024 * 1024, // 5 Mo
+ },
+ });
+
+ // Middleware custom pour vérifier et sauvegarder
+ const verifyAndSave = async (
+ req: Request,
+ res: Response,
+ next: NextFunction
+ ) => {
+ try {
+ if (!req.file) return Error(res, {msg: "Aucun fichier reçu"});
+
+ const user = (req as Request).user?.userId || "anonymous";
+ const { originalname, mimetype, buffer } = req.file;
+
+ console.log(
+ `[UPLOAD] User: ${user}, File: ${originalname}, Mimetype annoncé: ${mimetype}`
+ );
+
+ // Vérif du vrai type
+ const detected = await fileTypeFromBuffer(buffer);
+ console.log(
+ `[UPLOAD] Type détecté: ${detected?.mime || "inconnu"}`
+ );
-// Final middleware
-const upload = multer({
- storage,
- fileFilter,
- limits: {
- fileSize: 5 * 1024 * 1024, // 5 Mo
- },
-});
+ const isImage = detected?.mime?.startsWith("image/");
+ const isPDF = detected?.mime === "application/pdf";
-export default upload;
+ if (!isImage && !isPDF) {
+ return Error(res,{ msg:"Seules les images et les PDF sont autorisés"});
+ }
+
+ // Création dossier si nécessaire
+ await fs.mkdir(uploadPath, { recursive: true });
+
+ const ext = path.extname(originalname);
+ const baseName = path.basename(originalname, ext);
+ const timestamp = Date.now();
+ const finalName = modifiedName
+ ? `${baseName}-${timestamp}${ext}`
+ : originalname;
+
+ const finalPath = path.join(uploadPath, finalName);
+
+ // Sauvegarde du fichier sur disque
+ await fs.writeFile(finalPath, buffer);
+
+ // On rajoute le chemin pour les middlewares suivants
+ (req as any).savedFilePath = finalPath;
+
+ next();
+ } catch (err) {
+ next(err);
+ }
+ };
+
+ return { multerUpload, verifyAndSave };
+};
diff --git a/backend/src/middlewares/respoperm.middleware.ts b/backend/src/middlewares/respoperm.middleware.ts
new file mode 100644
index 0000000..c9b3915
--- /dev/null
+++ b/backend/src/middlewares/respoperm.middleware.ts
@@ -0,0 +1,29 @@
+import { Request, Response, NextFunction } from "express";
+import { Error } from "../utils/responses";
+import { isUserRespoOfPermanence } from "../services/permanence.service";
+
+export const isRespoMiddleware = async (
+ req: Request,
+ res: Response,
+ next: NextFunction
+) => {
+ const userId = req.user?.userId;
+
+ if (!userId) {
+ Error(res, { msg: "Utilisateur ou permanence non spécifié" });
+ return;
+ }
+
+ try {
+ const isRespo = await isUserRespoOfPermanence(userId);
+ if (!isRespo) {
+ Error(res, { msg: "Accès refusé : vous n'êtes pas responsable d'une permanence" });
+ return;
+ }
+
+ next(); // ✅ L'utilisateur est bien respo, on continue
+ } catch (err) {
+ console.error(err);
+ Error(res, { msg: "Erreur lors de la vérification du responsable" });
+ }
+};
\ No newline at end of file
diff --git a/backend/src/routes/event.routes.ts b/backend/src/routes/event.routes.ts
index d729ee0..62c60d4 100644
--- a/backend/src/routes/event.routes.ts
+++ b/backend/src/routes/event.routes.ts
@@ -11,6 +11,7 @@ eventRouter.get("/user/preregisterstatus",checkRole("Student",[]), eventControll
eventRouter.get("/user/sdistatus", eventController.checkSDIStatus);
eventRouter.get("/user/weistatus", eventController.checkWEIStatus);
eventRouter.get("/user/foodstatus", eventController.checkFoodStatus);
+eventRouter.get("/user/challstatus", eventController.checkChallStatus);
eventRouter.post("/user/shotgunattempt",checkRole("Student",[]), eventController.shotgunAttempt);
@@ -20,5 +21,6 @@ eventRouter.post("/admin/preregistrationtoggle",checkRole("Admin",[]), eventCont
eventRouter.post("/admin/sditoggle",checkRole("Admin",[]),eventController.toggleSDI);
eventRouter.post("/admin/weitoggle",checkRole("Admin",[]), eventController.toggleWEI);
eventRouter.post("/admin/foodtoggle",checkRole("Admin",[]), eventController.toggleFood);
+eventRouter.post("/admin/challtoggle",checkRole("Admin",[]), eventController.toggleChall);
export default eventRouter;
\ No newline at end of file
diff --git a/backend/src/routes/export.routes.ts b/backend/src/routes/export.routes.ts
deleted file mode 100644
index 6451ac5..0000000
--- a/backend/src/routes/export.routes.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-import express from 'express';
-import * as exportController from '../controllers/export.controller';
-import { checkRole } from '../middlewares/user.middleware';
-
-const exportRouter = express.Router();
-
-// Route d'inscription
-exportRouter.post('/admin/export',checkRole("Admin",[]), exportController.exportAllDataToSheets)
-
-
-
-export default exportRouter;
diff --git a/backend/src/routes/im_export.routes.ts b/backend/src/routes/im_export.routes.ts
new file mode 100644
index 0000000..42cec07
--- /dev/null
+++ b/backend/src/routes/im_export.routes.ts
@@ -0,0 +1,19 @@
+import express from 'express';
+import * as imexportController from '../controllers/im_export.controller';
+import { checkRole } from '../middlewares/user.middleware';
+import {createUploadMiddleware} from "../middlewares/multer.middleware";
+
+const uploadFoodMenu = createUploadMiddleware("uploads/foodmenu/", false);
+const uploadPlannings = createUploadMiddleware("uploads/plannings/", false);
+const imexportRouter = express.Router();
+
+
+imexportRouter.post('/admin/foodimport',checkRole("Admin",[]),uploadFoodMenu.multerUpload.single("foodFile"), uploadFoodMenu.verifyAndSave, imexportController.updateFoodMenu);
+imexportRouter.post('/admin/plannings',checkRole("Admin",[]),uploadPlannings.multerUpload.single("planningFile"), uploadPlannings.verifyAndSave, imexportController.updatePlannings);
+imexportRouter.post('/admin/exportgsheet',checkRole("Admin",[]), imexportController.exportAllDataToSheets);
+imexportRouter.get('/admin/exportbus',checkRole("Admin",[]), imexportController.exportUsersCSV);
+
+
+
+
+export default imexportRouter;
diff --git a/backend/src/routes/news.routes.ts b/backend/src/routes/news.routes.ts
index 35b3db4..a6cb339 100644
--- a/backend/src/routes/news.routes.ts
+++ b/backend/src/routes/news.routes.ts
@@ -1,16 +1,19 @@
import express from "express";
import * as newsController from "../controllers/news.controller";
import { checkRole } from "../middlewares/user.middleware";
-import upload from "../middlewares/multer.middleware";
+import { createUploadMiddleware } from "../middlewares/multer.middleware";
+const uploadImgNews = createUploadMiddleware("uploads/imgnews/", true);
const newsRouter = express.Router();
+
//Admin routes
-newsRouter.post("/admin/createnews",checkRole("Admin",["Communication"]), upload.single("file"), newsController.createNews);
+newsRouter.post("/admin/createnews",checkRole("Admin",["Communication"]), uploadImgNews.multerUpload.single("file"), uploadImgNews.verifyAndSave, newsController.createNews);
+newsRouter.post("/admin/updatenews", checkRole("Admin", ["Communication"]), uploadImgNews.multerUpload.single("file"), uploadImgNews.verifyAndSave, newsController.updateNews);
newsRouter.get("/admin/all",checkRole("Admin",["Communication"]), newsController.listAllNews);
newsRouter.post("/admin/publish",checkRole("Admin",["Communication"]) , newsController.publishNews);
newsRouter.delete("/admin/deletenews",checkRole("Admin",["Communication"]) ,newsController.deleteNews);
-newsRouter.post("/admin/updatenews", checkRole("Admin", ["Communication"]), upload.single("file"), newsController.updateNews);
+
//User routes
diff --git a/backend/src/routes/permanences.routes.ts b/backend/src/routes/permanences.routes.ts
index 6f167b1..a5d2f6d 100644
--- a/backend/src/routes/permanences.routes.ts
+++ b/backend/src/routes/permanences.routes.ts
@@ -2,6 +2,7 @@ import express from "express";
import multer from "multer";
import * as permanenceController from "../controllers/permanence.controller";
import { checkRole } from "../middlewares/user.middleware";
+import { isRespoMiddleware } from "../middlewares/respoperm.middleware";
const permanenceRouter = express.Router();
const upload = multer({ dest: "uploads/permcsv/" });
@@ -18,11 +19,16 @@ permanenceRouter.post("/admin/add", checkRole("Admin",[]), permanenceController.
permanenceRouter.post("/admin/remove", checkRole("Admin",[]), permanenceController.removeUserToPermanence);
permanenceRouter.post("/admin/importpermanences",checkRole("Admin",[]), upload.single("file"), permanenceController.uploadPermanencesCSV);
+//Respo de perm routes
+
+permanenceRouter.get("/respo/respodetails",isRespoMiddleware, permanenceController.getRespoPermanencesWithMembers);
+permanenceRouter.post("/respo/claimedmember",isRespoMiddleware, permanenceController.claimMember);
+
// Student routes
permanenceRouter.get("/user/permanences", checkRole("Student",[]), permanenceController.getOpenPermanences);
permanenceRouter.post("/user/apply", checkRole("Student",[]), permanenceController.applyToPermanence);
permanenceRouter.post("/user/leave", checkRole("Student",[]), permanenceController.leavePermanence);
permanenceRouter.get("/user/me", checkRole("Student",[]), permanenceController.getMyPermanences );
-
+permanenceRouter.get("/user/isrespo", permanenceController.isUserRespo);
export default permanenceRouter;
diff --git a/backend/src/routes/tent.routes.ts b/backend/src/routes/tent.routes.ts
new file mode 100644
index 0000000..de98ac2
--- /dev/null
+++ b/backend/src/routes/tent.routes.ts
@@ -0,0 +1,21 @@
+import express from 'express';
+import * as tentController from '../controllers/tent.controller';
+import { checkRole } from '../middlewares/user.middleware';
+
+const tentRouter = express.Router();
+
+// Admin routes
+tentRouter.get('/admin/tents', checkRole("Admin",[]), tentController.getAllTentPairs);
+tentRouter.post('/admin/toggleconfirmation', checkRole("Admin",[]), tentController.toggleTentConfirmation);
+
+
+// User routes
+tentRouter.post("/user/tent",checkRole("Nouveau",[]), tentController.createTent);
+tentRouter.delete("/user/tent",checkRole("Nouveau",[]), tentController.cancelTent);
+tentRouter.get("/user/tent",checkRole("Nouveau",[]), tentController.getUserTent);
+
+
+
+
+
+export default tentRouter;
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/schemas/Basic/event.schema.ts b/backend/src/schemas/Basic/event.schema.ts
index 759945e..a6b4764 100644
--- a/backend/src/schemas/Basic/event.schema.ts
+++ b/backend/src/schemas/Basic/event.schema.ts
@@ -6,7 +6,8 @@ export const eventSchema = pgTable("events", {
shotgun_open: boolean("shotgun_open").default(false),
sdi_open: boolean("sdi_open").default(false),
wei_open: boolean("wei_open").default(false),
- food_open: boolean("wei_open").default(false),
+ food_open: boolean("food_open").default(false),
+ chall_open: boolean("chall_open").default(false),
});
export type Event = typeof eventSchema.$inferSelect;
\ No newline at end of file
diff --git a/backend/src/schemas/Basic/permanence.schema.ts b/backend/src/schemas/Basic/permanence.schema.ts
index d720593..25cb42a 100644
--- a/backend/src/schemas/Basic/permanence.schema.ts
+++ b/backend/src/schemas/Basic/permanence.schema.ts
@@ -15,7 +15,8 @@ export const permanenceSchema = pgTable("permanences", {
start_at: timestamp("start_at"),
end_at: timestamp("end_at"),
capacity: integer("capacity"),
- is_open: boolean("is_open").default(false), // Géré par l'admin
+ is_open: boolean("is_open").default(false),
+ difficulty: integer("difficulty")
});
export type Permanence = typeof permanenceSchema.$inferSelect;
diff --git a/backend/src/schemas/Relational/userpermanences.schema.ts b/backend/src/schemas/Relational/userpermanences.schema.ts
index 2a94249..2cbfe55 100644
--- a/backend/src/schemas/Relational/userpermanences.schema.ts
+++ b/backend/src/schemas/Relational/userpermanences.schema.ts
@@ -1,4 +1,4 @@
-import { pgTable, integer, primaryKey, timestamp } from "drizzle-orm/pg-core";
+import { pgTable, integer, primaryKey, timestamp, boolean } from "drizzle-orm/pg-core";
import { userSchema } from "../Basic/user.schema";
import { permanenceSchema } from "../Basic/permanence.schema";
@@ -6,9 +6,20 @@ export const userPermanenceSchema = pgTable("user_permanences", {
user_id: integer("user_id").references(() => userSchema.id, { onDelete: "cascade" }),
permanence_id: integer("permanence_id").references(() => permanenceSchema.id, { onDelete: "cascade" }),
registered_at: timestamp("registered_at").defaultNow(),
+ claimed: boolean("claimed").default(false),
},
(table) => [
primaryKey({ columns: [table.user_id, table.permanence_id] }),
]);
export type UserPermanence = typeof userPermanenceSchema.$inferSelect;
+
+export const respoPermanenceSchema = pgTable("respo_permanences", {
+ user_id: integer("user_id").references(() => userSchema.id, { onDelete: "cascade" }),
+ permanence_id: integer("permanence_id").references(() => permanenceSchema.id, { onDelete: "cascade" }),
+},
+(table) => [
+ primaryKey({ columns: [table.user_id, table.permanence_id] }),
+]);
+
+export type RespoPermanence = typeof userPermanenceSchema.$inferSelect;
diff --git a/backend/src/schemas/Relational/usertent.schema.ts b/backend/src/schemas/Relational/usertent.schema.ts
new file mode 100644
index 0000000..6991e3e
--- /dev/null
+++ b/backend/src/schemas/Relational/usertent.schema.ts
@@ -0,0 +1,14 @@
+import { pgTable, serial, integer, primaryKey, boolean, timestamp } from "drizzle-orm/pg-core";
+import { userSchema } from "../Basic/user.schema";
+
+export const userTentSchema = pgTable("user_tent", {
+ user_id_1: integer("user_id_1").references(() => userSchema.id, { onDelete: "cascade" }),
+ user_id_2: integer("user_id_2").references(() => userSchema.id, { onDelete: "cascade" }),
+ confirmed: boolean("confirmed").default(false), // optionnel : pour savoir si les deux ont validé
+ created_at: timestamp("created_at").defaultNow(),
+}, (table) => [
+ primaryKey({ columns: [table.user_id_1, table.user_id_2] }),
+]);
+
+
+export type UserTent = typeof userTentSchema.$inferSelect;
\ No newline at end of file
diff --git a/backend/src/services/event.service.ts b/backend/src/services/event.service.ts
index 16e74da..b3003d6 100644
--- a/backend/src/services/event.service.ts
+++ b/backend/src/services/event.service.ts
@@ -72,4 +72,10 @@ export const updateFoodStatus = async ( foodOpen: boolean) => {
return await db.update(eventSchema)
.set({ food_open: foodOpen })
.returning();
+};
+
+export const updateChallStatus = async (challOpen: boolean) => {
+ return await db.update(eventSchema)
+ .set({ chall_open: challOpen })
+ .returning();
};
\ No newline at end of file
diff --git a/backend/src/services/export.service.ts b/backend/src/services/export.service.ts
deleted file mode 100644
index 1feacd1..0000000
--- a/backend/src/services/export.service.ts
+++ /dev/null
@@ -1,39 +0,0 @@
-// src/services/googleSheets.service.ts
-import { google } from 'googleapis';
-import { JWT } from 'google-auth-library';
-
-const path = require('path');
-const keyFilePath = path.resolve(__dirname, '../utils/google_credentials.json');
-
-// Crée une instance JWT en utilisant la clé du service account
-const jwtClient = new JWT({
- keyFile: keyFilePath,
- scopes: ['https://www.googleapis.com/auth/spreadsheets'],
-});
-
-// Fonction pour écrire dans Google Sheets
-export const writeToGoogleSheet = async (
- spreadsheetId: string,
- range: string,
- values: any[][]
-) => {
- try {
- // Crée un client Sheets en utilisant JWT pour l'authentification
- const sheets = google.sheets({ version: 'v4', auth: jwtClient });
-
- // Appel pour écrire les données dans la feuille
- await sheets.spreadsheets.values.update({
- spreadsheetId,
- range,
- valueInputOption: 'RAW',
- requestBody: {
- values,
- },
- });
-
- console.log(`Données envoyées à Google Sheets dans la plage ${range}`);
- } catch (error) {
- console.error('Erreur lors de l\'écriture dans Google Sheets:', error);
- throw error;
- }
-};
diff --git a/backend/src/services/im_export.service.ts b/backend/src/services/im_export.service.ts
new file mode 100644
index 0000000..44c9a62
--- /dev/null
+++ b/backend/src/services/im_export.service.ts
@@ -0,0 +1,87 @@
+// src/services/googleSheets.service.ts
+import { google } from 'googleapis';
+import { JWT } from 'google-auth-library';
+import { existsSync, mkdirSync, writeFileSync } from "fs";
+import { parse } from "json2csv";
+import * as user_service from './user.service';
+
+
+const path = require('path');
+const keyFilePath = path.resolve(__dirname, '../utils/google_credentials.json');
+
+// Crée une instance JWT en utilisant la clé du service account
+const jwtClient = new JWT({
+ keyFile: keyFilePath,
+ scopes: ['https://www.googleapis.com/auth/spreadsheets'],
+});
+
+// Fonction pour écrire dans Google Sheets
+export const writeToGoogleSheet = async (
+ spreadsheetId: string,
+ range: string,
+ values: any[][]
+) => {
+ try {
+ // Crée un client Sheets en utilisant JWT pour l'authentification
+ const sheets = google.sheets({ version: 'v4', auth: jwtClient });
+
+ // Appel pour écrire les données dans la feuille
+ await sheets.spreadsheets.values.update({
+ spreadsheetId,
+ range,
+ valueInputOption: 'RAW',
+ requestBody: {
+ values,
+ },
+ });
+
+ console.log(`Données envoyées à Google Sheets dans la plage ${range}`);
+ } catch (error) {
+ console.error('Erreur lors de l\'écriture dans Google Sheets:', error);
+ throw error;
+ }
+};
+
+
+export const exportUsersToCSV = async (): Promise => {
+ const users = await user_service.getUsersAll();
+
+ const formattedUsers = users.map(u => {
+ const isOrga = u.roles && u.roles.length > 0;
+ const isCE = u.permission === "Student" && u.teamId !== null;
+ const isBenevole = u.roles.some(role => role.roleName === "Bénévole");
+
+ return {
+ userId: u.id,
+ prenom: u.first_name ?? "",
+ nom: u.last_name ?? "",
+ mail: u.email ?? "",
+ telephone: u.contact ?? "",
+ nouveau: u.permission === "Nouveau",
+ ce: isCE,
+ num_equipe: u.teamId ?? null,
+ benevole: isBenevole,
+ orga: isOrga,
+ majeur: u.majeur ?? false,
+ };
+ });
+
+ const csv = parse(formattedUsers, {
+ fields: [
+ "id", "prenom", "nom", "mail", "telephone",
+ "nouveau", "ce", "num_equipe", "benevole", "orga", "majeur", "bus_manual"
+ ]
+ });
+
+ const exportDir = path.join(__dirname, "../../exports/bus");
+ const filePath = path.join(exportDir, "bus.csv");
+
+ if (!existsSync(exportDir)) {
+ mkdirSync(exportDir, { recursive: true });
+ }
+
+ writeFileSync(filePath, csv);
+
+ return filePath;
+
+};
diff --git a/backend/src/services/permanence.service.ts b/backend/src/services/permanence.service.ts
index ccde2b1..f98d1a1 100644
--- a/backend/src/services/permanence.service.ts
+++ b/backend/src/services/permanence.service.ts
@@ -1,19 +1,20 @@
import fs from "fs";
import Papa from "papaparse";
-import { and, count, eq, sql } from "drizzle-orm";
+import { and, count, eq, inArray, sql } from "drizzle-orm";
import { userSchema } from "../schemas/Basic/user.schema";
import { permanenceSchema } from "../schemas/Basic/permanence.schema";
import { db } from "../database/db";
-import { userPermanenceSchema } from "../schemas/Relational/userpermanences.schema";
+import { respoPermanenceSchema, userPermanenceSchema } from "../schemas/Relational/userpermanences.schema";
type CsvPermanence = {
name: string;
description: string;
location: string;
- start_at: string; // ISO string
+ start_at: string;
end_at: string;
capacity: string;
- is_open: string; // 'true' or 'false'
+ is_open: string;
+ difficulty: string;
};
// Classes d'erreurs personnalisées
@@ -146,26 +147,53 @@ export const createPermanence = async (
location: string,
start_at: Date,
end_at: Date,
- capacity: number
+ capacity: number,
+ difficulty: number,
+ respoId: number
) => {
- await db.insert(permanenceSchema).values({
- name,
- description,
- location,
- start_at,
- end_at,
- capacity,
- is_open: false, // pas ouverte à la création
- });
+ // Étape 1 : Création de la permanence
+ const [newPermanence] = await db
+ .insert(permanenceSchema)
+ .values({
+ name,
+ description,
+ location,
+ start_at,
+ end_at,
+ capacity,
+ is_open: false,
+ difficulty,
+ })
+ .returning({ id: permanenceSchema.id });
+
+ // Étape 2 : Ajout du responsable
+ if (newPermanence?.id && respoId) {
+ await db.insert(respoPermanenceSchema).values({
+ user_id: respoId,
+ permanence_id: newPermanence.id,
+ });
+ }
};
+
export const deletePermanence = async (permId: number) => {
+ // Étape 1 : Supprimer les inscriptions des utilisateurs
await db
.delete(userPermanenceSchema)
.where(eq(userPermanenceSchema.permanence_id, permId));
- await db.delete(permanenceSchema).where(eq(permanenceSchema.id, permId));
+
+ // Étape 2 : Supprimer les responsables associés
+ await db
+ .delete(respoPermanenceSchema)
+ .where(eq(respoPermanenceSchema.permanence_id, permId));
+
+ // Étape 3 : Supprimer la permanence
+ await db
+ .delete(permanenceSchema)
+ .where(eq(permanenceSchema.id, permId));
};
+
export const updatePermanence = async (
permId: number,
name: string,
@@ -173,8 +201,11 @@ export const updatePermanence = async (
location: string,
start_at: Date,
end_at: Date,
- capacity: number
+ capacity: number,
+ difficulty: number,
+ respoId: number
) => {
+ // Étape 1 : Mise à jour de la permanence
await db
.update(permanenceSchema)
.set({
@@ -184,11 +215,26 @@ export const updatePermanence = async (
start_at,
end_at,
capacity,
- is_open: false, // pas ouverte à la création
+ is_open: false,
+ difficulty,
})
.where(eq(permanenceSchema.id, permId));
+
+ // Étape 2 : Suppression des anciens responsables (si nécessaire)
+ await db
+ .delete(respoPermanenceSchema)
+ .where(eq(respoPermanenceSchema.permanence_id, permId));
+
+ // Étape 3 : Ajout du nouveau responsable
+ if(respoId){
+ await db.insert(respoPermanenceSchema).values({
+ user_id: respoId,
+ permanence_id: permId,
+ });
+ }
};
+
// Ouvrir une permanence (Admin action)
export const openPermanence = async (permId: number) => {
await db
@@ -197,7 +243,7 @@ export const openPermanence = async (permId: number) => {
.where(eq(permanenceSchema.id, permId));
};
-// Fermer une permanence
+// Fermer une permanence (Admin action)
export const closePermanence = async (permId: number) => {
await db
.update(permanenceSchema)
@@ -220,7 +266,7 @@ export const modifyPermCap = async (permId: number, factor: number) => {
// Voir ses permanences
export const getMyPermanences = async (userId: number) => {
- return await db
+ const userPerms = await db
.select({
id: permanenceSchema.id,
name: permanenceSchema.name,
@@ -234,24 +280,75 @@ export const getMyPermanences = async (userId: number) => {
eq(permanenceSchema.id, userPermanenceSchema.permanence_id)
)
.where(eq(userPermanenceSchema.user_id, userId));
+
+ // Ajout du responsables
+ const results = await Promise.all(
+ userPerms.map(async (perm) => {
+ const [respo] = await db
+ .select({
+ id: userSchema.id,
+ firstName: userSchema.first_name,
+ lastName: userSchema.last_name,
+ email: userSchema.email,
+ })
+ .from(respoPermanenceSchema)
+ .innerJoin(userSchema, eq(userSchema.id, respoPermanenceSchema.user_id))
+ .where(eq(respoPermanenceSchema.permanence_id, perm.id));
+
+ return {
+ ...perm,
+ respo: respo ?? null,
+ };
+ })
+ );
+
+
+ return results;
};
+
export const getAllPermanences = async () => {
- return await db.select().from(permanenceSchema);
+ const perms = await db.select().from(permanenceSchema);
+
+ const results = await Promise.all(
+ perms.map(async (perm) => {
+ const [respo] = await db
+ .select({
+ userId: userSchema.id,
+ firstName: userSchema.first_name,
+ lastName: userSchema.last_name,
+ email: userSchema.email,
+ })
+ .from(respoPermanenceSchema)
+ .innerJoin(userSchema, eq(userSchema.id, respoPermanenceSchema.user_id))
+ .where(eq(respoPermanenceSchema.permanence_id, perm.id));
+
+ return {
+ ...perm,
+ respo: respo ?? null,
+ };
+ })
+ );
+
+
+ return results;
};
+
export const getUsersInPermanence = async (permId: number) => {
return await db
.select({
userId: userSchema.id,
firstName: userSchema.first_name,
lastName: userSchema.last_name,
+ claimed: userPermanenceSchema.claimed,
})
.from(userPermanenceSchema)
.innerJoin(userSchema, eq(userSchema.id, userPermanenceSchema.user_id))
.where(eq(userPermanenceSchema.permanence_id, permId));
};
+
export const addUserToPermanence = async (userId: number, permId: number) => {
// Désinscrire l'utilisateur
await db.insert(userPermanenceSchema).values({
@@ -280,14 +377,18 @@ export const removeUserToPermanence = async (
export const getAllPermanencesWithUsers = async () => {
// Récupère toutes les permanences
- const permanences = await db.select().from(permanenceSchema);
+ const permanences = await getAllPermanences();
- // Pour chaque permanence, on récupère les users associés
+ // Pour chaque permanence, on récupère les users associés avec leur statut claimed
const results = await Promise.all(
permanences.map(async (permanence) => {
const userRelations = await db
.select({
- user: userSchema,
+ id: userSchema.id,
+ first_name: userSchema.first_name,
+ last_name: userSchema.last_name,
+ email: userSchema.email,
+ claimed: userPermanenceSchema.claimed,
})
.from(userPermanenceSchema)
.innerJoin(userSchema, eq(userSchema.id, userPermanenceSchema.user_id))
@@ -295,7 +396,7 @@ export const getAllPermanencesWithUsers = async () => {
return {
...permanence,
- users: userRelations.map((entry) => entry.user),
+ users: userRelations,
};
})
);
@@ -325,8 +426,78 @@ export const importPermanencesFromCSV = async (
start_at: new Date(r.start_at),
end_at: new Date(r.end_at),
capacity: parseInt(r.capacity, 10),
- is_open: r.is_open?.toLowerCase() === "true",
+ difficulty: r.difficulty,
+ is_open: false,
+
}));
await db.insert(permanenceSchema).values(parsedData);
};
+
+export const isUserRespoOfPermanence = async (
+ userId: number,
+): Promise => {
+ const respo = await db
+ .select()
+ .from(respoPermanenceSchema)
+ .where(
+ eq(respoPermanenceSchema.user_id, userId)
+ );
+
+ return respo.length > 0;
+};
+
+export const getPermanenceDetailsForRespo = async (respoId: number) => {
+ // Étape 1 : Trouver les permanences dont il est respo
+ const respos = await db
+ .select()
+ .from(respoPermanenceSchema)
+ .where(eq(respoPermanenceSchema.user_id, respoId));
+
+ const permanenceIds = respos.map((r) => r.permanence_id);
+ if (permanenceIds.length === 0) throw new Error("Pas de permanences");;
+
+ // Étape 2 : Récupérer les permanences
+ const permanences = await db
+ .select()
+ .from(permanenceSchema)
+ .where(inArray(permanenceSchema.id, permanenceIds));
+
+ // Étape 3 : Récupérer les membres avec infos utiles
+ const results = await Promise.all(
+ permanences.map(async (perm) => {
+ const members = await db
+ .select({
+ id: userSchema.id,
+ first_name: userSchema.first_name,
+ last_name: userSchema.last_name,
+ email: userSchema.email,
+ claimed: userPermanenceSchema.claimed,
+ })
+ .from(userPermanenceSchema)
+ .innerJoin(userSchema, eq(userSchema.id, userPermanenceSchema.user_id))
+ .where(eq(userPermanenceSchema.permanence_id, perm.id));
+
+ return {
+ permanence: perm,
+ members,
+ };
+ })
+ );
+
+ return results;
+};
+
+export const claimMember = async ( userId: number, permId: number, claimed: boolean ) => {
+ await db
+ .update(userPermanenceSchema)
+ .set({ claimed })
+ .where(
+ and(
+ eq(userPermanenceSchema.user_id, userId),
+ eq(userPermanenceSchema.permanence_id, permId)
+ )
+ );
+};
+
+
diff --git a/backend/src/services/tent.service.ts b/backend/src/services/tent.service.ts
new file mode 100644
index 0000000..bab65c0
--- /dev/null
+++ b/backend/src/services/tent.service.ts
@@ -0,0 +1,136 @@
+import { db } from "../database/db";
+import { and, eq, or } from "drizzle-orm";
+import { userTentSchema } from "../schemas/Relational/usertent.schema";
+import { userSchema } from "../schemas/Basic/user.schema";
+import { alias } from "drizzle-orm/pg-core";
+
+/**
+ * Créer une réservation de tente entre 2 utilisateurs.
+ */
+export const createTent = async (userId1: number, userId2: number) => {
+ if (userId1 === userId2) {
+ throw new Error("Impossible de réserver une tente avec soi-même.");
+ }
+
+ // Vérifier si l'un des deux a déjà une tente
+ const existing = await db
+ .select()
+ .from(userTentSchema)
+ .where(
+ or(
+ eq(userTentSchema.user_id_1, userId1),
+ eq(userTentSchema.user_id_2, userId1),
+ eq(userTentSchema.user_id_1, userId2),
+ eq(userTentSchema.user_id_2, userId2)
+ )
+ );
+
+ if (existing.length > 0) {
+ throw new Error("Un des utilisateurs a déjà une tente.");
+ }
+
+ return await db.insert(userTentSchema).values({
+ user_id_1: userId1,
+ user_id_2: userId2,
+ });
+};
+
+/**
+ * Annuler une tente (par l'un ou l'autre des utilisateurs).
+ */
+export const cancelTent = async (userId1: number) => {
+
+ return await db
+ .delete(userTentSchema)
+ .where(
+ or(eq(userTentSchema.user_id_1, userId1), eq(userTentSchema.user_id_2, userId1)),
+ );
+};
+
+/**
+ * Récupérer la tente d’un utilisateur.
+ */
+export const getTentByUser = async (userId: number) => {
+ return await db
+ .select()
+ .from(userTentSchema)
+ .where(or(eq(userTentSchema.user_id_1, userId), eq(userTentSchema.user_id_2, userId)));
+};
+
+/**
+ * Récupérer toutes les tentes (avec infos des 2 utilisateurs).
+ */
+export const getAllTents = async () => {
+
+ const user2 = alias(userSchema, "user2");
+
+ return await db
+ .select({
+ user1_id: userTentSchema.user_id_1,
+ user2_id: userTentSchema.user_id_2,
+ user1_first_name: userSchema.first_name,
+ user1_last_name: userSchema.last_name,
+ user1_email: userSchema.email,
+ user2_first_name: user2.first_name,
+ user2_last_name: user2.last_name,
+ user2_email: user2.email,
+ confirmed: userTentSchema.confirmed
+ })
+ .from(userTentSchema)
+ .innerJoin(userSchema, eq(userTentSchema.user_id_1, userSchema.id))
+ .innerJoin(user2, eq(userTentSchema.user_id_2, user2.id));
+};
+
+/**
+ * Met à jour la confirmation(avec infos des 2 utilisateurs).
+ */
+export const toggleTentConfirmation = async (
+ userId1: number,
+ userId2: number,
+ confirmed: boolean
+) => {
+ if (userId1 === userId2) {
+ throw new Error("Les deux utilisateurs doivent être différents.");
+ }
+
+ // Vérifier si la tente existe
+ const existingTent = await db
+ .select()
+ .from(userTentSchema)
+ .where(
+ or(
+ and(
+ eq(userTentSchema.user_id_1, userId1),
+ eq(userTentSchema.user_id_2, userId2)
+ ),
+ and(
+ eq(userTentSchema.user_id_1, userId2),
+ eq(userTentSchema.user_id_2, userId1)
+ )
+ )
+ );
+
+ if (existingTent.length === 0) {
+ throw new Error("La tente entre ces deux utilisateurs n'existe pas.");
+ }
+
+ // Mettre à jour la confirmation
+ await db
+ .update(userTentSchema)
+ .set({ confirmed })
+ .where(
+ or(
+ and(
+ eq(userTentSchema.user_id_1, userId1),
+ eq(userTentSchema.user_id_2, userId2)
+ ),
+ and(
+ eq(userTentSchema.user_id_1, userId2),
+ eq(userTentSchema.user_id_2, userId1)
+ )
+ )
+ );
+
+ return { success: true, message: confirmed ? "Tente validée." : "Tente dévalidée." };
+};
+
diff --git a/backend/src/services/user.service.ts b/backend/src/services/user.service.ts
index 92b0b23..741783f 100644
--- a/backend/src/services/user.service.ts
+++ b/backend/src/services/user.service.ts
@@ -5,6 +5,9 @@ import { eq } from 'drizzle-orm';
import { userTeamsSchema } from '../schemas/Relational/userteams.schema';
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) => {
@@ -121,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;
@@ -134,19 +138,36 @@ export const getUsersAll = async () => {
try {
const users = await db.select().from(userSchema);
- const userWithTeam = await Promise.all(
- users.map(async (user) => {
- const teamId = await getUserTeam(user.id);
- const teamName = (await getTeam(teamId))?.teamName;
- const factionId = await getTeamFaction(teamId);
- const factionName = (await getFaction(factionId))?.name;
- return {
- ...user,
- teamName,
- factionName
- };
- })
- );
+ const userWithTeam = await Promise.all(
+ users.map(async (user) => {
+ const roles = await getUserRoles(user.id);
+ let teamId = await getUserTeam(user.id);
+ teamId= teamId ?? null;
+
+ let teamName: string | null = null;
+ let factionId: number | null = null;
+ let factionName: string | null = null;
+
+ if (teamId) {
+ const team = await getTeam(teamId);
+ teamName = team?.teamName ?? null;
+
+ factionId = await getTeamFaction(teamId);
+ const faction = await getFaction(factionId);
+ factionName = faction?.name ?? null;
+ }
+
+ return {
+ ...user,
+ teamId,
+ teamName,
+ factionId,
+ factionName,
+ roles,
+ };
+ })
+ );
+
return userWithTeam;
} catch (err) {
@@ -232,6 +253,13 @@ export const updateUserByAdmin = async (
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));
+
+ if(user_registration_token.length > 0){
+ await db.delete(registrationSchema).where(eq(registrationSchema.user_id, userId));
+ }
+
const result = await db.delete(userSchema).where(eq(userSchema.id, userId));
return result;
} catch (err) {
diff --git a/backend/src/utils/emailtemplates.ts b/backend/src/utils/emailtemplates.ts
index fc756ee..63eecef 100644
--- a/backend/src/utils/emailtemplates.ts
+++ b/backend/src/utils/emailtemplates.ts
@@ -345,6 +345,37 @@ export const templateNotifyNews = `
`;
+export const templateNotifyTentConfirmation = `
+
+
+
+
+
+
+
Intégration UTT
+
+
+
+
⛺ Mise à jour de ta tente
+
+ La tente entre {{user1}} et {{user2}} a été
+
+ {{#if confirmed}}validée{{else}}dévalidée{{/if}}
+ .
+
+
+
+ 👉 Tu peux consulter l’état de ta tente sur le site de l'inté dans l’onglet Tentes .
+
+
+
+
+ Accéder au site
+
+
+
+`;
+
// Fonction pour compiler le template
diff --git a/backend/src/utils/no_sync_list.ts b/backend/src/utils/no_sync_list.ts
new file mode 100644
index 0000000..46cd864
--- /dev/null
+++ b/backend/src/utils/no_sync_list.ts
@@ -0,0 +1,4 @@
+//This list is to ignore user from UTT API who already have an account on the website but not with the same email address
+// OR People we don't want to sync like "BIO REF" or demissionary
+export const noSyncEmails = [
+];
\ No newline at end of file
diff --git a/frontend/Dockerfile b/frontend/Dockerfile
index 87aa4ca..9c29e08 100644
--- a/frontend/Dockerfile
+++ b/frontend/Dockerfile
@@ -14,7 +14,7 @@ ENV VITE_ANALYTICS_WEBSITE_ID=${VITE_ANALYTICS_WEBSITE_ID}
COPY package.json package-lock.json ./
RUN npm install -g npm@latest
-RUN npm install --force
+RUN npm install
COPY . .
RUN npm run build
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index ad9faed..b7c3d0a 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -9,26 +9,29 @@
"version": "1.0.0",
"dependencies": {
"@headlessui/react": "^2.2.7",
+ "@radix-ui/react-accordion": "^1.2.12",
"@radix-ui/react-checkbox": "^1.1.5",
"@radix-ui/react-label": "^2.1.3",
"@radix-ui/react-popover": "^1.1.7",
"@radix-ui/react-select": "^2.1.7",
"@radix-ui/react-slot": "^1.2.0",
"@tailwindcss/vite": "^4.1.3",
+ "@tanstack/react-query": "^5.85.5",
"axios": "^1.8.4",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"date-fns": "^3.6.0",
- "framer-motion": "^12.7.4",
+ "framer-motion": "^12.23.12",
"jwt-decode": "^4.0.0",
"lucide-react": "^0.488.0",
- "react": "^19.1.1",
+ "react": "^19.1.0",
"react-day-picker": "^8.10.1",
- "react-dom": "^19.1.1",
+ "react-dom": "^19.0.0",
"react-hook-form": "^7.55.0",
"react-icons": "^5.5.0",
"react-router-dom": "^7.5.0",
"react-select": "^5.10.1",
+ "shadcn": "^2.10.0",
"sweetalert2": "^11.6.13",
"swiper": "^11.2.6",
"tailwind-merge": "^3.2.0",
@@ -37,6 +40,7 @@
},
"devDependencies": {
"@eslint/js": "^9.21.0",
+ "@heroicons/react": "^2.2.0",
"@types/node": "^22.14.1",
"@types/react": "^19.0.10",
"@types/react-dom": "^19.0.4",
@@ -63,6 +67,21 @@
"node": ">=6.0.0"
}
},
+ "node_modules/@antfu/ni": {
+ "version": "23.3.1",
+ "resolved": "https://registry.npmjs.org/@antfu/ni/-/ni-23.3.1.tgz",
+ "integrity": "sha512-C90iyzm/jLV7Lomv2UzwWUzRv9WZr1oRsFRKsX5HjQL4EXrbi9H/RtBkjCP+NF+ABZXUKpAa4F1dkoTaea4zHg==",
+ "license": "MIT",
+ "bin": {
+ "na": "bin/na.mjs",
+ "nci": "bin/nci.mjs",
+ "ni": "bin/ni.mjs",
+ "nlx": "bin/nlx.mjs",
+ "nr": "bin/nr.mjs",
+ "nu": "bin/nu.mjs",
+ "nun": "bin/nun.mjs"
+ }
+ },
"node_modules/@babel/code-frame": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
@@ -81,7 +100,6 @@
"version": "7.28.0",
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.0.tgz",
"integrity": "sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=6.9.0"
@@ -91,7 +109,6 @@
"version": "7.28.0",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.0.tgz",
"integrity": "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@ampproject/remapping": "^2.2.0",
@@ -119,13 +136,13 @@
}
},
"node_modules/@babel/generator": {
- "version": "7.28.0",
- "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.0.tgz",
- "integrity": "sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg==",
+ "version": "7.28.3",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz",
+ "integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==",
"license": "MIT",
"dependencies": {
- "@babel/parser": "^7.28.0",
- "@babel/types": "^7.28.0",
+ "@babel/parser": "^7.28.3",
+ "@babel/types": "^7.28.2",
"@jridgewell/gen-mapping": "^0.3.12",
"@jridgewell/trace-mapping": "^0.3.28",
"jsesc": "^3.0.2"
@@ -134,11 +151,22 @@
"node": ">=6.9.0"
}
},
+ "node_modules/@babel/helper-annotate-as-pure": {
+ "version": "7.27.3",
+ "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz",
+ "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.27.3"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
"node_modules/@babel/helper-compilation-targets": {
"version": "7.27.2",
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz",
"integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@babel/compat-data": "^7.27.2",
@@ -151,6 +179,27 @@
"node": ">=6.9.0"
}
},
+ "node_modules/@babel/helper-create-class-features-plugin": {
+ "version": "7.28.3",
+ "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.3.tgz",
+ "integrity": "sha512-V9f6ZFIYSLNEbuGA/92uOvYsGCJNsuA8ESZ4ldc09bWk/j8H8TKiPw8Mk1eG6olpnO0ALHJmYfZvF4MEE4gajg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-annotate-as-pure": "^7.27.3",
+ "@babel/helper-member-expression-to-functions": "^7.27.1",
+ "@babel/helper-optimise-call-expression": "^7.27.1",
+ "@babel/helper-replace-supers": "^7.27.1",
+ "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1",
+ "@babel/traverse": "^7.28.3",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
"node_modules/@babel/helper-globals": {
"version": "7.28.0",
"resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
@@ -160,6 +209,19 @@
"node": ">=6.9.0"
}
},
+ "node_modules/@babel/helper-member-expression-to-functions": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.27.1.tgz",
+ "integrity": "sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/traverse": "^7.27.1",
+ "@babel/types": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
"node_modules/@babel/helper-module-imports": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz",
@@ -177,7 +239,6 @@
"version": "7.27.3",
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz",
"integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-module-imports": "^7.27.1",
@@ -191,16 +252,57 @@
"@babel/core": "^7.0.0"
}
},
+ "node_modules/@babel/helper-optimise-call-expression": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz",
+ "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
"node_modules/@babel/helper-plugin-utils": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz",
"integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
+ "node_modules/@babel/helper-replace-supers": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.27.1.tgz",
+ "integrity": "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-member-expression-to-functions": "^7.27.1",
+ "@babel/helper-optimise-call-expression": "^7.27.1",
+ "@babel/traverse": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-skip-transparent-expression-wrappers": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz",
+ "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/traverse": "^7.27.1",
+ "@babel/types": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
"node_modules/@babel/helper-string-parser": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
@@ -223,7 +325,6 @@
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz",
"integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=6.9.0"
@@ -233,7 +334,6 @@
"version": "7.27.6",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.6.tgz",
"integrity": "sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@babel/template": "^7.27.2",
@@ -244,12 +344,12 @@
}
},
"node_modules/@babel/parser": {
- "version": "7.28.0",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.0.tgz",
- "integrity": "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==",
+ "version": "7.28.3",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.3.tgz",
+ "integrity": "sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA==",
"license": "MIT",
"dependencies": {
- "@babel/types": "^7.28.0"
+ "@babel/types": "^7.28.2"
},
"bin": {
"parser": "bin/babel-parser.js"
@@ -258,6 +358,21 @@
"node": ">=6.0.0"
}
},
+ "node_modules/@babel/plugin-syntax-typescript": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz",
+ "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
"node_modules/@babel/plugin-transform-react-jsx-self": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz",
@@ -290,6 +405,25 @@
"@babel/core": "^7.0.0-0"
}
},
+ "node_modules/@babel/plugin-transform-typescript": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.0.tgz",
+ "integrity": "sha512-4AEiDEBPIZvLQaWlc9liCavE0xRM0dNca41WtBeM3jgFptfUOSG9z0uteLhq6+3rq+WB6jIvUwKDTpXEHPJ2Vg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-annotate-as-pure": "^7.27.3",
+ "@babel/helper-create-class-features-plugin": "^7.27.1",
+ "@babel/helper-plugin-utils": "^7.27.1",
+ "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1",
+ "@babel/plugin-syntax-typescript": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
"node_modules/@babel/runtime": {
"version": "7.27.6",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.6.tgz",
@@ -314,17 +448,17 @@
}
},
"node_modules/@babel/traverse": {
- "version": "7.28.0",
- "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.0.tgz",
- "integrity": "sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg==",
+ "version": "7.28.3",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.3.tgz",
+ "integrity": "sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ==",
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.27.1",
- "@babel/generator": "^7.28.0",
+ "@babel/generator": "^7.28.3",
"@babel/helper-globals": "^7.28.0",
- "@babel/parser": "^7.28.0",
+ "@babel/parser": "^7.28.3",
"@babel/template": "^7.27.2",
- "@babel/types": "^7.28.0",
+ "@babel/types": "^7.28.2",
"debug": "^4.3.1"
},
"engines": {
@@ -332,9 +466,9 @@
}
},
"node_modules/@babel/types": {
- "version": "7.28.1",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.1.tgz",
- "integrity": "sha512-x0LvFTekgSX+83TI28Y9wYPUfzrnl2aT5+5QLnO6v7mSJYtEEevuDRN0F0uSHRk1G1IWZC43o00Y0xDDrpBGPQ==",
+ "version": "7.28.2",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz",
+ "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==",
"license": "MIT",
"dependencies": {
"@babel/helper-string-parser": "^7.27.1",
@@ -344,6 +478,43 @@
"node": ">=6.9.0"
}
},
+ "node_modules/@bundled-es-modules/cookie": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/@bundled-es-modules/cookie/-/cookie-2.0.1.tgz",
+ "integrity": "sha512-8o+5fRPLNbjbdGRRmJj3h6Hh1AQJf2dk3qQ/5ZFb+PXkRNiSoMGGUKlsgLfrxneb72axVJyIYji64E2+nNfYyw==",
+ "license": "ISC",
+ "dependencies": {
+ "cookie": "^0.7.2"
+ }
+ },
+ "node_modules/@bundled-es-modules/cookie/node_modules/cookie": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
+ "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/@bundled-es-modules/statuses": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@bundled-es-modules/statuses/-/statuses-1.0.1.tgz",
+ "integrity": "sha512-yn7BklA5acgcBr+7w064fGV+SGIFySjCKpqjcWgBAIfrAkY+4GQTJJHQMeT3V/sgz23VTEVV8TtOmkvJAhFVfg==",
+ "license": "ISC",
+ "dependencies": {
+ "statuses": "^2.0.1"
+ }
+ },
+ "node_modules/@bundled-es-modules/tough-cookie": {
+ "version": "0.1.6",
+ "resolved": "https://registry.npmjs.org/@bundled-es-modules/tough-cookie/-/tough-cookie-0.1.6.tgz",
+ "integrity": "sha512-dvMHbL464C0zI+Yqxbz6kZ5TOEp7GLW+pry/RWndAR8MJQAXZ2rPmIs8tziTZjeIyhSNZgZbCePtfSbdWqStJw==",
+ "license": "ISC",
+ "dependencies": {
+ "@types/tough-cookie": "^4.0.5",
+ "tough-cookie": "^4.1.4"
+ }
+ },
"node_modules/@emotion/babel-plugin": {
"version": "11.13.5",
"resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz",
@@ -1107,6 +1278,16 @@
"react-dom": "^18 || ^19 || ^19.0.0-rc"
}
},
+ "node_modules/@heroicons/react": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/@heroicons/react/-/react-2.2.0.tgz",
+ "integrity": "sha512-LMcepvRaS9LYHJGsF0zzmgKCUim/X3N/DQKc4jepAXJ7l8QxJ1PmxJzqplF2Z3FE4PqBAIGyJAQ/w4B5dsqbtQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "react": ">= 16 || ^19.0.0-rc"
+ }
+ },
"node_modules/@humanfs/core": {
"version": "0.19.1",
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
@@ -1173,6 +1354,92 @@
"url": "https://github.com/sponsors/nzakas"
}
},
+ "node_modules/@inquirer/confirm": {
+ "version": "5.1.15",
+ "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.15.tgz",
+ "integrity": "sha512-SwHMGa8Z47LawQN0rog0sT+6JpiL0B7eW9p1Bb7iCeKDGTI5Ez25TSc2l8kw52VV7hA4sX/C78CGkMrKXfuspA==",
+ "license": "MIT",
+ "dependencies": {
+ "@inquirer/core": "^10.1.15",
+ "@inquirer/type": "^3.0.8"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@types/node": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@inquirer/core": {
+ "version": "10.1.15",
+ "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.1.15.tgz",
+ "integrity": "sha512-8xrp836RZvKkpNbVvgWUlxjT4CraKk2q+I3Ksy+seI2zkcE+y6wNs1BVhgcv8VyImFecUhdQrYLdW32pAjwBdA==",
+ "license": "MIT",
+ "dependencies": {
+ "@inquirer/figures": "^1.0.13",
+ "@inquirer/type": "^3.0.8",
+ "ansi-escapes": "^4.3.2",
+ "cli-width": "^4.1.0",
+ "mute-stream": "^2.0.0",
+ "signal-exit": "^4.1.0",
+ "wrap-ansi": "^6.2.0",
+ "yoctocolors-cjs": "^2.1.2"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@types/node": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@inquirer/core/node_modules/signal-exit": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
+ "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/@inquirer/figures": {
+ "version": "1.0.13",
+ "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.13.tgz",
+ "integrity": "sha512-lGPVU3yO9ZNqA7vTYz26jny41lE7yoQansmqdMLBEfqaGsmdg7V3W9mK9Pvb5IL4EVZ9GnSDGMO/cJXud5dMaw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@inquirer/type": {
+ "version": "3.0.8",
+ "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.8.tgz",
+ "integrity": "sha512-lg9Whz8onIHRthWaN1Q9EGLa/0LFJjyM8mEUbL1eTi6yMGvBf8gvyDLtxSXztQsxMvhxxNpJYrwa1YHdq+w4Jw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@types/node": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@isaacs/fs-minipass": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz",
@@ -1220,11 +1487,50 @@
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
+ "node_modules/@modelcontextprotocol/sdk": {
+ "version": "1.17.3",
+ "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.17.3.tgz",
+ "integrity": "sha512-JPwUKWSsbzx+DLFznf/QZ32Qa+ptfbUlHhRLrBQBAFu9iI1iYvizM4p+zhhRDceSsPutXp4z+R/HPVphlIiclg==",
+ "license": "MIT",
+ "dependencies": {
+ "ajv": "^6.12.6",
+ "content-type": "^1.0.5",
+ "cors": "^2.8.5",
+ "cross-spawn": "^7.0.5",
+ "eventsource": "^3.0.2",
+ "eventsource-parser": "^3.0.0",
+ "express": "^5.0.1",
+ "express-rate-limit": "^7.5.0",
+ "pkce-challenge": "^5.0.0",
+ "raw-body": "^3.0.0",
+ "zod": "^3.23.8",
+ "zod-to-json-schema": "^3.24.1"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@mswjs/interceptors": {
+ "version": "0.39.6",
+ "resolved": "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.39.6.tgz",
+ "integrity": "sha512-bndDP83naYYkfayr/qhBHMhk0YGwS1iv6vaEGcr0SQbO0IZtbOPqjKjds/WcG+bJA+1T5vCx6kprKOzn5Bg+Vw==",
+ "license": "MIT",
+ "dependencies": {
+ "@open-draft/deferred-promise": "^2.2.0",
+ "@open-draft/logger": "^0.3.0",
+ "@open-draft/until": "^2.0.0",
+ "is-node-process": "^1.2.0",
+ "outvariant": "^1.4.3",
+ "strict-event-emitter": "^0.5.1"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
"integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@nodelib/fs.stat": "2.0.5",
@@ -1238,7 +1544,6 @@
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
"integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">= 8"
@@ -1248,7 +1553,6 @@
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
"integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@nodelib/fs.scandir": "2.1.5",
@@ -1258,6 +1562,28 @@
"node": ">= 8"
}
},
+ "node_modules/@open-draft/deferred-promise": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/@open-draft/deferred-promise/-/deferred-promise-2.2.0.tgz",
+ "integrity": "sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==",
+ "license": "MIT"
+ },
+ "node_modules/@open-draft/logger": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/@open-draft/logger/-/logger-0.3.0.tgz",
+ "integrity": "sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==",
+ "license": "MIT",
+ "dependencies": {
+ "is-node-process": "^1.2.0",
+ "outvariant": "^1.4.0"
+ }
+ },
+ "node_modules/@open-draft/until": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/@open-draft/until/-/until-2.1.0.tgz",
+ "integrity": "sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==",
+ "license": "MIT"
+ },
"node_modules/@radix-ui/number": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.1.tgz",
@@ -1270,6 +1596,43 @@
"integrity": "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA==",
"license": "MIT"
},
+ "node_modules/@radix-ui/react-accordion": {
+ "version": "1.2.12",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-accordion/-/react-accordion-1.2.12.tgz",
+ "integrity": "sha512-T4nygeh9YE9dLRPhAHSeOZi7HBXo+0kYIPJXayZfvWOWA0+n3dESrZbjfDPUABkUNym6Hd+f2IR113To8D2GPA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-collapsible": "1.1.12",
+ "@radix-ui/react-collection": "1.1.7",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-direction": "1.1.1",
+ "@radix-ui/react-id": "1.1.1",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-controllable-state": "1.2.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-accordion/node_modules/@radix-ui/primitive": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz",
+ "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==",
+ "license": "MIT"
+ },
"node_modules/@radix-ui/react-arrow": {
"version": "1.1.7",
"resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz",
@@ -1323,16 +1686,20 @@
}
}
},
- "node_modules/@radix-ui/react-collection": {
- "version": "1.1.7",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz",
- "integrity": "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==",
+ "node_modules/@radix-ui/react-collapsible": {
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.1.12.tgz",
+ "integrity": "sha512-Uu+mSh4agx2ib1uIGPP4/CKNULyajb3p92LsVXmH2EHVMTfZWpll88XJ0j4W0z3f8NK1eYl1+Mf/szHPmcHzyA==",
"license": "MIT",
"dependencies": {
+ "@radix-ui/primitive": "1.1.3",
"@radix-ui/react-compose-refs": "1.1.2",
"@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-id": "1.1.1",
+ "@radix-ui/react-presence": "1.1.5",
"@radix-ui/react-primitive": "2.1.3",
- "@radix-ui/react-slot": "1.2.3"
+ "@radix-ui/react-use-controllable-state": "1.2.2",
+ "@radix-ui/react-use-layout-effect": "1.1.1"
},
"peerDependencies": {
"@types/react": "*",
@@ -1349,14 +1716,70 @@
}
}
},
- "node_modules/@radix-ui/react-compose-refs": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz",
- "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==",
+ "node_modules/@radix-ui/react-collapsible/node_modules/@radix-ui/primitive": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz",
+ "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==",
+ "license": "MIT"
+ },
+ "node_modules/@radix-ui/react-collapsible/node_modules/@radix-ui/react-presence": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz",
+ "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==",
"license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-use-layout-effect": "1.1.1"
+ },
"peerDependencies": {
"@types/react": "*",
- "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-collection": {
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz",
+ "integrity": "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-slot": "1.2.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-compose-refs": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz",
+ "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
@@ -2503,6 +2926,32 @@
"vite": "^5.2.0 || ^6 || ^7"
}
},
+ "node_modules/@tanstack/query-core": {
+ "version": "5.85.5",
+ "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.85.5.tgz",
+ "integrity": "sha512-KO0WTob4JEApv69iYp1eGvfMSUkgw//IpMnq+//cORBzXf0smyRwPLrUvEe5qtAEGjwZTXrjxg+oJNP/C00t6w==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ }
+ },
+ "node_modules/@tanstack/react-query": {
+ "version": "5.85.5",
+ "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.85.5.tgz",
+ "integrity": "sha512-/X4EFNcnPiSs8wM2v+b6DqS5mmGeuJQvxBglmDxl6ZQb5V26ouD2SJYAcC3VjbNwqhY2zjxVD15rDA5nGbMn3A==",
+ "license": "MIT",
+ "dependencies": {
+ "@tanstack/query-core": "5.85.5"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ },
+ "peerDependencies": {
+ "react": "^18 || ^19"
+ }
+ },
"node_modules/@tanstack/react-virtual": {
"version": "3.13.12",
"resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.13.12.tgz",
@@ -2530,6 +2979,57 @@
"url": "https://github.com/sponsors/tannerlinsley"
}
},
+ "node_modules/@ts-morph/common": {
+ "version": "0.19.0",
+ "resolved": "https://registry.npmjs.org/@ts-morph/common/-/common-0.19.0.tgz",
+ "integrity": "sha512-Unz/WHmd4pGax91rdIKWi51wnVUW11QttMEPpBiBgIewnc9UQIX7UDLxr5vRlqeByXCwhkF6VabSsI0raWcyAQ==",
+ "license": "MIT",
+ "dependencies": {
+ "fast-glob": "^3.2.12",
+ "minimatch": "^7.4.3",
+ "mkdirp": "^2.1.6",
+ "path-browserify": "^1.0.1"
+ }
+ },
+ "node_modules/@ts-morph/common/node_modules/brace-expansion": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
+ "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/@ts-morph/common/node_modules/minimatch": {
+ "version": "7.4.6",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-7.4.6.tgz",
+ "integrity": "sha512-sBz8G/YjVniEz6lKPNpKxXwazJe4c19fEfV2GDMX6AjFz+MX9uDWIZW8XreVhkFW3fkIdTv/gxWr/Kks5FFAVw==",
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/@ts-morph/common/node_modules/mkdirp": {
+ "version": "2.1.6",
+ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-2.1.6.tgz",
+ "integrity": "sha512-+hEnITedc8LAtIP9u3HJDFIdcLV2vXP33sqLLIzkv1Db1zO/1OxbvYf0Y1OC/S/Qo5dxHXepofhmxL02PsKe+A==",
+ "license": "MIT",
+ "bin": {
+ "mkdirp": "dist/cjs/src/bin.js"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
"node_modules/@types/babel__core": {
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
@@ -2575,6 +3075,12 @@
"@babel/types": "^7.20.7"
}
},
+ "node_modules/@types/cookie": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz",
+ "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==",
+ "license": "MIT"
+ },
"node_modules/@types/estree": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
@@ -2632,6 +3138,18 @@
"@types/react": "*"
}
},
+ "node_modules/@types/statuses": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/@types/statuses/-/statuses-2.0.6.tgz",
+ "integrity": "sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA==",
+ "license": "MIT"
+ },
+ "node_modules/@types/tough-cookie": {
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz",
+ "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==",
+ "license": "MIT"
+ },
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.38.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.38.0.tgz",
@@ -2924,6 +3442,40 @@
"vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
}
},
+ "node_modules/accepts": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
+ "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-types": "^3.0.0",
+ "negotiator": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/accepts/node_modules/mime-db": {
+ "version": "1.54.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
+ "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/accepts/node_modules/mime-types": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz",
+ "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "^1.54.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
"node_modules/acorn": {
"version": "8.15.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
@@ -2947,11 +3499,19 @@
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
}
},
+ "node_modules/agent-base": {
+ "version": "7.1.4",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
+ "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 14"
+ }
+ },
"node_modules/ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
- "dev": true,
"license": "MIT",
"dependencies": {
"fast-deep-equal": "^3.1.1",
@@ -2964,11 +3524,49 @@
"url": "https://github.com/sponsors/epoberezkin"
}
},
+ "node_modules/ansi-escapes": {
+ "version": "4.3.2",
+ "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz",
+ "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==",
+ "license": "MIT",
+ "dependencies": {
+ "type-fest": "^0.21.3"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/ansi-escapes/node_modules/type-fest": {
+ "version": "0.21.3",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz",
+ "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==",
+ "license": "(MIT OR CC0-1.0)",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/ansi-regex": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.0.tgz",
+ "integrity": "sha512-TKY5pyBkHyADOPYlRT9Lx6F544mPl0vS5Ew7BJ45hA08Q+t3GjbueLliBWN3sMICk6+y7HdyxSzC4bWS8baBdg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+ }
+ },
"node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
- "dev": true,
"license": "MIT",
"dependencies": {
"color-convert": "^2.0.1"
@@ -2984,7 +3582,6 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
- "dev": true,
"license": "Python-2.0"
},
"node_modules/aria-hidden": {
@@ -2999,6 +3596,18 @@
"node": ">=10"
}
},
+ "node_modules/ast-types": {
+ "version": "0.16.1",
+ "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.16.1.tgz",
+ "integrity": "sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
@@ -3035,9 +3644,59 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
- "dev": true,
"license": "MIT"
},
+ "node_modules/base64-js": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
+ "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/bl": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/bl/-/bl-5.1.0.tgz",
+ "integrity": "sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==",
+ "license": "MIT",
+ "dependencies": {
+ "buffer": "^6.0.3",
+ "inherits": "^2.0.4",
+ "readable-stream": "^3.4.0"
+ }
+ },
+ "node_modules/body-parser": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz",
+ "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==",
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "^3.1.2",
+ "content-type": "^1.0.5",
+ "debug": "^4.4.0",
+ "http-errors": "^2.0.0",
+ "iconv-lite": "^0.6.3",
+ "on-finished": "^2.4.1",
+ "qs": "^6.14.0",
+ "raw-body": "^3.0.0",
+ "type-is": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/brace-expansion": {
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
@@ -3053,7 +3712,6 @@
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
- "dev": true,
"license": "MIT",
"dependencies": {
"fill-range": "^7.1.1"
@@ -3066,7 +3724,6 @@
"version": "4.25.1",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.1.tgz",
"integrity": "sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==",
- "dev": true,
"funding": [
{
"type": "opencollective",
@@ -3095,6 +3752,39 @@
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
}
},
+ "node_modules/buffer": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
+ "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "base64-js": "^1.3.1",
+ "ieee754": "^1.2.1"
+ }
+ },
+ "node_modules/bytes": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
+ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/call-bind-apply-helpers": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
@@ -3108,6 +3798,22 @@
"node": ">= 0.4"
}
},
+ "node_modules/call-bound": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
+ "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "get-intrinsic": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/callsites": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
@@ -3121,7 +3827,6 @@
"version": "1.0.30001727",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001727.tgz",
"integrity": "sha512-pB68nIHmbN6L/4C6MH1DokyR3bYqFwjaSs/sWDHGj4CTcFtQUQMuJftVwWkXq7mNWOybD3KhUv3oWHoGxgP14Q==",
- "dev": true,
"funding": [
{
"type": "opencollective",
@@ -3176,33 +3881,134 @@
"url": "https://polar.sh/cva"
}
},
- "node_modules/clsx": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
- "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
+ "node_modules/cli-cursor": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz",
+ "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==",
"license": "MIT",
+ "dependencies": {
+ "restore-cursor": "^4.0.0"
+ },
"engines": {
- "node": ">=6"
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/color-convert": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
- "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
- "dev": true,
+ "node_modules/cli-spinners": {
+ "version": "2.9.2",
+ "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz",
+ "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==",
"license": "MIT",
- "dependencies": {
- "color-name": "~1.1.4"
+ "engines": {
+ "node": ">=6"
},
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/cli-width": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz",
+ "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==",
+ "license": "ISC",
"engines": {
- "node": ">=7.0.0"
+ "node": ">= 12"
+ }
+ },
+ "node_modules/cliui": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
+ "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
+ "license": "ISC",
+ "dependencies": {
+ "string-width": "^4.2.0",
+ "strip-ansi": "^6.0.1",
+ "wrap-ansi": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/cliui/node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/cliui/node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/cliui/node_modules/wrap-ansi": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+ "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/clone": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz",
+ "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
+ "node_modules/clsx": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
+ "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/code-block-writer": {
+ "version": "12.0.0",
+ "resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-12.0.0.tgz",
+ "integrity": "sha512-q4dMFMlXtKR3XNBHyMHt/3pwYNA69EDk00lloMOaaUMKPUXBw6lpXtbu3MMVG6/uOihGnRDOlkyqsONEUj60+w==",
+ "license": "MIT"
+ },
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
}
},
"node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
- "dev": true,
"license": "MIT"
},
"node_modules/combined-stream": {
@@ -3217,6 +4023,15 @@
"node": ">= 0.8"
}
},
+ "node_modules/commander": {
+ "version": "10.0.1",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz",
+ "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=14"
+ }
+ },
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@@ -3224,11 +4039,31 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/content-disposition": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz",
+ "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==",
+ "license": "MIT",
+ "dependencies": {
+ "safe-buffer": "5.2.1"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/content-type": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
+ "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
"node_modules/convert-source-map": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
- "dev": true,
"license": "MIT"
},
"node_modules/cookie": {
@@ -3240,6 +4075,28 @@
"node": ">=18"
}
},
+ "node_modules/cookie-signature": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz",
+ "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.6.0"
+ }
+ },
+ "node_modules/cors": {
+ "version": "2.8.5",
+ "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
+ "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
+ "license": "MIT",
+ "dependencies": {
+ "object-assign": "^4",
+ "vary": "^1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
"node_modules/cosmiconfig": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz",
@@ -3269,7 +4126,6 @@
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
- "dev": true,
"license": "MIT",
"dependencies": {
"path-key": "^3.1.0",
@@ -3286,6 +4142,15 @@
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
"license": "MIT"
},
+ "node_modules/data-uri-to-buffer": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
+ "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 12"
+ }
+ },
"node_modules/date-fns": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz",
@@ -3320,6 +4185,27 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/deepmerge": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
+ "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/defaults": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz",
+ "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==",
+ "license": "MIT",
+ "dependencies": {
+ "clone": "^1.0.2"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
@@ -3329,6 +4215,15 @@
"node": ">=0.4.0"
}
},
+ "node_modules/depd": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/detect-libc": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz",
@@ -3344,6 +4239,15 @@
"integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==",
"license": "MIT"
},
+ "node_modules/diff": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz",
+ "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.3.1"
+ }
+ },
"node_modules/dom-helpers": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz",
@@ -3368,13 +4272,33 @@
"node": ">= 0.4"
}
},
+ "node_modules/ee-first": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
+ "license": "MIT"
+ },
"node_modules/electron-to-chromium": {
"version": "1.5.189",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.189.tgz",
"integrity": "sha512-y9D1ntS1ruO/pZ/V2FtLE+JXLQe28XoRpZ7QCCo0T8LdQladzdcOVQZH/IWLVJvCw12OGMb6hYOeOAjntCmJRQ==",
- "dev": true,
"license": "ISC"
},
+ "node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "license": "MIT"
+ },
+ "node_modules/encodeurl": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
+ "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/enhanced-resolve": {
"version": "5.18.2",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.2.tgz",
@@ -3487,12 +4411,17 @@
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
"integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
}
},
+ "node_modules/escape-html": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
+ "license": "MIT"
+ },
"node_modules/escape-string-regexp": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
@@ -3637,6 +4566,19 @@
"url": "https://opencollective.com/eslint"
}
},
+ "node_modules/esprima": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
+ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
+ "license": "BSD-2-Clause",
+ "bin": {
+ "esparse": "bin/esparse.js",
+ "esvalidate": "bin/esvalidate.js"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/esquery": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz",
@@ -3683,18 +4625,156 @@
"node": ">=0.10.0"
}
},
+ "node_modules/etag": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/eventsource": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz",
+ "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==",
+ "license": "MIT",
+ "dependencies": {
+ "eventsource-parser": "^3.0.1"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/eventsource-parser": {
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.5.tgz",
+ "integrity": "sha512-bSRG85ZrMdmWtm7qkF9He9TNRzc/Bm99gEJMaQoHJ9E6Kv9QBbsldh2oMj7iXmYNEAVvNgvv5vPorG6W+XtBhQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/execa": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/execa/-/execa-7.2.0.tgz",
+ "integrity": "sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA==",
+ "license": "MIT",
+ "dependencies": {
+ "cross-spawn": "^7.0.3",
+ "get-stream": "^6.0.1",
+ "human-signals": "^4.3.0",
+ "is-stream": "^3.0.0",
+ "merge-stream": "^2.0.0",
+ "npm-run-path": "^5.1.0",
+ "onetime": "^6.0.0",
+ "signal-exit": "^3.0.7",
+ "strip-final-newline": "^3.0.0"
+ },
+ "engines": {
+ "node": "^14.18.0 || ^16.14.0 || >=18.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/execa?sponsor=1"
+ }
+ },
+ "node_modules/express": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz",
+ "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==",
+ "license": "MIT",
+ "dependencies": {
+ "accepts": "^2.0.0",
+ "body-parser": "^2.2.0",
+ "content-disposition": "^1.0.0",
+ "content-type": "^1.0.5",
+ "cookie": "^0.7.1",
+ "cookie-signature": "^1.2.1",
+ "debug": "^4.4.0",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "etag": "^1.8.1",
+ "finalhandler": "^2.1.0",
+ "fresh": "^2.0.0",
+ "http-errors": "^2.0.0",
+ "merge-descriptors": "^2.0.0",
+ "mime-types": "^3.0.0",
+ "on-finished": "^2.4.1",
+ "once": "^1.4.0",
+ "parseurl": "^1.3.3",
+ "proxy-addr": "^2.0.7",
+ "qs": "^6.14.0",
+ "range-parser": "^1.2.1",
+ "router": "^2.2.0",
+ "send": "^1.1.0",
+ "serve-static": "^2.2.0",
+ "statuses": "^2.0.1",
+ "type-is": "^2.0.1",
+ "vary": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/express-rate-limit": {
+ "version": "7.5.1",
+ "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.1.tgz",
+ "integrity": "sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/express-rate-limit"
+ },
+ "peerDependencies": {
+ "express": ">= 4.11"
+ }
+ },
+ "node_modules/express/node_modules/cookie": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
+ "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/express/node_modules/mime-db": {
+ "version": "1.54.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
+ "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/express/node_modules/mime-types": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz",
+ "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "^1.54.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
- "dev": true,
"license": "MIT"
},
"node_modules/fast-glob": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
"integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@nodelib/fs.stat": "^2.0.2",
@@ -3711,7 +4791,6 @@
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
- "dev": true,
"license": "ISC",
"dependencies": {
"is-glob": "^4.0.1"
@@ -3724,7 +4803,6 @@
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
"integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
- "dev": true,
"license": "MIT"
},
"node_modules/fast-levenshtein": {
@@ -3738,12 +4816,34 @@
"version": "1.19.1",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz",
"integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==",
- "dev": true,
"license": "ISC",
"dependencies": {
"reusify": "^1.0.4"
}
},
+ "node_modules/fetch-blob": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz",
+ "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/jimmywarting"
+ },
+ {
+ "type": "paypal",
+ "url": "https://paypal.me/jimmywarting"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "node-domexception": "^1.0.0",
+ "web-streams-polyfill": "^3.0.3"
+ },
+ "engines": {
+ "node": "^12.20 || >= 14.13"
+ }
+ },
"node_modules/file-entry-cache": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
@@ -3761,7 +4861,6 @@
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
- "dev": true,
"license": "MIT",
"dependencies": {
"to-regex-range": "^5.0.1"
@@ -3770,6 +4869,23 @@
"node": ">=8"
}
},
+ "node_modules/finalhandler": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz",
+ "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.4.0",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "on-finished": "^2.4.1",
+ "parseurl": "^1.3.3",
+ "statuses": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/find-root": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz",
@@ -3850,13 +4966,34 @@
"node": ">= 6"
}
},
+ "node_modules/formdata-polyfill": {
+ "version": "4.0.10",
+ "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
+ "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
+ "license": "MIT",
+ "dependencies": {
+ "fetch-blob": "^3.1.2"
+ },
+ "engines": {
+ "node": ">=12.20.0"
+ }
+ },
+ "node_modules/forwarded": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
+ "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
"node_modules/framer-motion": {
- "version": "12.23.6",
- "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.23.6.tgz",
- "integrity": "sha512-dsJ389QImVE3lQvM8Mnk99/j8tiZDM/7706PCqvkQ8sSCnpmWxsgX+g0lj7r5OBVL0U36pIecCTBoIWcM2RuKw==",
+ "version": "12.23.12",
+ "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.23.12.tgz",
+ "integrity": "sha512-6e78rdVtnBvlEVgu6eFEAgG9v3wLnYEboM8I5O5EXvfKC8gxGQB8wXJdhkMy10iVcn05jl6CNw7/HTsTCfwcWg==",
"license": "MIT",
"dependencies": {
- "motion-dom": "^12.23.6",
+ "motion-dom": "^12.23.12",
"motion-utils": "^12.23.6",
"tslib": "^2.4.0"
},
@@ -3877,6 +5014,29 @@
}
}
},
+ "node_modules/fresh": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
+ "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/fs-extra": {
+ "version": "11.3.1",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.1.tgz",
+ "integrity": "sha512-eXvGGwZ5CL17ZSwHWd3bbgk7UUpF6IFHtP57NYYakPvHOs8GDgDe5KJI36jIJzDkJ6eJjuzRA8eBQb6SkKue0g==",
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^6.0.1",
+ "universalify": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=14.14"
+ }
+ },
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
@@ -3904,16 +5064,24 @@
"version": "1.0.0-beta.2",
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
"integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
- "node_modules/get-intrinsic": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
- "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+ "node_modules/get-caller-file": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
+ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
+ "license": "ISC",
+ "engines": {
+ "node": "6.* || 8.* || >= 10.*"
+ }
+ },
+ "node_modules/get-intrinsic": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.2",
@@ -3943,6 +5111,18 @@
"node": ">=6"
}
},
+ "node_modules/get-own-enumerable-keys": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/get-own-enumerable-keys/-/get-own-enumerable-keys-1.0.0.tgz",
+ "integrity": "sha512-PKsK2FSrQCyxcGHsGrLDcK0lx+0Ke+6e8KFFozA9/fIQLhQzPaRvJFdcz7+Axg3jUH/Mq+NI4xa5u/UT2tQskA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/get-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
@@ -3956,6 +5136,18 @@
"node": ">= 0.4"
}
},
+ "node_modules/get-stream": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
+ "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/glob-parent": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
@@ -4007,6 +5199,15 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/graphql": {
+ "version": "16.11.0",
+ "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.11.0.tgz",
+ "integrity": "sha512-mS1lbMsxgQj6hge1XZ6p7GPhbrtFwUFYi3wRzXAC/FmYnyXMTvvI3td3rjmQ2u8ewXueaSvRPWaEcgVVOT9Jnw==",
+ "license": "MIT",
+ "engines": {
+ "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0"
+ }
+ },
"node_modules/has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
@@ -4056,6 +5257,12 @@
"node": ">= 0.4"
}
},
+ "node_modules/headers-polyfill": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/headers-polyfill/-/headers-polyfill-4.0.3.tgz",
+ "integrity": "sha512-IScLbePpkvO846sIwOtOTDjutRMWdXdJmXdMvk6gCBHxFO8d+QKOQedyZSxFTTFYRSmlgSTDtXqqq4pcenBXLQ==",
+ "license": "MIT"
+ },
"node_modules/hoist-non-react-statics": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
@@ -4065,6 +5272,85 @@
"react-is": "^16.7.0"
}
},
+ "node_modules/http-errors": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
+ "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
+ "license": "MIT",
+ "dependencies": {
+ "depd": "2.0.0",
+ "inherits": "2.0.4",
+ "setprototypeof": "1.2.0",
+ "statuses": "2.0.1",
+ "toidentifier": "1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/http-errors/node_modules/statuses": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
+ "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/https-proxy-agent": {
+ "version": "6.2.1",
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-6.2.1.tgz",
+ "integrity": "sha512-ONsE3+yfZF2caH5+bJlcddtWqNI3Gvs5A38+ngvljxaBiRXRswym2c7yf8UAeFpRFKjFNHIFEHqR/OLAWJzyiA==",
+ "license": "MIT",
+ "dependencies": {
+ "agent-base": "^7.0.2",
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/human-signals": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-4.3.1.tgz",
+ "integrity": "sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=14.18.0"
+ }
+ },
+ "node_modules/iconv-lite": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
+ "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/ieee754": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
+ "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "BSD-3-Clause"
+ },
"node_modules/ignore": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
@@ -4101,6 +5387,21 @@
"node": ">=0.8.19"
}
},
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "license": "ISC"
+ },
+ "node_modules/ipaddr.js": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
+ "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
"node_modules/is-arrayish": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
@@ -4126,17 +5427,24 @@
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
+ "node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/is-glob": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
- "dev": true,
"license": "MIT",
"dependencies": {
"is-extglob": "^2.1.1"
@@ -4145,21 +5453,91 @@
"node": ">=0.10.0"
}
},
+ "node_modules/is-interactive": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz",
+ "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/is-node-process": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/is-node-process/-/is-node-process-1.2.0.tgz",
+ "integrity": "sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==",
+ "license": "MIT"
+ },
"node_modules/is-number": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=0.12.0"
}
},
+ "node_modules/is-obj": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-3.0.0.tgz",
+ "integrity": "sha512-IlsXEHOjtKhpN8r/tRFj2nDyTmHvcfNeu/nrRIcXE17ROeatXchkojffa1SpdqW4cr/Fj6QkEf/Gn4zf6KKvEQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/is-promise": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
+ "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
+ "license": "MIT"
+ },
+ "node_modules/is-regexp": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-3.1.0.tgz",
+ "integrity": "sha512-rbku49cWloU5bSMI+zaRaXdQHXnthP6DZ/vLnfdSKyL4zUzuWnomtOEiZZOd+ioQ+avFo/qau3KPTc7Fjy1uPA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/is-stream": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz",
+ "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==",
+ "license": "MIT",
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/is-unicode-supported": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz",
+ "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
- "dev": true,
"license": "ISC"
},
"node_modules/jiti": {
@@ -4181,7 +5559,6 @@
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
- "dev": true,
"license": "MIT",
"dependencies": {
"argparse": "^2.0.1"
@@ -4219,7 +5596,6 @@
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
- "dev": true,
"license": "MIT"
},
"node_modules/json-stable-stringify-without-jsonify": {
@@ -4233,7 +5609,6 @@
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
- "dev": true,
"license": "MIT",
"bin": {
"json5": "lib/cli.js"
@@ -4242,6 +5617,18 @@
"node": ">=6"
}
},
+ "node_modules/jsonfile": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz",
+ "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==",
+ "license": "MIT",
+ "dependencies": {
+ "universalify": "^2.0.0"
+ },
+ "optionalDependencies": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
"node_modules/jwt-decode": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz",
@@ -4261,6 +5648,15 @@
"json-buffer": "3.0.1"
}
},
+ "node_modules/kleur": {
+ "version": "4.1.5",
+ "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz",
+ "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/levn": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
@@ -4532,6 +5928,34 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/log-symbols": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-5.1.0.tgz",
+ "integrity": "sha512-l0x2DvrW294C9uDCoQe1VSU4gf529FkSZ6leBl4TiqZH/e+0R7hSfHQBNut2mNygDgHwvYHfFLn6Oxb3VWj2rA==",
+ "license": "MIT",
+ "dependencies": {
+ "chalk": "^5.0.0",
+ "is-unicode-supported": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/log-symbols/node_modules/chalk": {
+ "version": "5.6.0",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.0.tgz",
+ "integrity": "sha512-46QrSQFyVSEyYAgQ22hQ+zDa60YHA4fBstHmtSApj1Y5vKtG27fWowW03jCk5KcbXEWPZUIR894aARCA/G1kfQ==",
+ "license": "MIT",
+ "engines": {
+ "node": "^12.17.0 || ^14.13 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
"node_modules/loose-envify": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
@@ -4548,7 +5972,6 @@
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
"integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
- "dev": true,
"license": "ISC",
"dependencies": {
"yallist": "^3.0.2"
@@ -4581,17 +6004,43 @@
"node": ">= 0.4"
}
},
+ "node_modules/media-typer": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz",
+ "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/memoize-one": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz",
"integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==",
"license": "MIT"
},
+ "node_modules/merge-descriptors": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz",
+ "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/merge-stream": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
+ "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
+ "license": "MIT"
+ },
"node_modules/merge2": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
"integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">= 8"
@@ -4601,7 +6050,6 @@
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
- "dev": true,
"license": "MIT",
"dependencies": {
"braces": "^3.0.3",
@@ -4632,6 +6080,18 @@
"node": ">= 0.6"
}
},
+ "node_modules/mimic-fn": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz",
+ "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
@@ -4645,6 +6105,15 @@
"node": "*"
}
},
+ "node_modules/minimist": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
+ "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/minipass": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
@@ -4682,9 +6151,9 @@
}
},
"node_modules/motion-dom": {
- "version": "12.23.6",
- "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.23.6.tgz",
- "integrity": "sha512-G2w6Nw7ZOVSzcQmsdLc0doMe64O/Sbuc2bVAbgMz6oP/6/pRStKRiVRV4bQfHp5AHYAKEGhEdVHTM+R3FDgi5w==",
+ "version": "12.23.12",
+ "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.23.12.tgz",
+ "integrity": "sha512-RcR4fvMCTESQBD/uKQe49D5RUeDOokkGRmz4ceaJKDBgHYtZtntC/s2vLvY38gqGaytinij/yi3hMcWVcEF5Kw==",
"license": "MIT",
"dependencies": {
"motion-utils": "^12.23.6"
@@ -4702,6 +6171,59 @@
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT"
},
+ "node_modules/msw": {
+ "version": "2.10.5",
+ "resolved": "https://registry.npmjs.org/msw/-/msw-2.10.5.tgz",
+ "integrity": "sha512-0EsQCrCI1HbhpBWd89DvmxY6plmvrM96b0sCIztnvcNHQbXn5vqwm1KlXslo6u4wN9LFGLC1WFjjgljcQhe40A==",
+ "hasInstallScript": true,
+ "license": "MIT",
+ "dependencies": {
+ "@bundled-es-modules/cookie": "^2.0.1",
+ "@bundled-es-modules/statuses": "^1.0.1",
+ "@bundled-es-modules/tough-cookie": "^0.1.6",
+ "@inquirer/confirm": "^5.0.0",
+ "@mswjs/interceptors": "^0.39.1",
+ "@open-draft/deferred-promise": "^2.2.0",
+ "@open-draft/until": "^2.1.0",
+ "@types/cookie": "^0.6.0",
+ "@types/statuses": "^2.0.4",
+ "graphql": "^16.8.1",
+ "headers-polyfill": "^4.0.2",
+ "is-node-process": "^1.2.0",
+ "outvariant": "^1.4.3",
+ "path-to-regexp": "^6.3.0",
+ "picocolors": "^1.1.1",
+ "strict-event-emitter": "^0.5.1",
+ "type-fest": "^4.26.1",
+ "yargs": "^17.7.2"
+ },
+ "bin": {
+ "msw": "cli/index.js"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/mswjs"
+ },
+ "peerDependencies": {
+ "typescript": ">= 4.8.x"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/mute-stream": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz",
+ "integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==",
+ "license": "ISC",
+ "engines": {
+ "node": "^18.17.0 || >=20.5.0"
+ }
+ },
"node_modules/nanoid": {
"version": "3.3.11",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
@@ -4727,25 +6249,146 @@
"dev": true,
"license": "MIT"
},
- "node_modules/node-releases": {
- "version": "2.0.19",
- "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz",
- "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/object-assign": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
- "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+ "node_modules/negotiator": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
+ "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==",
"license": "MIT",
"engines": {
- "node": ">=0.10.0"
+ "node": ">= 0.6"
}
},
- "node_modules/optionator": {
- "version": "0.9.4",
- "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
+ "node_modules/node-domexception": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
+ "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
+ "deprecated": "Use your platform's native DOMException instead",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/jimmywarting"
+ },
+ {
+ "type": "github",
+ "url": "https://paypal.me/jimmywarting"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.5.0"
+ }
+ },
+ "node_modules/node-fetch": {
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz",
+ "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==",
+ "license": "MIT",
+ "dependencies": {
+ "data-uri-to-buffer": "^4.0.0",
+ "fetch-blob": "^3.1.4",
+ "formdata-polyfill": "^4.0.10"
+ },
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/node-fetch"
+ }
+ },
+ "node_modules/node-releases": {
+ "version": "2.0.19",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz",
+ "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==",
+ "license": "MIT"
+ },
+ "node_modules/npm-run-path": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz",
+ "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==",
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^4.0.0"
+ },
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/npm-run-path/node_modules/path-key": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz",
+ "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/object-inspect": {
+ "version": "1.13.4",
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
+ "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/on-finished": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
+ "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
+ "license": "MIT",
+ "dependencies": {
+ "ee-first": "1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+ "license": "ISC",
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "node_modules/onetime": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz",
+ "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==",
+ "license": "MIT",
+ "dependencies": {
+ "mimic-fn": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/optionator": {
+ "version": "0.9.4",
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
"integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
"dev": true,
"license": "MIT",
@@ -4761,6 +6404,47 @@
"node": ">= 0.8.0"
}
},
+ "node_modules/ora": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/ora/-/ora-6.3.1.tgz",
+ "integrity": "sha512-ERAyNnZOfqM+Ao3RAvIXkYh5joP220yf59gVe2X/cI6SiCxIdi4c9HZKZD8R6q/RDXEje1THBju6iExiSsgJaQ==",
+ "license": "MIT",
+ "dependencies": {
+ "chalk": "^5.0.0",
+ "cli-cursor": "^4.0.0",
+ "cli-spinners": "^2.6.1",
+ "is-interactive": "^2.0.0",
+ "is-unicode-supported": "^1.1.0",
+ "log-symbols": "^5.1.0",
+ "stdin-discarder": "^0.1.0",
+ "strip-ansi": "^7.0.1",
+ "wcwidth": "^1.0.1"
+ },
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/ora/node_modules/chalk": {
+ "version": "5.6.0",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.0.tgz",
+ "integrity": "sha512-46QrSQFyVSEyYAgQ22hQ+zDa60YHA4fBstHmtSApj1Y5vKtG27fWowW03jCk5KcbXEWPZUIR894aARCA/G1kfQ==",
+ "license": "MIT",
+ "engines": {
+ "node": "^12.17.0 || ^14.13 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/outvariant": {
+ "version": "1.4.3",
+ "resolved": "https://registry.npmjs.org/outvariant/-/outvariant-1.4.3.tgz",
+ "integrity": "sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==",
+ "license": "MIT"
+ },
"node_modules/p-limit": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
@@ -4823,6 +6507,21 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/parseurl": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/path-browserify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz",
+ "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==",
+ "license": "MIT"
+ },
"node_modules/path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
@@ -4837,7 +6536,6 @@
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
@@ -4849,6 +6547,12 @@
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
"license": "MIT"
},
+ "node_modules/path-to-regexp": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz",
+ "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==",
+ "license": "MIT"
+ },
"node_modules/path-type": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
@@ -4868,7 +6572,6 @@
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=8.6"
@@ -4877,6 +6580,15 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
+ "node_modules/pkce-challenge": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.0.tgz",
+ "integrity": "sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
"node_modules/postcss": {
"version": "8.5.6",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
@@ -4915,6 +6627,28 @@
"node": ">= 0.8.0"
}
},
+ "node_modules/prompts": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz",
+ "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==",
+ "license": "MIT",
+ "dependencies": {
+ "kleur": "^3.0.3",
+ "sisteransi": "^1.0.5"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/prompts/node_modules/kleur": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz",
+ "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/prop-types": {
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
@@ -4926,27 +6660,71 @@
"react-is": "^16.13.1"
}
},
+ "node_modules/proxy-addr": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
+ "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
+ "license": "MIT",
+ "dependencies": {
+ "forwarded": "0.2.0",
+ "ipaddr.js": "1.9.1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
"node_modules/proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
"license": "MIT"
},
+ "node_modules/psl": {
+ "version": "1.15.0",
+ "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz",
+ "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==",
+ "license": "MIT",
+ "dependencies": {
+ "punycode": "^2.3.1"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/lupomontero"
+ }
+ },
"node_modules/punycode": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
}
},
+ "node_modules/qs": {
+ "version": "6.14.0",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz",
+ "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "side-channel": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=0.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/querystringify": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz",
+ "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==",
+ "license": "MIT"
+ },
"node_modules/queue-microtask": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
"integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
- "dev": true,
"funding": [
{
"type": "github",
@@ -4963,27 +6741,37 @@
],
"license": "MIT"
},
- "node_modules/react": {
- "version": "19.1.1",
- "resolved": "https://registry.npmjs.org/react/-/react-19.1.1.tgz",
- "integrity": "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==",
+ "node_modules/range-parser": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
+ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
"license": "MIT",
"engines": {
- "node": ">=0.10.0"
+ "node": ">= 0.6"
}
},
- "node_modules/react-day-picker": {
- "version": "8.10.1",
- "resolved": "https://registry.npmjs.org/react-day-picker/-/react-day-picker-8.10.1.tgz",
- "integrity": "sha512-TMx7fNbhLk15eqcMt+7Z7S2KF7mfTId/XJDjKE8f+IUcFn0l08/kI4FiYTL/0yuOLmEcbR4Fwe3GJf/NiiMnPA==",
+ "node_modules/raw-body": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz",
+ "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==",
"license": "MIT",
- "funding": {
- "type": "individual",
- "url": "https://github.com/sponsors/gpbl"
+ "dependencies": {
+ "bytes": "3.1.2",
+ "http-errors": "2.0.0",
+ "iconv-lite": "0.6.3",
+ "unpipe": "1.0.0"
},
- "peerDependencies": {
- "date-fns": "^2.28.0 || ^3.0.0",
- "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/react": {
+ "version": "19.1.0",
+ "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz",
+ "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
}
},
"node_modules/react-dom": {
@@ -5183,6 +6971,60 @@
"react-dom": ">=16.6.0"
}
},
+ "node_modules/readable-stream": {
+ "version": "3.6.2",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
+ "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
+ "license": "MIT",
+ "dependencies": {
+ "inherits": "^2.0.3",
+ "string_decoder": "^1.1.1",
+ "util-deprecate": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/recast": {
+ "version": "0.23.11",
+ "resolved": "https://registry.npmjs.org/recast/-/recast-0.23.11.tgz",
+ "integrity": "sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA==",
+ "license": "MIT",
+ "dependencies": {
+ "ast-types": "^0.16.1",
+ "esprima": "~4.0.0",
+ "source-map": "~0.6.1",
+ "tiny-invariant": "^1.3.3",
+ "tslib": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/recast/node_modules/source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/require-directory": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
+ "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/requires-port": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
+ "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==",
+ "license": "MIT"
+ },
"node_modules/resolve": {
"version": "1.22.10",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
@@ -5212,11 +7054,50 @@
"node": ">=4"
}
},
+ "node_modules/restore-cursor": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz",
+ "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==",
+ "license": "MIT",
+ "dependencies": {
+ "onetime": "^5.1.0",
+ "signal-exit": "^3.0.2"
+ },
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/restore-cursor/node_modules/mimic-fn": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
+ "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/restore-cursor/node_modules/onetime": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
+ "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
+ "license": "MIT",
+ "dependencies": {
+ "mimic-fn": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/reusify": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
"integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==",
- "dev": true,
"license": "MIT",
"engines": {
"iojs": ">=1.0.0",
@@ -5262,11 +7143,35 @@
"fsevents": "~2.3.2"
}
},
+ "node_modules/router": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
+ "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.4.0",
+ "depd": "^2.0.0",
+ "is-promise": "^4.0.0",
+ "parseurl": "^1.3.3",
+ "path-to-regexp": "^8.0.0"
+ },
+ "engines": {
+ "node": ">= 18"
+ }
+ },
+ "node_modules/router/node_modules/path-to-regexp": {
+ "version": "8.2.0",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz",
+ "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=16"
+ }
+ },
"node_modules/run-parallel": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
"integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
- "dev": true,
"funding": [
{
"type": "github",
@@ -5283,70 +7188,430 @@
],
"license": "MIT",
"dependencies": {
- "queue-microtask": "^1.2.2"
+ "queue-microtask": "^1.2.2"
+ }
+ },
+ "node_modules/safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+ "license": "MIT"
+ },
+ "node_modules/scheduler": {
+ "version": "0.26.0",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz",
+ "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==",
+ "license": "MIT"
+ },
+ "node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/send": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz",
+ "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.3.5",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "etag": "^1.8.1",
+ "fresh": "^2.0.0",
+ "http-errors": "^2.0.0",
+ "mime-types": "^3.0.1",
+ "ms": "^2.1.3",
+ "on-finished": "^2.4.1",
+ "range-parser": "^1.2.1",
+ "statuses": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 18"
+ }
+ },
+ "node_modules/send/node_modules/mime-db": {
+ "version": "1.54.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
+ "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/send/node_modules/mime-types": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz",
+ "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "^1.54.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/serve-static": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz",
+ "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==",
+ "license": "MIT",
+ "dependencies": {
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "parseurl": "^1.3.3",
+ "send": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 18"
+ }
+ },
+ "node_modules/set-cookie-parser": {
+ "version": "2.7.1",
+ "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz",
+ "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==",
+ "license": "MIT"
+ },
+ "node_modules/setprototypeof": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
+ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
+ "license": "ISC"
+ },
+ "node_modules/shadcn": {
+ "version": "2.10.0",
+ "resolved": "https://registry.npmjs.org/shadcn/-/shadcn-2.10.0.tgz",
+ "integrity": "sha512-/zxjmHGbaYVFtI6bUridFVV7VFStIv3vU/w1h7xenhz7KRzc9pqHsyFvcExZprG7dlA5kW9knRgv8+Cl/H7w9w==",
+ "license": "MIT",
+ "dependencies": {
+ "@antfu/ni": "^23.2.0",
+ "@babel/core": "^7.22.1",
+ "@babel/parser": "^7.22.6",
+ "@babel/plugin-transform-typescript": "^7.22.5",
+ "@modelcontextprotocol/sdk": "^1.10.2",
+ "commander": "^10.0.0",
+ "cosmiconfig": "^8.1.3",
+ "deepmerge": "^4.3.1",
+ "diff": "^5.1.0",
+ "execa": "^7.0.0",
+ "fast-glob": "^3.3.2",
+ "fs-extra": "^11.1.0",
+ "https-proxy-agent": "^6.2.0",
+ "kleur": "^4.1.5",
+ "msw": "^2.7.1",
+ "node-fetch": "^3.3.0",
+ "ora": "^6.1.2",
+ "postcss": "^8.4.24",
+ "prompts": "^2.4.2",
+ "recast": "^0.23.2",
+ "stringify-object": "^5.0.0",
+ "ts-morph": "^18.0.0",
+ "tsconfig-paths": "^4.2.0",
+ "zod": "^3.20.2",
+ "zod-to-json-schema": "^3.24.5"
+ },
+ "bin": {
+ "shadcn": "dist/index.js"
+ }
+ },
+ "node_modules/shadcn/node_modules/cosmiconfig": {
+ "version": "8.3.6",
+ "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz",
+ "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==",
+ "license": "MIT",
+ "dependencies": {
+ "import-fresh": "^3.3.0",
+ "js-yaml": "^4.1.0",
+ "parse-json": "^5.2.0",
+ "path-type": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/d-fischer"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.9.5"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "license": "MIT",
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/side-channel": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
+ "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.3",
+ "side-channel-list": "^1.0.0",
+ "side-channel-map": "^1.0.1",
+ "side-channel-weakmap": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-list": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
+ "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-map": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
+ "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-weakmap": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
+ "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3",
+ "side-channel-map": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/signal-exit": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
+ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
+ "license": "ISC"
+ },
+ "node_modules/sisteransi": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
+ "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==",
+ "license": "MIT"
+ },
+ "node_modules/source-map": {
+ "version": "0.5.7",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
+ "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/statuses": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
+ "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/stdin-discarder": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.1.0.tgz",
+ "integrity": "sha512-xhV7w8S+bUwlPTb4bAOUQhv8/cSS5offJuX8GQGq32ONF0ZtDWKfkdomM3HMRA+LhX6um/FZ0COqlwsjD53LeQ==",
+ "license": "MIT",
+ "dependencies": {
+ "bl": "^5.0.0"
+ },
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/scheduler": {
- "version": "0.26.0",
- "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz",
- "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==",
+ "node_modules/strict-event-emitter": {
+ "version": "0.5.1",
+ "resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.5.1.tgz",
+ "integrity": "sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==",
"license": "MIT"
},
- "node_modules/semver": {
- "version": "6.3.1",
- "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
- "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
- "dev": true,
- "license": "ISC",
- "bin": {
- "semver": "bin/semver.js"
+ "node_modules/string_decoder": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
+ "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
+ "license": "MIT",
+ "dependencies": {
+ "safe-buffer": "~5.2.0"
}
},
- "node_modules/set-cookie-parser": {
- "version": "2.7.1",
- "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz",
- "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==",
- "license": "MIT"
- },
- "node_modules/shebang-command": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
- "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
- "dev": true,
+ "node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"license": "MIT",
"dependencies": {
- "shebang-regex": "^3.0.0"
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
},
"engines": {
"node": ">=8"
}
},
- "node_modules/shebang-regex": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
- "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
- "dev": true,
+ "node_modules/string-width/node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
- "node_modules/source-map": {
- "version": "0.5.7",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
- "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==",
- "license": "BSD-3-Clause",
+ "node_modules/string-width/node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
"engines": {
- "node": ">=0.10.0"
+ "node": ">=8"
}
},
- "node_modules/source-map-js": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
- "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
- "license": "BSD-3-Clause",
+ "node_modules/stringify-object": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-5.0.0.tgz",
+ "integrity": "sha512-zaJYxz2FtcMb4f+g60KsRNFOpVMUyuJgA51Zi5Z1DOTC3S59+OQiVOzE9GZt0x72uBGWKsQIuBKeF9iusmKFsg==",
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "get-own-enumerable-keys": "^1.0.0",
+ "is-obj": "^3.0.0",
+ "is-regexp": "^3.1.0"
+ },
"engines": {
- "node": ">=0.10.0"
+ "node": ">=14.16"
+ },
+ "funding": {
+ "url": "https://github.com/yeoman/stringify-object?sponsor=1"
+ }
+ },
+ "node_modules/strip-ansi": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
+ "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+ }
+ },
+ "node_modules/strip-bom": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
+ "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/strip-final-newline": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz",
+ "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/strip-json-comments": {
@@ -5479,6 +7744,12 @@
"node": ">=18"
}
},
+ "node_modules/tiny-invariant": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz",
+ "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==",
+ "license": "MIT"
+ },
"node_modules/tinyglobby": {
"version": "0.2.14",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz",
@@ -5525,7 +7796,6 @@
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
- "dev": true,
"license": "MIT",
"dependencies": {
"is-number": "^7.0.0"
@@ -5534,6 +7804,39 @@
"node": ">=8.0"
}
},
+ "node_modules/toidentifier": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
+ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.6"
+ }
+ },
+ "node_modules/tough-cookie": {
+ "version": "4.1.4",
+ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz",
+ "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "psl": "^1.1.33",
+ "punycode": "^2.1.1",
+ "universalify": "^0.2.0",
+ "url-parse": "^1.5.3"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/tough-cookie/node_modules/universalify": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz",
+ "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4.0.0"
+ }
+ },
"node_modules/ts-api-utils": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz",
@@ -5547,6 +7850,30 @@
"typescript": ">=4.8.4"
}
},
+ "node_modules/ts-morph": {
+ "version": "18.0.0",
+ "resolved": "https://registry.npmjs.org/ts-morph/-/ts-morph-18.0.0.tgz",
+ "integrity": "sha512-Kg5u0mk19PIIe4islUI/HWRvm9bC1lHejK4S0oh1zaZ77TMZAEmQC0sHQYiu2RgCQFZKXz1fMVi/7nOOeirznA==",
+ "license": "MIT",
+ "dependencies": {
+ "@ts-morph/common": "~0.19.0",
+ "code-block-writer": "^12.0.0"
+ }
+ },
+ "node_modules/tsconfig-paths": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz",
+ "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==",
+ "license": "MIT",
+ "dependencies": {
+ "json5": "^2.2.2",
+ "minimist": "^1.2.6",
+ "strip-bom": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
@@ -5575,11 +7902,58 @@
"node": ">= 0.8.0"
}
},
+ "node_modules/type-fest": {
+ "version": "4.41.0",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz",
+ "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==",
+ "license": "(MIT OR CC0-1.0)",
+ "engines": {
+ "node": ">=16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/type-is": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz",
+ "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==",
+ "license": "MIT",
+ "dependencies": {
+ "content-type": "^1.0.5",
+ "media-typer": "^1.1.0",
+ "mime-types": "^3.0.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/type-is/node_modules/mime-db": {
+ "version": "1.54.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
+ "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/type-is/node_modules/mime-types": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz",
+ "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "^1.54.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
"node_modules/typescript": {
"version": "5.7.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz",
"integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==",
- "dev": true,
+ "devOptional": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
@@ -5620,11 +7994,28 @@
"devOptional": true,
"license": "MIT"
},
+ "node_modules/universalify": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
+ "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 10.0.0"
+ }
+ },
+ "node_modules/unpipe": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+ "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/update-browserslist-db": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz",
"integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==",
- "dev": true,
"funding": [
{
"type": "opencollective",
@@ -5655,12 +8046,21 @@
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
"integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
- "dev": true,
"license": "BSD-2-Clause",
"dependencies": {
"punycode": "^2.1.0"
}
},
+ "node_modules/url-parse": {
+ "version": "1.5.10",
+ "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
+ "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==",
+ "license": "MIT",
+ "dependencies": {
+ "querystringify": "^2.1.1",
+ "requires-port": "^1.0.0"
+ }
+ },
"node_modules/use-callback-ref": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz",
@@ -5727,6 +8127,21 @@
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
+ "node_modules/util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
+ "license": "MIT"
+ },
+ "node_modules/vary": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+ "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/vite": {
"version": "6.3.5",
"resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz",
@@ -5827,11 +8242,28 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
+ "node_modules/wcwidth": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz",
+ "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==",
+ "license": "MIT",
+ "dependencies": {
+ "defaults": "^1.0.3"
+ }
+ },
+ "node_modules/web-streams-polyfill": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz",
+ "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
- "dev": true,
"license": "ISC",
"dependencies": {
"isexe": "^2.0.0"
@@ -5853,11 +8285,60 @@
"node": ">=0.10.0"
}
},
+ "node_modules/wrap-ansi": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
+ "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/wrap-ansi/node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/wrap-ansi/node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+ "license": "ISC"
+ },
+ "node_modules/y18n": {
+ "version": "5.0.8",
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
+ "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/yallist": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
- "dev": true,
"license": "ISC"
},
"node_modules/yaml": {
@@ -5874,6 +8355,33 @@
"node": ">= 14.6"
}
},
+ "node_modules/yargs": {
+ "version": "17.7.2",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
+ "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
+ "license": "MIT",
+ "dependencies": {
+ "cliui": "^8.0.1",
+ "escalade": "^3.1.1",
+ "get-caller-file": "^2.0.5",
+ "require-directory": "^2.1.1",
+ "string-width": "^4.2.3",
+ "y18n": "^5.0.5",
+ "yargs-parser": "^21.1.1"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/yargs-parser": {
+ "version": "21.1.1",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
+ "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/yocto-queue": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
@@ -5886,6 +8394,36 @@
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
+ },
+ "node_modules/yoctocolors-cjs": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.3.tgz",
+ "integrity": "sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/zod": {
+ "version": "3.25.76",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
+ "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/colinhacks"
+ }
+ },
+ "node_modules/zod-to-json-schema": {
+ "version": "3.24.6",
+ "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.6.tgz",
+ "integrity": "sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg==",
+ "license": "ISC",
+ "peerDependencies": {
+ "zod": "^3.24.1"
+ }
}
}
-}
+}
\ No newline at end of file
diff --git a/frontend/package.json b/frontend/package.json
index f4e63c6..d3e7d57 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -11,26 +11,29 @@
},
"dependencies": {
"@headlessui/react": "^2.2.7",
+ "@radix-ui/react-accordion": "^1.2.12",
"@radix-ui/react-checkbox": "^1.1.5",
"@radix-ui/react-label": "^2.1.3",
"@radix-ui/react-popover": "^1.1.7",
"@radix-ui/react-select": "^2.1.7",
"@radix-ui/react-slot": "^1.2.0",
"@tailwindcss/vite": "^4.1.3",
+ "@tanstack/react-query": "^5.85.5",
"axios": "^1.8.4",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"date-fns": "^3.6.0",
- "framer-motion": "^12.7.4",
+ "framer-motion": "^12.23.12",
"jwt-decode": "^4.0.0",
"lucide-react": "^0.488.0",
- "react": "^19.1.1",
+ "react": "^19.1.0",
"react-day-picker": "^8.10.1",
- "react-dom": "^19.1.1",
+ "react-dom": "^19.0.0",
"react-hook-form": "^7.55.0",
"react-icons": "^5.5.0",
"react-router-dom": "^7.5.0",
"react-select": "^5.10.1",
+ "shadcn": "^2.10.0",
"sweetalert2": "^11.6.13",
"swiper": "^11.2.6",
"tailwind-merge": "^3.2.0",
@@ -39,6 +42,7 @@
},
"devDependencies": {
"@eslint/js": "^9.21.0",
+ "@heroicons/react": "^2.2.0",
"@types/node": "^22.14.1",
"@types/react": "^19.0.10",
"@types/react-dom": "^19.0.4",
@@ -51,4 +55,4 @@
"typescript-eslint": "^8.24.1",
"vite": "^6.2.0"
}
-}
+}
\ No newline at end of file
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index db1fe25..095774f 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -2,10 +2,10 @@
import React from 'react';
import { BrowserRouter as Router, Route, Routes } from 'react-router-dom';
-import LoginPage from './pages/Auth';
-import { HomePage } from './pages/Home';
-import { ProfilPage } from './pages/Profil';
-import { ShotgunPage } from './pages/Shotgun';
+import LoginPage from './pages/auth';
+import { HomePage } from './pages/home';
+import { ProfilPage } from './pages/profil';
+import { ShotgunPage } from './pages/shotgun';
import {
AdminPageRole,
AdminPageTeam,
@@ -17,24 +17,25 @@ import {
AdminPageEmail,
AdminPageUser,
AdminPageNews,
- AdminPageGames
-} from './pages/Admin';
+ AdminPageGames,
+ AdminPageTent
+} from './pages/admin';
import ProtectedRoute from './components/utils/protectedroute';
import AdminRoute from './components/utils/adminroute';
-import { PermPage } from './pages/Perm';
-import { ChallPage } from './pages/Challenge';
-import { ParrainagePage } from './pages/Parrainage';
-import { RegisterPage } from './pages/Register';
-import { ResetPasswordPage } from './pages/ResetPassword'
-import { WeiPage } from './pages/Wei';
-import { SdiPage } from './pages/Sdi';
-import { NewsPage } from './pages/News';
-import { DiscordPage } from './pages/Discord';
-import { Roadbook } from './pages/Roadbook';
+import { AvailablePermanencesPage, MyPermanencesPage, RespoCallPage } from './pages/perm';
+import { ChallPage } from './pages/challenge';
+import { ParrainagePage } from './pages/parrainage';
+import { RegisterPage } from './pages/register';
+import { ResetPasswordPage } from './pages/resetPassword'
+import { WeiPage } from './pages/wei';
+import { SdiPage } from './pages/sdi';
+import { NewsPage } from './pages/news';
+import { DiscordPage } from './pages/discord';
import PrivateRoute from './components/utils/privateroute';
-import { GamesPage } from './pages/Games';
-import { FoodPage } from './pages/Food';
+import { GamesPage } from './pages/games';
+import { FoodPage } from './pages/food';
+import { PlanningsPage } from './pages/plannings';
const App: React.FC = () => {
@@ -64,6 +65,7 @@ const App: React.FC = () => {
{/* Utilisateurs connectés */}
} />
+ } />
} />
} />
} />
@@ -75,7 +77,9 @@ const App: React.FC = () => {
{/* Étudiant et Admin */}
} />
- } />
+ } />
+ } />
+ } />
} />
{/* ResposCE et Admin */}
@@ -95,6 +99,7 @@ const App: React.FC = () => {
} />
} />
} />
+ } />
diff --git a/frontend/src/components/Admin/AdminChallenge/adminChalengeList.tsx b/frontend/src/components/Admin/AdminChallenge/adminChalengeList.tsx
new file mode 100644
index 0000000..bf2fa71
--- /dev/null
+++ b/frontend/src/components/Admin/AdminChallenge/adminChalengeList.tsx
@@ -0,0 +1,69 @@
+import { Card } from "../../ui/card";
+import { Button } from "../../ui/button";
+import { Challenge } from "../../../interfaces/challenge.interface";
+import { deleteChallenge } from "../../../services/requests/challenge.service";
+import Swal from "sweetalert2";
+import { Trash2, Edit } from "lucide-react";
+
+interface Props {
+ challenges: Challenge[];
+ refreshChallenges: () => void;
+ onEdit: (c: Challenge) => void;
+}
+
+const AdminChallengeList = ({ challenges, refreshChallenges, onEdit }: Props) => {
+ const handleDelete = async (id: number) => {
+ const confirm = await Swal.fire({
+ icon: "warning",
+ title: "Supprimer ?",
+ text: "Cette action est irréversible",
+ showCancelButton: true,
+ confirmButtonText: "Oui, supprimer",
+ cancelButtonText: "Annuler",
+ });
+
+ if (!confirm.isConfirmed) return;
+
+ await deleteChallenge(id);
+ Swal.fire({ icon: "success", title: "Challenge supprimé !" });
+ refreshChallenges();
+ };
+
+ return (
+
+ 📜 Challenges
+
+ {challenges.map((c) => (
+
+
+
{c.title}
+
{c.description}
+
Catégorie : {c.category}
+
Points : {c.points}
+
+
+
+ onEdit(c)}
+ className="bg-yellow-600 hover:bg-yellow-700 text-white flex items-center gap-2"
+ >
+ Modifier
+
+ handleDelete(c.id)}
+ className="bg-red-600 hover:bg-red-700 text-white flex items-center gap-2"
+ >
+ Supprimer
+
+
+
+ ))}
+
+
+ );
+};
+
+export default AdminChallengeList;
diff --git a/frontend/src/components/Admin/AdminChallenge/adminChallengeAddPointsForm.tsx b/frontend/src/components/Admin/AdminChallenge/adminChallengeAddPointsForm.tsx
new file mode 100644
index 0000000..5903831
--- /dev/null
+++ b/frontend/src/components/Admin/AdminChallenge/adminChallengeAddPointsForm.tsx
@@ -0,0 +1,102 @@
+import { useState, useEffect } from "react";
+import { Input } from "../../ui/input";
+import { Button } from "../../ui/button";
+import Select from "react-select";
+import { getAllFactionsAdmin } from "../../../services/requests/faction.service";
+import { Faction } from "../../../interfaces/faction.interface";
+import Swal from "sweetalert2";
+import { addPointsToFaction } from "../../../services/requests/challenge.service";
+
+
+export const AdminChallengeAddPointsForm = () => {
+ const [factions, setFactions] = useState([]);
+ const [title, setTitle] = useState("");
+ const [factionId, setFactionId] = useState(null);
+ const [points, setPoints] = useState("");
+ const [reason, setReason] = useState("");
+
+ useEffect(() => {
+ const fetchFactions = async () => {
+ try {
+ const response = await getAllFactionsAdmin();
+ setFactions(response);
+ } catch (error) {
+ Swal.fire("Erreur", "Impossible de récupérer les factions", "error");
+ }
+ };
+ fetchFactions();
+ }, []);
+
+ const handleSubmit = async () => {
+ if (!title || !factionId || !points || !reason) {
+ Swal.fire("Champs manquants", "Tous les champs doivent être remplis", "warning");
+ return;
+ }
+
+ const pointsNumber = Number(points);
+ if (isNaN(pointsNumber)) {
+ Swal.fire("Erreur", "Veuillez entrer un nombre valide pour les points", "error");
+ return;
+ }
+
+ try {
+ const result = await addPointsToFaction({
+ title,
+ factionId,
+ points: pointsNumber,
+ reason,
+ });
+
+ Swal.fire({
+ icon: "success",
+ title: "Succès",
+ text: result.message,
+ timer: 1500,
+ showConfirmButton: false,
+ });
+
+ setTitle("");
+ setFactionId(null);
+ setPoints("");
+ setReason("");
+ } catch (error) {
+ Swal.fire("Erreur", "❌ Une erreur est survenue lors de l'ajout des points", "error");
+ }
+ };
+
+ return (
+
+ );
+};
diff --git a/frontend/src/components/Admin/AdminChallenge/adminChallengeEditor.tsx b/frontend/src/components/Admin/AdminChallenge/adminChallengeEditor.tsx
new file mode 100644
index 0000000..3d8a3a8
--- /dev/null
+++ b/frontend/src/components/Admin/AdminChallenge/adminChallengeEditor.tsx
@@ -0,0 +1,110 @@
+import { useState, useEffect } from "react";
+import { Button } from "../../ui/button";
+import { Input } from "../../ui/input";
+import { Card } from "../../ui/card";
+import Select from "react-select";
+import { createChallenge, updateChallenge } from "../../../services/requests/challenge.service";
+import { Challenge } from "../../../interfaces/challenge.interface";
+import Swal from "sweetalert2";
+import { Loader2 } from "lucide-react";
+
+interface Props {
+ editingChallenge: Challenge | null;
+ setEditingChallenge: (c: Challenge | null) => void;
+ refreshChallenges: () => void;
+}
+
+const categoryOptions = [
+ { value: "Team", label: "Team" },
+ { value: "Faction", label: "Faction" },
+ { value: "User", label: "User" },
+ { value: "Autre", label: "Autre" },
+];
+
+const ChallengeEditor = ({ editingChallenge, setEditingChallenge, refreshChallenges }: Props) => {
+ const [form, setForm] = useState({ title: "", description: "", category: "", points: 0 });
+ const [loading, setLoading] = useState(false);
+
+ useEffect(() => {
+ if (editingChallenge) {
+ setForm({
+ title: editingChallenge.title,
+ description: editingChallenge.description,
+ category: editingChallenge.category,
+ points: editingChallenge.points,
+ });
+ }
+ }, [editingChallenge]);
+
+ const resetForm = () => {
+ setForm({ title: "", description: "", category: "", points: 0 });
+ setEditingChallenge(null);
+ };
+
+ const handleSubmit = async () => {
+ setLoading(true);
+ try {
+ if (editingChallenge) {
+ await updateChallenge({ id: editingChallenge.id, ...form });
+ Swal.fire({ icon: "success", title: "Challenge mis à jour !" });
+ } else {
+ await createChallenge(form);
+ Swal.fire({ icon: "success", title: "Challenge créé !" });
+ }
+ refreshChallenges();
+ resetForm();
+ } catch {
+ Swal.fire({ icon: "error", title: "Erreur lors de l'enregistrement" });
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ return (
+
+
+ {editingChallenge ? "✏️ Modifier Challenge" : "🛠️ Créer Challenge"}
+
+
+ setForm({ ...form, title: e.target.value })}
+ />
+ setForm({ ...form, description: e.target.value })}
+ />
+ opt.value === form.category)}
+ onChange={(opt) => setForm({ ...form, category: opt?.value || "" })}
+ />
+ setForm({ ...form, points: Number(e.target.value) })}
+ />
+
+
+
+ {loading ? : "Enregistrer"}
+
+ {editingChallenge && (
+
+ Annuler
+
+ )}
+
+
+ );
+};
+
+export default ChallengeEditor;
diff --git a/frontend/src/components/Admin/AdminChallenge/adminChallengeValidatedList.tsx b/frontend/src/components/Admin/AdminChallenge/adminChallengeValidatedList.tsx
new file mode 100644
index 0000000..8facf02
--- /dev/null
+++ b/frontend/src/components/Admin/AdminChallenge/adminChallengeValidatedList.tsx
@@ -0,0 +1,106 @@
+import { useState, useEffect } from "react";
+import { Button } from "../../ui/button";
+import { getAllChallengesValidates, unvalidateChallenge } from "../../../services/requests/challenge.service";
+import Swal from "sweetalert2";
+
+
+export const AdminValidatedChallengesList = () => {
+ const [validatedChallenges, setValidatedChallenges] = useState([]);
+
+ const fetchValidatedChallenges = async () => {
+ try {
+ const challenges = await getAllChallengesValidates();
+ setValidatedChallenges(challenges);
+ } catch {
+ Swal.fire("Erreur", "Impossible de récupérer les challenges validés", "error");
+ }
+ };
+
+ useEffect(() => {
+ fetchValidatedChallenges();
+ }, []);
+
+ const handleUnvalidate = async (challengeId: number, factionId: number, teamId: number, userId: number) => {
+ const confirm = await Swal.fire({
+ title: "Confirmer la dévalidation ?",
+ text: "Cette action retirera la validation du challenge.",
+ icon: "warning",
+ showCancelButton: true,
+ confirmButtonColor: "#e3342f",
+ cancelButtonColor: "#6b7280",
+ confirmButtonText: "Oui, dévalider",
+ cancelButtonText: "Annuler",
+ });
+
+ if (!confirm.isConfirmed) return;
+
+ try {
+ const result = await unvalidateChallenge({ challengeId, factionId, teamId, userId });
+ Swal.fire("Succès", result.message, "success");
+ fetchValidatedChallenges();
+ } catch {
+ Swal.fire("Erreur", "❌ Une erreur est survenue lors de la dévalidation", "error");
+ }
+ };
+
+ return (
+
+
+
📋 Challenges validés
+
+ {validatedChallenges.length === 0 ? (
+
Aucun challenge validé pour le moment.
+ ) : (
+
+ {validatedChallenges.map((challenge) => (
+
+
+
+
{challenge.challenge_name}
+
{challenge.challenge_categorie}
+
{challenge.challenge_description}
+
+
+
+
+ Points : {challenge.points}
+
+
+ Validé le : {" "}
+ {new Date(challenge.validated_at).toLocaleDateString()}
+
+
+
+
+
+
+
Destinataire :
+
{challenge.target_faction_name}
+
{challenge.target_team_name}
+
+ {challenge.target_user_firstname} {challenge.target_user_lastname}
+
+
+
+
+ handleUnvalidate(
+ challenge.challenge_id,
+ challenge.target_faction_id,
+ challenge.target_team_id,
+ challenge.target_user_id
+ )
+ }
+ className="bg-red-600 hover:bg-red-700 text-white text-sm px-4 py-2 rounded"
+ >
+ Invalider
+
+
+
+ ))}
+
+ )}
+
+
+ );
+};
diff --git a/frontend/src/components/Admin/AdminPerm/adminPermAction.tsx b/frontend/src/components/Admin/AdminPerm/adminPermAction.tsx
new file mode 100644
index 0000000..e44ec01
--- /dev/null
+++ b/frontend/src/components/Admin/AdminPerm/adminPermAction.tsx
@@ -0,0 +1,87 @@
+import { Button } from "../../../components/ui/button";
+import Swal from "sweetalert2";
+
+import { openPermanence, closePermanence } from "../../../services/requests/permanence.service";
+import { Permanence } from "../../../interfaces/permanence.interface";
+
+interface PermanenceActionsProps {
+ permanences: Permanence[];
+ onRefresh: () => void;
+}
+
+// Fonction utilitaire pour "normaliser" une date au début de journée (00:00:00)
+const normalizeDate = (d: Date): Date => {
+ return new Date(d.getFullYear(), d.getMonth(), d.getDate());
+};
+
+const inSevenDays = (): Date => {
+ const d = new Date();
+ d.setDate(d.getDate() + 7);
+ return normalizeDate(d); // → seuil à J+7 mais à 00h00
+};
+
+const PermanenceActions: React.FC = ({ permanences, onRefresh }) => {
+ const handleOpenAll = async (): Promise => {
+ const confirm = await Swal.fire({
+ title: "Ouvrir toutes à J+7 ?",
+ text: "Toutes les permanences commençant avant J+7 seront ouvertes.",
+ icon: "question",
+ showCancelButton: true,
+ confirmButtonText: "Ouvrir",
+ cancelButtonText: "Annuler",
+ });
+ if (!confirm.isConfirmed) return;
+
+ const threshold = inSevenDays().getTime();
+ const toOpen = permanences.filter((p) => {
+ const permDate = normalizeDate(new Date(p.start_at)).getTime();
+ return permDate <= threshold;
+ });
+
+ try {
+ await Promise.all(toOpen.map((p) => openPermanence(p.id)));
+ await Swal.fire("Ouvertes", "Toutes les permanences ont été ouvertes !", "success");
+ onRefresh();
+ } catch {
+ Swal.fire("Erreur", "Une erreur est survenue lors de l'ouverture", "error");
+ }
+ };
+
+ const handleCloseAll = async (): Promise => {
+ const confirm = await Swal.fire({
+ title: "Fermer toutes à J+7 ?",
+ text: "Toutes les permanences commençant avant J+7 seront fermées.",
+ icon: "warning",
+ showCancelButton: true,
+ confirmButtonText: "Fermer",
+ cancelButtonText: "Annuler",
+ });
+ if (!confirm.isConfirmed) return;
+
+ const threshold = inSevenDays().getTime();
+ const toClose = permanences.filter((p) => {
+ const permDate = normalizeDate(new Date(p.start_at)).getTime();
+ return permDate <= threshold;
+ });
+ try {
+ await Promise.all(toClose.map((p) => closePermanence(p.id)));
+ await Swal.fire("Fermées", "Toutes les permanences ont été fermées !", "success");
+ onRefresh();
+ } catch {
+ Swal.fire("Erreur", "Une erreur est survenue lors de la fermeture", "error");
+ }
+ };
+
+ return (
+
+ void handleOpenAll()} className="bg-blue-600 hover:bg-blue-700 text-white px-6 py-2 rounded-xl">
+ 📅 Ouvrir toutes à J+7
+
+ void handleCloseAll()} className="bg-red-600 hover:bg-red-700 text-white px-6 py-2 rounded-xl">
+ 🛑 Fermer toutes à J+7
+
+
+ );
+};
+
+export default PermanenceActions;
diff --git a/frontend/src/components/Admin/AdminPerm/adminPermForm.tsx b/frontend/src/components/Admin/AdminPerm/adminPermForm.tsx
new file mode 100644
index 0000000..d68a57a
--- /dev/null
+++ b/frontend/src/components/Admin/AdminPerm/adminPermForm.tsx
@@ -0,0 +1,220 @@
+import { useState, useEffect } from "react";
+import { Card } from "../../../components/ui/card";
+import { Input } from "../../../components/ui/input";
+import { Textarea } from "../../../components/ui/textarea";
+import { Button } from "../../../components/ui/button";
+import Select from "react-select";
+import Swal from "sweetalert2";
+
+import {
+ createPermanence,
+ updatePermanence,
+} from "../../../services/requests/permanence.service";
+
+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;
+ editPermanence: Permanence | null;
+ onRefresh: () => void;
+ onCancelEdit: () => void;
+}
+
+const PermanenceForm = ({
+ editMode,
+ editPermanence,
+ onRefresh,
+ onCancelEdit,
+}: PermanenceFormProps) => {
+ const [name, setName] = useState("");
+ const [desc, setDesc] = useState("");
+ const [location, setLocation] = 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();
+ const [users, setUsers] = useState([]);
+
+ useEffect(() => {
+ const fetchUsers = async () => {
+ try {
+ const data = await getUsers();
+ setUsers(data);
+ } catch {
+ Swal.fire("Erreur", "Impossible de charger les utilisateurs", "error");
+ }
+ };
+
+ fetchUsers();
+ }, []);
+
+ useEffect(() => {
+ if (editMode && editPermanence) {
+ setName(editPermanence.name);
+ setDesc(editPermanence.description);
+ setLocation(editPermanence.location);
+ setStartAt(formatDateForInput(editPermanence.start_at));
+ setEndAt(formatDateForInput(editPermanence.end_at));
+ setCapacity(editPermanence.capacity);
+ setDifficulty(editPermanence.difficulty);
+ if (editPermanence.respo) {
+ setRespo(editPermanence.respo);
+ }
+ }
+ }, [editMode, editPermanence]);
+
+ const handleSubmit = async () => {
+ if (
+ !name ||
+ !desc ||
+ !location ||
+ !startAt ||
+ !endAt ||
+ !capacity ||
+ !difficulty
+ ) {
+ Swal.fire("Erreur", "Veuillez remplir tous les champs", "warning");
+ return;
+ }
+
+ const respoId =
+ respo && !isNaN(Number(respo.userId)) ? Number(respo.userId) : null;
+
+ try {
+ const payload = {
+ name,
+ description: desc,
+ location,
+ start_at: formatDateForInput(startAt),
+ end_at: formatDateForInput(endAt),
+ capacity,
+ difficulty,
+ respoId,
+ };
+
+ if (editMode && editPermanence) {
+ await updatePermanence(editPermanence.id, payload);
+ Swal.fire("Succès", "Permanence mise à jour", "success");
+ onCancelEdit();
+ } else {
+ await createPermanence(payload);
+ Swal.fire("Succès", "Permanence créée", "success");
+ }
+
+ resetForm();
+ onRefresh();
+ } catch (err: any) {
+ Swal.fire("Erreur", err.response.data.message, "error");
+ }
+ };
+
+ const resetForm = () => {
+ setName("");
+ setDesc("");
+ setLocation("");
+ setStartAt("");
+ setEndAt("");
+ setCapacity(0);
+ setDifficulty(0);
+ setRespo(null);
+ };
+
+ const respoOptions = users.map((user) => ({
+ value: user.userId,
+ label: `${user.firstName} ${user.lastName}`,
+ }));
+
+ const selectedRespoOption = respo
+ ? { value: respo.userId, label: `${respo.firstName} ${respo.lastName}` }
+ : null;
+
+ return (
+
+
+
+ );
+};
+
+export default PermanenceForm;
diff --git a/frontend/src/components/Admin/AdminPerm/adminPermImport.tsx b/frontend/src/components/Admin/AdminPerm/adminPermImport.tsx
new file mode 100644
index 0000000..a2658f8
--- /dev/null
+++ b/frontend/src/components/Admin/AdminPerm/adminPermImport.tsx
@@ -0,0 +1,90 @@
+import { useState } from "react";
+import { importPermanenceCSV } from "../../../services/requests/permanence.service";
+import { Button } from "../../ui/button";
+
+export const ImportPermCSV = () => {
+ const [file, setFile] = useState(null);
+ const [message, setMessage] = useState("");
+
+ const handleFileChange = (e: React.ChangeEvent) => {
+ const selectedFile = e.target.files?.[0];
+ if (selectedFile) {
+ setFile(selectedFile);
+ }
+ };
+
+ const handleFileUpload = async () => {
+ if (!file) {
+ setMessage("Veuillez sélectionner un fichier CSV.");
+ return;
+ }
+
+ const formData = new FormData();
+ formData.append("file", file);
+
+ try {
+ const response = await importPermanenceCSV(formData);
+ setMessage(response.message);
+ } catch (error) {
+ console.error(error);
+ setMessage("Erreur lors de l'import du fichier CSV.");
+ }
+ };
+
+ return (
+
+
+ Importer un fichier CSV pour les permanences
+
+
+
+ Uploadez un fichier CSV contenant les permanences à importer.
+
+
+
+
+
+
+ 📥 Importer le fichier
+
+
+
+ {message && (
+
+ {message}
+
+ )}
+
+
+
+ 📄 Exemple de fichier CSV :
+
+
+ {`name,description,location,start_at,end_at,capacity,difficulty,
+Permanence 1,Accueil matin,A001,2025-05-01T08:00,2025-05-01T10:00,3,10
+Permanence 2,Accueil après-midi,A002,2025-05-02T14:00,2025-05-02T16:00,4,15`}
+
+
+ Le fichier doit être encodé en UTF-8 et utiliser une virgule comme séparateur. Les dates doivent être au format
+
+ aaaa-mm-jjThh:mm
+ .
+
+
+
+ );
+};
\ No newline at end of file
diff --git a/frontend/src/components/Admin/AdminPerm/adminPermList.tsx b/frontend/src/components/Admin/AdminPerm/adminPermList.tsx
new file mode 100644
index 0000000..dbff9b3
--- /dev/null
+++ b/frontend/src/components/Admin/AdminPerm/adminPermList.tsx
@@ -0,0 +1,139 @@
+import { Button } from "../../../components/ui/button";
+import { Card } from "../../../components/ui/card";
+import { Accordion, AccordionItem, AccordionTrigger, AccordionContent } from "../../../components/ui/accordion";
+import { ChevronDown } from "lucide-react";
+import Swal from "sweetalert2";
+import { format } from "date-fns";
+import { fr } from "date-fns/locale";
+
+import PermanenceMembers from "./adminPermMembers";
+import {
+ deletePermanence,
+ openPermanence,
+ closePermanence,
+} from "../../../services/requests/permanence.service";
+
+import { Permanence } from "../../../interfaces/permanence.interface";
+import { User } from "../../../interfaces/user.interface";
+
+interface PermanenceListProps {
+ permanences: Permanence[];
+ users: User[];
+ onRefresh: () => void;
+ onEdit: (perm: Permanence) => void;
+}
+
+const PermanenceList = ({ permanences, users, onRefresh, onEdit }: PermanenceListProps) => {
+ const handleDelete = async (id: number) => {
+ const result = await Swal.fire({
+ title: "Êtes-vous sûr ?",
+ text: "Cette action est irréversible.",
+ icon: "warning",
+ showCancelButton: true,
+ confirmButtonText: "Oui, supprimer",
+ cancelButtonText: "Annuler",
+ });
+
+ if (result.isConfirmed) {
+ try {
+ await deletePermanence(id);
+ Swal.fire("Supprimée ✅", "La permanence a été supprimée", "success");
+ onRefresh();
+ } catch {
+ Swal.fire("Erreur", "Impossible de supprimer", "error");
+ }
+ }
+ };
+
+ // Tri + groupement par jour
+ const sortedPermanences = [...permanences].sort(
+ (a, b) => new Date(a.start_at).getTime() - new Date(b.start_at).getTime()
+ );
+
+ const groupedByDay = sortedPermanences.reduce((groups: Record, perm) => {
+ const dateKey = format(new Date(perm.start_at), "EEEE dd MMMM yyyy", { locale: fr });
+ if (!groups[dateKey]) groups[dateKey] = [];
+ groups[dateKey].push(perm);
+ return groups;
+ }, {});
+
+ return (
+
+ {Object.entries(groupedByDay).map(([day, perms]) => (
+
+
+ {day.charAt(0).toUpperCase() + day.slice(1)}
+
+
+
+ {perms.map((perm) => (
+
+
+ {/* Colonne gauche : infos + actions */}
+
+
{perm.name}
+
{perm.description}
+
+
+
📍 Lieu : {perm.location}
+
+ 🕒 Début : {" "}
+ {format(new Date(perm.start_at), "HH:mm", { locale: fr })}
+
+
+ 🕔 Fin : {" "}
+ {format(new Date(perm.end_at), "HH:mm", { locale: fr })}
+
+
👥 Capacité : {perm.capacity}
+
🎚️ Difficulté : {perm.difficulty}
+
+ 👤 Responsable : {" "}
+ {perm.respo ? `${perm.respo.firstName} ${perm.respo.lastName}` : "Aucun"}
+
+
+
+ {/* Actions */}
+
+ {perm.is_open ? (
+ closePermanence(perm.id).then(onRefresh)}
+ className="bg-orange-600 text-white"
+ >
+ Fermer
+
+ ) : (
+ openPermanence(perm.id).then(onRefresh)}
+ className="bg-blue-600 text-white"
+ >
+ Ouvrir
+
+ )}
+ onEdit(perm)} className="bg-yellow-500 text-white">
+ ✏️ Éditer
+
+ handleDelete(perm.id)} className="bg-red-600 text-white">
+ 🗑️ Supprimer
+
+
+
+
+ {/* Colonne droite : membres */}
+
+
+
+ ))}
+
+
+ ))}
+
+ );
+};
+
+export default PermanenceList;
diff --git a/frontend/src/components/Admin/AdminPerm/adminPermMembers.tsx b/frontend/src/components/Admin/AdminPerm/adminPermMembers.tsx
new file mode 100644
index 0000000..9005784
--- /dev/null
+++ b/frontend/src/components/Admin/AdminPerm/adminPermMembers.tsx
@@ -0,0 +1,195 @@
+import { useEffect, useMemo, useState } from "react";
+import Select, { SingleValue } from "react-select";
+import { Button } from "../../../components/ui/button";
+import { Card } from "../../../components/ui/card";
+import Swal from "sweetalert2";
+
+import {
+ getUsersByPermanence,
+ addUserToPermanence,
+ removeUserFromPermanence,
+ claimedMember,
+} from "../../../services/requests/permanence.service";
+import { Permanence } from "../../../interfaces/permanence.interface";
+import { User } from "../../../interfaces/user.interface";
+
+
+interface PermanenceMembersProps {
+ perm: Permanence;
+ users: User[];
+ onRefresh: () => void;
+}
+
+interface PermanenceMember extends User {
+ claimed: boolean;
+}
+
+
+interface Option {
+ value: User;
+ label: string;
+}
+
+const PermanenceMembers: React.FC = ({ perm, users, onRefresh }) => {
+ const [expanded, setExpanded] = useState(false);
+ const [members, setMembers] = useState([]);
+ const [selectedUser, setSelectedUser] = useState(null);
+ const [loading, setLoading] = useState(false);
+
+ const options: Option[] = useMemo(() => {
+ const memberIds = new Set(members.map((m) => m.userId));
+ return users
+ .filter((u) => !memberIds.has(u.userId))
+ .map((u) => ({
+ value: u,
+ label:
+ (u.firstName || u.lastName)
+ ? `${u.firstName ?? ""} ${u.lastName ?? ""}`.trim()
+ : `Utilisateur ${u.userId}`,
+ }));
+ }, [users, members]);
+
+ const fetchMembers = async (): Promise => {
+ try {
+ setLoading(true);
+ const res = await getUsersByPermanence(perm.id);
+ setMembers((res.data as PermanenceMember[]) ?? []);
+ } catch {
+ Swal.fire("Erreur", "Impossible de récupérer les membres", "error");
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ useEffect(() => {
+ if (expanded) {
+ void fetchMembers();
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [expanded, perm.id]);
+
+ const handleAdd = async (): Promise => {
+ if (!selectedUser) return;
+ try {
+ await addUserToPermanence(perm.id, selectedUser.userId);
+ await Swal.fire("Ajouté", "Membre ajouté à la permanence", "success");
+ setSelectedUser(null);
+ await fetchMembers();
+ onRefresh();
+ } catch {
+ Swal.fire("Erreur", "Impossible d'ajouter ce membre", "error");
+ }
+ };
+
+ const handleRemove = async (userId: number): Promise => {
+ const result = await Swal.fire({
+ title: "Retirer ce membre ?",
+ icon: "warning",
+ showCancelButton: true,
+ confirmButtonText: "Retirer",
+ cancelButtonText: "Annuler",
+ });
+
+ if (!result.isConfirmed) return;
+
+ try {
+ await removeUserFromPermanence(perm.id, userId);
+ await Swal.fire("Retiré", "Membre retiré de la permanence", "success");
+ await fetchMembers();
+ onRefresh();
+ } catch {
+ Swal.fire("Erreur", "Suppression impossible", "error");
+ }
+ };
+
+ const handleToggleClaim = async (user: PermanenceMember): Promise => {
+ try {
+ const newClaimedStatus = !user.claimed;
+ await claimedMember(user.userId, perm.id, newClaimedStatus);
+
+ await Swal.fire(
+ newClaimedStatus ? "Présence confirmée" : "Présence retirée",
+ `Le membre est marqué comme ${newClaimedStatus ? "présent" : "absent"}`,
+ newClaimedStatus ? "success" : "info"
+ );
+
+ await fetchMembers();
+ onRefresh();
+ } catch {
+ Swal.fire("Erreur", "Impossible de modifier la présence", "error");
+ }
+ };
+
+ return (
+
+
+
Membres
+ setExpanded((e) => !e)}>
+ {expanded ? "Masquer" : "👥 Voir membres"}
+
+
+
+ {expanded && (
+
+ {loading ? (
+
Chargement…
+ ) : members.length === 0 ? (
+
Aucun membre pour l’instant.
+ ) : (
+
+ )}
+
+
+
+
+ options={options}
+ value={selectedUser ? { value: selectedUser, label: options.find(o => o.value.userId === selectedUser.userId)?.label ?? "" } : null}
+ onChange={(opt: SingleValue) => setSelectedUser(opt?.value ?? null)}
+ placeholder="Sélectionner un utilisateur à ajouter"
+ isClearable
+ />
+
+
+
void handleAdd()} className="bg-green-600 hover:bg-green-700 text-white">
+ Ajouter
+
+
+
+
+ Attention : en tant qu'Admin vous pouvez bypass les quotas
+
+
+ )}
+
+ );
+};
+
+export default PermanenceMembers;
diff --git a/frontend/src/components/Admin/adminChallenge.tsx b/frontend/src/components/Admin/adminChallenge.tsx
deleted file mode 100644
index 2634d01..0000000
--- a/frontend/src/components/Admin/adminChallenge.tsx
+++ /dev/null
@@ -1,483 +0,0 @@
-import { useEffect, useState } from "react";
-import { Button } from "../ui/button";
-import { Input } from "../ui/input";
-import { Card } from "../ui/card";
-import {
- getAllChallenges,
- createChallenge,
- updateChallenge,
- deleteChallenge,
- validateChallenge,
- addPointsToFaction,
- getAllChallengesValidates,
- unvalidateChallenge,
-} from "../../services/requests/challenge.service";
-import { Challenge } from "../../interfaces/challenge.interface";
-import Select from "react-select";
-import { getAllTeams } from "../../services/requests/team.service";
-import { getAllFactionsAdmin } from "../../services/requests/faction.service";
-import { getUsers } from "../../services/requests/user.service";
-import { Team } from "../../interfaces/team.interface";
-import { Faction } from "../../interfaces/faction.interface";
-import { User } from "../../interfaces/user.interface";
-
-
-export const AdminChallengeForm = () => {
- const [challenges, setChallenges] = useState([]);
- const [newChallenge, setNewChallenge] = useState({ title: "", description: "", category: "", points: 0 });
- const [editingChallenge, setEditingChallenge] = useState(null);
-
- const [showValidationFormForId, setShowValidationFormForId] = useState(null);
- const [validationType, setValidationType] = useState<"user" | "team" | "faction" | null>(null);
- const [selectedTargetId, setSelectedTargetId] = useState(null);
- const [teams, setTeams] = useState([]);
- const [factions, setFactions] = useState([]);
- const [users, setUsers] = useState([]);
-
- type ValidationTarget = "user" | "team" | "faction";
-
- const typeOptions: { value: ValidationTarget; label: string }[] = [
- { value: "user", label: "Utilisateur" },
- { value: "team", label: "Équipe" },
- { value: "faction", label: "Faction" },
- ];
-
-
- const categoryOptions: { value: string; label: string }[] = [
- { value: "Team", label: "Team" },
- { value: "Faction", label: "Faction" },
- { value: "User", label: "User" },
- { value: "Autre", label: "Autre" },
- ];
-
-
- useEffect(() => {
- fetchChallenges();
- fetchAllTargets();
- }, []);
-
- const fetchChallenges = async () => {
- try {
- const challengeList = await getAllChallenges();
- setChallenges(challengeList);
- } catch (err) {
- console.error("Erreur lors du chargement des challenges", err);
- }
- };
-
- const fetchAllTargets = async () => {
- try {
- const [teamRes, factionRes, userRes] = await Promise.all([
- getAllTeams(),
- getAllFactionsAdmin(),
- getUsers(),
- ]);
- setTeams(teamRes);
- setFactions(factionRes);
- setUsers(userRes);
- } catch (err) {
- console.error("Erreur lors du chargement des cibles", err);
- }
- };
-
-
- const resetForm = () => {
- setNewChallenge({ title: "", description: "", category: "", points: 0 });
- setEditingChallenge(null);
- };
-
- const handleCreateChallenge = async () => {
- try {
- const response = await createChallenge(newChallenge);
- alert(response.message);
- fetchChallenges();
- resetForm();
- } catch (err) {
- console.error("Erreur lors de la création du challenge", err);
- }
- };
-
- const handleUpdateChallenge = async () => {
- if (!editingChallenge) return;
- try {
- const response = await updateChallenge({
- id: editingChallenge.id,
- title: newChallenge.title,
- description: newChallenge.description,
- category: newChallenge.category,
- points: newChallenge.points,
- });
- alert(response.message);
- fetchChallenges();
- fetchAllTargets();
- resetForm();
- } catch (err) {
- console.error("Erreur lors de la mise à jour du challenge", err);
- }
- };
-
- const handleValidateChallenge = async () => {
- if (!showValidationFormForId || !validationType || !selectedTargetId) return;
- try {
- const response = await validateChallenge({
- challengeId: showValidationFormForId,
- type: validationType,
- targetId: selectedTargetId,
- });
- alert(response.message);
- setShowValidationFormForId(null);
- setValidationType(null);
- setSelectedTargetId(null);
- fetchChallenges();
- fetchAllTargets();
- } catch (err) {
- console.error("Erreur lors de la validation du challenge", err);
- }
- };
-
- const handleDeleteChallenge = async (challengeId: number) => {
- try {
- const response = await deleteChallenge(challengeId);
- alert(response.message);
- fetchChallenges();
-
- } catch (err) {
- console.error("Erreur lors de la suppression du challenge", err);
- }
- };
-
- const handleEditClick = (challenge: Challenge) => {
- setEditingChallenge(challenge);
- setNewChallenge({
- title: challenge.title,
- description: challenge.description,
- category: challenge.category,
- points: challenge.points,
- });
- };
-
- return (
-
-
-
- {editingChallenge ? "✏️ Modifier un Challenge" : "🛠️ Créer un Challenge"}
-
-
-
-
-
-
- 📜 Challenges existants
-
- {challenges.map((challenge) => (
-
- {challenge.title}
- {challenge.description}
- Catégorie : {challenge.category}
- Points : {challenge.points}
-
- setShowValidationFormForId(challenge.id)} className="bg-blue-600 hover:bg-blue-700 text-white">
- Valider
-
- handleDeleteChallenge(challenge.id)} className="bg-red-600 hover:bg-red-700 text-white">
- Supprimer
-
- handleEditClick(challenge)} className="bg-yellow-600 hover:bg-yellow-700 text-white">
- Modifier
-
-
- {showValidationFormForId && showValidationFormForId === challenge.id && (
-
-
✅ Valider le challenge
-
-
{
- setValidationType(option?.value ?? null);
- setSelectedTargetId(null); // reset
- }}
- options={typeOptions}
- />
-
- {validationType === "user" && (
- setSelectedTargetId(Number(option?.value))}
- options={users.map((u) => ({
- value: u.userId,
- label: `${u.firstName} ${u.lastName}`,
- }))}
- />
- )}
-
- {validationType === "team" && (
- setSelectedTargetId(Number(option?.value))}
- options={teams.map((t) => ({
- value: t.teamId,
- label: t.name,
- }))}
- />
- )}
-
- {validationType === "faction" && (
- setSelectedTargetId(Number(option?.value))}
- options={factions.map((f) => ({
- value: f.factionId,
- label: f.name,
- }))}
- />
- )}
-
-
-
- ✅ Valider
-
- {
- setShowValidationFormForId(null);
- setValidationType(null);
- setSelectedTargetId(null);
- }}
- className="bg-gray-400 hover:bg-gray-500 text-white"
- >
- ❌ Annuler
-
-
-
- )}
-
- ))
- }
-
-
-
-
- );
-};
-
-/*===========================================================================================================*/
-
-export const AdminChallengeAddPointsForm = () => {
- const [factions, setFactions] = useState([]); // Type explicite pour factions
- const [title, setTitle] = useState("");
- const [factionId, setFactionId] = useState(null);
- const [points, setPoints] = useState("");
- const [reason, setReason] = useState("");
-
- useEffect(() => {
- const fetchFactions = async () => {
- try {
- const response = await getAllFactionsAdmin();
- setFactions(response);
- } catch (error) {
- console.error("Erreur lors de la récupération des factions", error);
- }
- };
-
- fetchFactions();
- }, []);
-
- const handleSubmit = async () => {
- if (!title || !factionId || !points || !reason) {
- alert("Tous les champs doivent être remplis.");
- return;
- }
-
- // Assurez-vous que points est un nombre
- const pointsNumber = Number(points);
- if (isNaN(pointsNumber)) {
- alert("Veuillez entrer un nombre valide pour les points.");
- return;
- }
-
- try {
- const result = await addPointsToFaction({
- title,
- factionId,
- points: pointsNumber,
- reason,
- });
- alert(result.message);
- // Réinitialiser le formulaire après soumission
- setTitle("");
- setFactionId(null);
- setPoints("");
- setReason("");
- } catch (error) {
- alert("❌ Une erreur est survenue lors de l'ajout des points.");
- console.error(error);
- }
- };
-
- return (
-
- );
-};
-
-/*===========================================================================================================*/
-
-export const AdminValidatedChallengesList = () => {
- const [validatedChallenges, setValidatedChallenges] = useState([]);
-
- useEffect(() => {
-
- fetchValidatedChallenges();
- }, []);
-
- const fetchValidatedChallenges = async () => {
- try {
- const challenges = await getAllChallengesValidates();
- setValidatedChallenges(challenges);
- } catch (error) {
- console.error("Erreur lors de la récupération des challenges validés", error);
- }
- };
-
- const handleUnvalidate = async (challengeId: number, factionId: number, teamId: number, userId: number) => {
- try {
- const result = await unvalidateChallenge({ challengeId, factionId, teamId, userId });
- alert(result.message);
- fetchValidatedChallenges();
- } catch (error) {
- alert("❌ Une erreur est survenue lors de la désvalidation.");
- console.error(error);
- }
- };
-
- return (
-
-
-
📋 Challenges validés
-
- {validatedChallenges.length === 0 ? (
-
Aucun challenge validé pour le moment.
- ) : (
-
- {validatedChallenges.map((challenge) => (
-
-
-
-
{challenge.challenge_name}
-
{challenge.challenge_categorie}
-
{challenge.challenge_description}
-
-
-
-
Points : {challenge.points}
-
Validé le : {new Date(challenge.validated_at).toLocaleDateString()}
-
-
-
-
-
-
Destinataire :
-
{challenge.target_faction_name}
-
{challenge.target_team_name}
-
{challenge.target_user_firstname} {challenge.target_user_lastname}
-
-
-
handleUnvalidate(
- challenge.challenge_id,
- challenge.target_faction_id,
- challenge.target_team_id,
- challenge.target_user_id)}
- className="bg-red-600 hover:bg-red-700 text-white text-sm px-4 py-2 rounded"
- >
- Unvalider
-
-
-
- ))}
-
- )}
-
-
- );
-};
\ No newline at end of file
diff --git a/frontend/src/components/Admin/adminEvent.tsx b/frontend/src/components/Admin/adminEvent.tsx
index 426c0d7..339b4f4 100644
--- a/frontend/src/components/Admin/adminEvent.tsx
+++ b/frontend/src/components/Admin/adminEvent.tsx
@@ -1,31 +1,62 @@
import { useState, useEffect } from "react";
-import { toggleShotgun, togglePreRegistration, checkShotgunStatus, checkPreRegisterStatus, checkSDIStatus, checkWEIStatus, toggleSDI, toggleWEI } from "../../services/requests/event.service";
+import {
+ toggleShotgun,
+ togglePreRegistration,
+ checkShotgunStatus,
+ checkPreRegisterStatus,
+ checkSDIStatus,
+ checkWEIStatus,
+ toggleSDI,
+ toggleWEI,
+ checkFoodStatus,
+ toggleFood,
+ toggleChallenge,
+ checkChallengeStatus,
+} from "../../services/requests/event.service";
import { Button } from "../ui/button";
+import { Loader2, CheckCircle, XCircle } from "lucide-react";
+import Swal from "sweetalert2";
export const AdminEvents = () => {
- const [preRegistrationOpen, setPreRegistrationOpen] = useState(false);
- const [shotgunOpen, setShotgunOpen] = useState(false);
- const [sdiOpen, setSdiOpen] = useState(false);
- const [weiOpen, setWeiOpen] = useState(false);
const [loading, setLoading] = useState(false);
const [loadingStatuses, setLoadingStatuses] = useState(true);
- // Récupérer les statuts de pré-inscription et de shotgun au montage du composant
+ const [statuses, setStatuses] = useState({
+ preRegistration: false,
+ shotgun: false,
+ sdi: false,
+ wei: false,
+ food: false,
+ chall : false,
+ });
+
+ // Charger les statuts au montage
useEffect(() => {
const fetchStatuses = async () => {
try {
- const preRegStatus = await checkPreRegisterStatus();
- const shotgunStatus = await checkShotgunStatus();
- const sdiStatus = await checkSDIStatus();
- const weiStatus = await checkWEIStatus();
-
- setPreRegistrationOpen(preRegStatus);
- setShotgunOpen(shotgunStatus);
- setSdiOpen(sdiStatus);
- setWeiOpen(weiStatus);
+ const [preReg, shot, sdi, wei, food, chall] = await Promise.all([
+ checkPreRegisterStatus(),
+ checkShotgunStatus(),
+ checkSDIStatus(),
+ checkWEIStatus(),
+ checkFoodStatus(),
+ checkChallengeStatus()
+ ]);
- } catch (error) {
- alert("Erreur lors de la récupération des statuts.");
+ setStatuses({
+ preRegistration: preReg,
+ shotgun: shot,
+ sdi,
+ wei,
+ food,
+ chall,
+ });
+ } catch {
+ Swal.fire({
+ icon: "error",
+ title: "Erreur",
+ text: "Impossible de récupérer les statuts.",
+ });
} finally {
setLoadingStatuses(false);
}
@@ -33,131 +64,124 @@ export const AdminEvents = () => {
fetchStatuses();
}, []);
- const handleTogglePreRegistration = async () => {
- setLoading(true);
- try {
- await togglePreRegistration(!preRegistrationOpen);
- setPreRegistrationOpen(!preRegistrationOpen);
- alert("Pré-inscription mise à jour !");
- } catch (error : any) {
- alert(error.response.data.message);
- } finally {
- setLoading(false);
- }
- };
-
- const handleToggleShotgun = async () => {
- setLoading(true);
- try {
- await toggleShotgun(!shotgunOpen);
- setShotgunOpen(!shotgunOpen);
- alert("Shotgun mis à jour !");
- } catch (error : any) {
- alert(error.response.data.message);
- } finally {
- setLoading(false);
- }
- };
-
- const handleToggleSDI = async () => {
+ // Fonction générique pour toggle un événement
+ const handleToggle = async (
+ key: keyof typeof statuses,
+ toggleFn: (value: boolean) => Promise,
+ successMsg: string
+ ) => {
setLoading(true);
try {
- await toggleSDI(!sdiOpen);
- setSdiOpen(!sdiOpen);
- alert("SDI mis à jour !");
- } catch (error : any) {
- alert(error.response.data.message);
+ await toggleFn(!statuses[key]);
+ setStatuses((prev) => ({ ...prev, [key]: !prev[key] }));
+ Swal.fire({
+ icon: "success",
+ title: "Succès",
+ text: successMsg,
+ timer: 1500,
+ showConfirmButton: false,
+ });
+ } catch (error: any) {
+ Swal.fire({
+ icon: "error",
+ title: "Erreur",
+ text: error.response?.data?.message || "Une erreur est survenue",
+ });
} finally {
setLoading(false);
}
};
- const handleToggleWEI = async () => {
- setLoading(true);
- try {
- await toggleWEI(!weiOpen);
- setWeiOpen(!weiOpen);
- alert("WEI mis à jour !");
- } catch (error : any) {
- alert(error.response.data.message);
- } finally {
- setLoading(false);
- }
- };
+ // Configuration des événements
+ const events = [
+ {
+ key: "preRegistration" as const,
+ label: "Pré-inscription",
+ toggleFn: togglePreRegistration,
+ },
+ {
+ key: "shotgun" as const,
+ label: "Shotgun",
+ toggleFn: toggleShotgun,
+ },
+ {
+ key: "sdi" as const,
+ label: "SDI (Billetterie)",
+ toggleFn: toggleSDI,
+ },
+ {
+ key: "wei" as const,
+ label: "WEI (Billetterie + Tentes)",
+ toggleFn: toggleWEI,
+ },
+ {
+ key: "food" as const,
+ label: "Nourriture (Billetterie)",
+ toggleFn: toggleFood,
+ },
+ {
+ key: "chall" as const,
+ label: "Challenges (Affichage des challenges)",
+ toggleFn: toggleChallenge,
+ },
+ ];
- // Si les statuts sont en cours de chargement, on affiche un indicateur
if (loadingStatuses) {
return (
-
Chargement des statuts...
+
+
+ Chargement des statuts...
+
);
}
return (
-
-
Gestion des Événements Shotgun
+
+
+ ⚙️ Gestion des Événements
+
- {/* Pré-inscription */}
-
- Pré-inscription
-
- {preRegistrationOpen ? "Désactiver" : "Activer"}
-
-
-
- {/* Shotgun */}
-
- Shotgun
-
- {shotgunOpen ? "Désactiver" : "Activer"}
-
-
-
- {/* SDI */}
-
- SDI
-
- {sdiOpen ? "Désactiver" : "Activer"}
-
-
+ {events.map(({ key, label, toggleFn }) => {
+ const isActive = statuses[key];
+ return (
+
+
+ {isActive ? (
+
+ ) : (
+
+ )}
+ {label}
+
- {/* WEI */}
-
- WEI
-
- {weiOpen ? "Désactiver" : "Activer"}
-
-
+
+ handleToggle(key, toggleFn, `${label} mis à jour !`)
+ }
+ disabled={loading}
+ className={`transition-colors duration-300 ${
+ isActive
+ ? "bg-red-600 text-white hover:bg-red-700"
+ : "bg-blue-500 text-white hover:bg-blue-600"
+ } p-2 rounded-lg min-w-[110px] flex items-center justify-center`}
+ >
+ {loading ? (
+
+ ) : isActive ? (
+ "Désactiver"
+ ) : (
+ "Activer"
+ )}
+
+
+ );
+ })}
);
diff --git a/frontend/src/components/Admin/adminExportImport.tsx b/frontend/src/components/Admin/adminExportImport.tsx
index ad5cd1e..c79cb2d 100644
--- a/frontend/src/components/Admin/adminExportImport.tsx
+++ b/frontend/src/components/Admin/adminExportImport.tsx
@@ -1,21 +1,136 @@
-import { useState } from "react";
+import { ChangeEvent, useState } from "react";
import { Button } from "../ui/button";
-import { exportDb } from "../../services/requests/export.service";
-import { importPermanenceCSV } from "../../services/requests/permanence.service";
+import { exportBus, exportDb, importFoodMenu, importPlannings } from "../../services/requests/im_export.service";
+import { FileText } from "lucide-react";
export const AdminExportConnect = () => {
+ const [loading, setLoading] = useState<{ db: boolean; bus: boolean }>({
+ db: false,
+ bus: false,
+ });
+ const [error, setError] = useState
(null);
+ const [message, setMessage] = useState("");
+ const [showBusExport, setShowBusExport] = useState(false);
+
+ const busUrl = "https://integration.utt.fr/api/exports/bus/bus.csv";
+
+ const handleExport = async (
+ type: "db" | "bus",
+ exportFn: () => Promise<{ message: string }>
+ ) => {
+ setLoading((prev) => ({ ...prev, [type]: true }));
+ setError(null);
+ setMessage("");
+ try {
+ const response = await exportFn();
+ setMessage(response.message);
+ if (type === "bus") setShowBusExport(true);
+ } catch (err) {
+ console.error(`Erreur export ${type}`, err);
+ setError(
+ type === "db"
+ ? "Erreur lors de l'export vers Google Sheets."
+ : "Erreur lors de l'export des bus."
+ );
+ } finally {
+ setLoading((prev) => ({ ...prev, [type]: false }));
+ }
+ };
+
+ return (
+
+
+ ⚡ Exporter les données
+
+
+
+ handleExport("db", exportDb)}
+ disabled={loading.db}
+ className="w-full sm:w-auto bg-gradient-to-r from-blue-600 to-blue-700 hover:from-blue-700 hover:to-blue-800 text-white py-2 px-6 rounded-xl shadow-md transition-all duration-200"
+ >
+ {loading.db ? "⏳ Export en cours..." : "Exporter vers Google Sheets"}
+
+
+ handleExport("bus", exportBus)}
+ disabled={loading.bus}
+ className="w-full sm:w-auto bg-gradient-to-r from-green-600 to-green-700 hover:from-green-700 hover:to-green-800 text-white py-2 px-6 rounded-xl shadow-md transition-all duration-200"
+ >
+ {loading.bus ? "⏳ Export en cours..." : "Exporter les bus"}
+
+
+
+ {error && (
+
+ {error}
+
+ )}
+ {message && !error && (
+
+ ✅ {message}
+
+ )}
+
+ {showBusExport && (
+
+ )}
+
+ );
+};
+
+
+
+export const AdminImportFoodMenu = () => {
+ const [menu, setMenu] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const [message, setMessage] = useState("");
- const handleExport = async () => {
+ const handleFileChange = (e: ChangeEvent) => {
+ setError(null);
+ setMessage("");
+ if (e.target.files && e.target.files.length > 0) {
+ const selected = e.target.files[0];
+ if (selected.type !== "application/pdf") {
+ setError("Seuls les fichiers PDF sont autorisés");
+ setMenu(null);
+ } else {
+ setMenu(selected);
+ }
+ }
+ };
+
+ const handleImport = async () => {
+ if (!menu) {
+ setError("Veuillez sélectionner un fichier PDF avant d’importer.");
+ return;
+ }
+
setLoading(true);
+ setError(null);
+ setMessage("");
+
try {
- const response = await exportDb();
- setMessage(response.message);
- } catch (error) {
- console.error("Erreur de connexion à Google", error);
- setError("Erreur lors de la tentative de connexion.");
+ const formData = new FormData();
+ formData.append("foodFile", menu);
+
+ const response = await importFoodMenu(formData);
+ setMessage(response.message || "Importation réussie !");
+ } catch (err) {
+ console.error("Erreur lors de l’importation du menu", err);
+ setError("Une erreur est survenue pendant l’importation.");
} finally {
setLoading(false);
}
@@ -24,16 +139,46 @@ export const AdminExportConnect = () => {
return (
- Exporter vers Google Sheets
+ Importer le menu au format PDF
-
+ {/* Rappel des règles de nommage */}
+
+ ⚠️ Le fichier doit être nommé FoodMenu.pdf
+
+
+
+ {/* Input fichier masqué */}
+
+
+
+ Choisir un fichier
+
+
+ {/* Affiche le nom du fichier sélectionné */}
+ {menu && (
+
+
+ {menu.name}
+
+ )}
+
+ {/* Bouton importer */}
- {loading ? "Chargement..." : "Exporter les données"}
+ {loading ? "Import en cours..." : "Importer le PDF"}
@@ -49,89 +194,153 @@ export const AdminExportConnect = () => {
);
};
-export const ImportPermCSV = () => {
- const [file, setFile] = useState
(null);
+
+export const AdminImportPlannings = () => {
+ const [planning, setPlanning] = useState(null);
+ const [loading, setLoading] = useState(false);
+ const [error, setError] = useState(null);
const [message, setMessage] = useState("");
+ const [selectedPlanning, setSelectedPlanning] = useState("");
- const handleFileChange = (e: React.ChangeEvent) => {
- const selectedFile = e.target.files?.[0];
- if (selectedFile) {
- setFile(selectedFile);
+ const handleFileChange = (e: ChangeEvent) => {
+ setError(null);
+ setMessage("");
+ if (e.target.files && e.target.files.length > 0) {
+ const selected = e.target.files[0];
+ if (selected.type !== "application/pdf") {
+ setError("Seuls les fichiers PDF sont autorisés");
+ setPlanning(null);
+ } else {
+ setPlanning(selected);
+ }
}
};
- const handleFileUpload = async () => {
- if (!file) {
- setMessage("Veuillez sélectionner un fichier CSV.");
+ const handleImport = async (planningName: string) => {
+ if (!planning) {
+ setError("Veuillez sélectionner un fichier PDF avant d’importer.");
return;
}
- const formData = new FormData();
- formData.append("file", file);
+ setLoading(true);
+ setError(null);
+ setMessage("");
+ setSelectedPlanning(planningName);
try {
- const response = await importPermanenceCSV(formData);
- setMessage(response.message);
- } catch (error) {
- console.error(error);
- setMessage("Erreur lors de l'import du fichier CSV.");
+ const formData = new FormData();
+ formData.append("planningFile", planning);
+
+ const response = await importPlannings(formData);
+ setMessage(response.message || `Importation réussie pour ${planningName} !`);
+ } catch (err) {
+ console.error("Erreur lors de l’importation du planning", err);
+ setError("Une erreur est survenue pendant l’importation.");
+ } finally {
+ setLoading(false);
}
};
return (
-
-
- Importer un fichier CSV pour les permanences
+
+
+ Importer les plannings au format PDF
-
- Uploadez un fichier CSV contenant les permanences à importer.
+ {/* Rappel des règles de nommage */}
+
+ ⚠️ Le fichier doit être nommé en minuscules, sans accents, au format
+ filiere.pdf (ex: tc.pdf, bachelor.pdf)
-
+
+ {/* Input fichier masqué */}
+ id="planningFileInput"
+ type="file"
+ accept=".pdf"
+ onChange={handleFileChange}
+ className="hidden"
+ />
-
- 📥 Importer le fichier
-
+
+ Choisir un fichier
+
+
+ {/* Affiche le nom du fichier sélectionné */}
+ {planning && (
+
+
+ {planning.name}
+
+ )}
+
+ {/* Différents boutons d'import */}
+
+ handleImport("Planning TC")}
+ disabled={loading}
+ className="bg-indigo-600 hover:bg-indigo-700 text-white py-2 px-6 rounded-xl shadow-md"
+ >
+ {loading && selectedPlanning === "Planning TC"
+ ? "Import en cours..."
+ : "Planning TC"}
+
+
+ handleImport("Planning Bachelor IA")}
+ disabled={loading}
+ className="bg-violet-600 hover:bg-violet-700 text-white py-2 px-6 rounded-xl shadow-md"
+ >
+ {loading && selectedPlanning === "Planning Bachelor IA"
+ ? "Import en cours..."
+ : "Planning Bachelor IA"}
+
+
+ handleImport("Planning Branche (non-alternant)")}
+ disabled={loading}
+ className="bg-purple-600 hover:bg-purple-700 text-white py-2 px-6 rounded-xl shadow-md"
+ >
+ {loading && selectedPlanning === "Planning Branche (non-alternant)"
+ ? "Import en cours..."
+ : "Planning Branche (non-alternant)"}
+
+
+ handleImport("Planning Branche FISEA (alternants)")}
+ disabled={loading}
+ className="bg-pink-600 hover:bg-pink-700 text-white py-2 px-6 rounded-xl shadow-md"
+ >
+ {loading && selectedPlanning === "Planning Branche FISEA (alternants)"
+ ? "Import en cours..."
+ : "Planning Branche FISEA (alternants)"}
+
+
+ handleImport("Planning Master")}
+ disabled={loading}
+ className="bg-green-600 hover:bg-green-700 text-white py-2 px-6 rounded-xl shadow-md"
+ >
+ {loading && selectedPlanning === "Planning Master"
+ ? "Import en cours..."
+ : "Planning Master"}
+
+
+ {/* Messages */}
+ {error && (
+
{error}
+ )}
{message && (
-
+
{message}
)}
-
-
-
- 📄 Exemple de fichier CSV :
-
-
- {`name,description,location,start_at,end_at,capacity,is_open
-Permanence 1,Accueil matin,A001,2025-05-01T08:00,2025-05-01T10:00,10,false
-Permanence 2,Accueil après-midi,A002,2025-05-02T14:00,2025-05-02T16:00,15,false`}
-
-
- Le fichier doit être encodé en UTF-8 et utiliser une virgule comme séparateur. Les dates doivent être au format
-
- aaaa-mm-jjThh:mm
- .
-
-
);
};
diff --git a/frontend/src/components/Admin/adminNews.tsx b/frontend/src/components/Admin/adminNews.tsx
index ce44b7b..9317b9a 100644
--- a/frontend/src/components/Admin/adminNews.tsx
+++ b/frontend/src/components/Admin/adminNews.tsx
@@ -282,7 +282,9 @@ export const AdminNews = () => {
{news.title}
-
{news.description}
+
+ {news.description}
+
{news.image_url && (
{
-
- const [newPermanenceName, setNewPermanenceName] = useState("");
- const [newPermanenceDescription, setNewPermanenceDescription] = useState("");
- const [newPermanenceLocation, setNewPermanenceLocation] = useState("");
- const [newPermanenceStartDate, setNewPermanenceStartDate] = useState("");
- const [newPermanenceEndDate, setNewPermanenceEndDate] = useState("");
- const [newPermanenceCapacity, setNewPermanenceCapacity] = useState(0);
- const [permanences, setPermanences] = useState
([]); // Liste des permanences existantes
- const [editMode, setEditMode] = useState(false);
- const [editPermanence, setEditPermanence] = useState(null); // Permanence à éditer
- const [showMembers, setShowMembers] = useState(null);
- const [members, setMembers] = useState([]);
- const [newMember, setNewMember] = useState(null);
- const [users, setUsers] = useState([]);
-
-
- useEffect(() => {
- // Charger la liste des permanences au démarrage
- fetchPermanences();
- fetchUsers();
- }, []);
-
- const fetchPermanences = async () => {
- try {
- const response = await getAllPermanences(); // ton service
- setPermanences(response.data);
- } catch (error) {
- console.error("Erreur lors du chargement des permanences", error);
- }
- };
-
- const fetchUsers = async () => {
- try {
- const response = await getUsers(); // ton service
- setUsers(response);
- } catch (error) {
- console.error("Erreur lors du chargement des permanences", error);
- }
- };
-
-
- const fetchMembers = async (permId: number) => {
- try {
- const res = await getUsersByPermanence(permId);
- setMembers(res.data);
- setShowMembers(permId);
- } catch (err) {
- console.error("Erreur lors de la récupération des membres", err);
- }
- };
-
- const handleCreatePermanence = async () => {
- if (
- !newPermanenceName ||
- !newPermanenceDescription ||
- !newPermanenceLocation ||
- !newPermanenceStartDate ||
- !newPermanenceEndDate ||
- !newPermanenceCapacity
- ) {
- alert("Veuillez remplir tous les champs");
- return;
- }
-
- try {
- const response = await createPermanence({
- name: newPermanenceName,
- description: newPermanenceDescription,
- location: newPermanenceLocation,
- start_at: newPermanenceStartDate,
- end_at: newPermanenceEndDate,
- capacity: newPermanenceCapacity,
- });
- alert(response.message);
- // Réinitialiser les champs du formulaire
- setNewPermanenceName("");
- setNewPermanenceDescription("");
- setNewPermanenceLocation("");
- setNewPermanenceStartDate("");
- setNewPermanenceEndDate("");
- setNewPermanenceCapacity(0);
- fetchPermanences(); // Recharger les permanences après création
- } catch (err) {
- console.error("Erreur lors de la création de la permanence", err);
- }
- };
-
- const handleOpenAllPermanences = async () => {
- try {
- // Ouvrir toutes les permanences à J+7
- await Promise.all(
- permanences.filter((perm) => {
- const startDate = new Date(perm.start_at);
- const today = new Date();
- today.setDate(today.getDate() + 7);
- return today > startDate
- }).map((perm) => openPermanence(perm.id))
- );
- alert("Toutes les permanences ont été ouvertes !");
- fetchPermanences(); // Recharger les permanences après ouverture
- } catch (err) {
- console.error("Erreur lors de l'ouverture des permanences", err);
- }
- };
-
- const handleCloseAllPermanences = async () => {
- try {
- // Ouvrir toutes les permanences à J+7
- await Promise.all(
- permanences.filter((perm) => {
- const startDate = new Date(perm.start_at);
- const today = new Date();
- today.setDate(today.getDate() + 7);
- return today > startDate
- }).map((perm) => closePermanence(perm.id))
- );
- alert("Toutes les permanences ont été fermées !");
- fetchPermanences(); // Recharger les permanences après ouverture
- } catch (err) {
- console.error("Erreur lors de l'ouverture des permanences", err);
- }
- };
-
- const handleOpenPermanence = async (permId: number) => {
- try {
- const response = await openPermanence(permId);
- alert(response.message);
- fetchPermanences(); // Recharger les permanences après ouverture
- } catch (err) {
- console.error("Erreur lors de l'ouverture de la permanence", err);
- }
- };
-
- const handleClosePermanence = async (permId: number) => {
- try {
- const response = await closePermanence(permId);
- alert(response.message);
- fetchPermanences(); // Recharger les permanences après fermeture
- } catch (err) {
- console.error("Erreur lors de la fermeture de la permanence", err);
- }
- };
-
- const handleEditPermanence = (perm: any) => {
- setEditMode(true);
- setEditPermanence(perm);
- setNewPermanenceName(perm.name);
- setNewPermanenceDescription(perm.description);
- setNewPermanenceLocation(perm.location);
- setNewPermanenceStartDate(formatDateForInput(perm.start_at)); // Formatage de la date pour l'input
- setNewPermanenceEndDate(formatDateForInput(perm.end_at)); // Formatage de la date pour l'input
- setNewPermanenceCapacity(perm.capacity);
- };
-
- const handleSaveEditPermanence = async () => {
- if (
- !newPermanenceName ||
- !newPermanenceDescription ||
- !newPermanenceLocation ||
- !newPermanenceStartDate ||
- !newPermanenceEndDate ||
- !newPermanenceCapacity
- ) {
- alert("Veuillez remplir tous les champs");
- return;
- }
-
- try {
- const response = await updatePermanence( editPermanence.id, {
- name: newPermanenceName,
- description: newPermanenceDescription,
- location: newPermanenceLocation,
- start_at: newPermanenceStartDate,
- end_at: newPermanenceEndDate,
- capacity: newPermanenceCapacity,
- });
- alert(response.message);
- setEditMode(false);
-
- setNewPermanenceName("");
- setNewPermanenceDescription("");
- setNewPermanenceLocation("");
- setNewPermanenceStartDate("");
- setNewPermanenceEndDate("");
- setNewPermanenceCapacity(0);
-
- fetchPermanences(); // Recharger les permanences après modification
- } catch (err) {
- console.error("Erreur lors de l'édition de la permanence", err);
- }
- };
-
- const handleAddMember = async (permId: number, user: User) => {
- try {
- const response = await addUserToPermanence(permId, user.userId);
- alert(response.message);
- setNewMember(null);
- fetchMembers(permId);
- fetchPermanences();
- } catch (err) {
- console.error("Erreur lors de l'ajout du membre", err);
- }
- };
-
- const handleRemoveMember = async (permId: number, userId: number) => {
- try {
- const response = await removeUserFromPermanence(permId, userId);
- alert(response.message);
- fetchMembers(permId);
- fetchPermanences();
- } catch (err) {
- console.error("Erreur lors de la suppression du membre", err);
- }
- };
-
- const handleDeletePermanence = async (permId: number) => {
- try {
- const response = await deletePermanence(permId);
- alert(response.message);
- fetchPermanences();
- } catch (err) {
- console.error("Erreur lors de la suppression du membre", err);
- }
- };
-
- return (
-
- {/* Formulaire */}
-
-
- {editMode ? "✏️ Éditer la permanence" : "➕ Créer une permanence"}
-
-
-
- setNewPermanenceName(e.target.value)} />
-
-
-
- {/* Boutons d’action groupés */}
-
-
- 📅 Ouvrir toutes à J+7
-
-
- 🛑 Fermer toutes à J+7
-
-
-
- {/* Liste des permanences */}
-
-
📋 Permanences existantes
-
- {permanences.map((perm) => (
-
- {perm.name}
- {perm.description}
-
-
📍 Emplacement : {perm.location}
-
🕒 Début : {formatDateForDisplay(perm.start_at)}
-
🕔 Fin : {formatDateForDisplay(perm.end_at)}
-
👥 Capacité restante : {perm.capacity}
-
-
-
- {perm.is_open ? (
- handleClosePermanence(perm.id)} className="bg-orange-600 hover:bg-orange-700 text-white">
- Fermer
-
- ) : (
- handleOpenPermanence(perm.id)} className="bg-blue-600 hover:bg-blue-700 text-white">
- Ouvrir
-
- )}
- handleEditPermanence(perm)} className="bg-yellow-500 hover:bg-yellow-600 text-white">
- Éditer
-
- fetchMembers(perm.id)} className="bg-indigo-600 hover:bg-indigo-700 text-white">
- 👥 Voir membres
-
- handleDeletePermanence(perm.id)} className="bg-red-600 hover:bg-red-700 text-white">
- 🗑️ Supprimer
-
-
-
- {/* Liste des membres */}
- {showMembers === perm.id && (
-
-
Membres inscrits
-
-
- {members.length === 0 ? (
-
Aucun membre pour l’instant.
- ) : (
-
- {members.map((user) => (
-
- {user.firstName + " " + user.lastName}
- handleRemoveMember(perm.id, user.userId)}
- className="bg-red-500 hover:bg-red-600 text-white text-xs px-2 py-1"
- >
- Retirer
-
-
- ))}
-
- )}
-
-
-
- !members.some((m) => m.userId === user.userId))
- .map((user) => ({
- value: user,
- label: user.firstName || user.lastName
- ? `${user.firstName || ""} ${user.lastName || ""}`
- : `Utilisateur ${user.userId}`,
- }))}
- onChange={(option) => setNewMember(option?.value || null)}
- placeholder="Sélectionner un utilisateur à ajouter"
- isClearable
- />
-
-
-
newMember && handleAddMember(perm.id, newMember)}
- className="bg-green-600 hover:bg-green-700 text-white"
- >
- Ajouter
-
-
-
Attention en tant qu'Admin vous pouvez bypass les quotas
-
setShowMembers(null)}
- className="text-xs text-gray-500 underline mt-4"
- >
- Fermer la liste des membres
-
-
- )}
-
- ))}
-
-
-
-)};
-
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 [pairs, setPairs] = useState
([]);
+ const [loading, setLoading] = useState(false);
+ const [search, setSearch] = useState("");
+ const [filter, setFilter] = useState<"all" | "confirmed" | "unconfirmed">("all");
+
+ const fetchPairs = async () => {
+ setLoading(true);
+ try {
+ const result = await getAllTentPairs();
+ if (result?.data) {
+ setPairs(result.data);
+ }
+ } catch {
+ Swal.fire("Erreur", "Impossible de récupérer les binômes", "error");
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const handleToggle = async (pair: TentPair) => {
+ const action = pair.confirmed ? "dévalider" : "valider";
+
+ const confirm = await Swal.fire({
+ title: '⚠️ Confirmation requise',
+ html: `
+ Souhaitez-vous vraiment ${action} la tente de :
+
+ ${pair.user1_first_name} ${pair.user1_last_name}
+ ${pair.user2_first_name} ${pair.user2_last_name}
+
+
+ ⚠️ Cette action enverra un email au binôme concerné.
+
+ `,
+ icon: 'warning',
+ showCancelButton: true,
+ confirmButtonColor: pair.confirmed ? '#d33' : '#28a745',
+ cancelButtonColor: '#3085d6',
+ confirmButtonText: `✅ Oui, ${action}`,
+ cancelButtonText: '❌ Annuler',
+ reverseButtons: true,
+ focusCancel: true,
+ });
+
+
+ if (!confirm.isConfirmed) return;
+
+ try {
+ await toggleTentConfirmation(pair.user1_id, pair.user2_id, !pair.confirmed);
+
+ Swal.fire(
+ "Succès ✅",
+ `La tente a bien été ${pair.confirmed ? "dévalidée" : "validée"}.`,
+ "success"
+ );
+
+ fetchPairs();
+ } catch {
+ Swal.fire("Erreur", "Impossible de mettre à jour la confirmation", "error");
+ }
+ };
+
+ useEffect(() => {
+ fetchPairs();
+ }, []);
+
+ // Filtrage par recherche + état
+ const filteredPairs = pairs.filter((pair) => {
+ const query = search.toLowerCase();
+ const matchesSearch =
+ pair.user1_first_name.toLowerCase().includes(query) ||
+ pair.user1_last_name.toLowerCase().includes(query) ||
+ pair.user1_email.toLowerCase().includes(query) ||
+ pair.user2_first_name.toLowerCase().includes(query) ||
+ pair.user2_last_name.toLowerCase().includes(query) ||
+ pair.user2_email.toLowerCase().includes(query);
+
+ const matchesFilter =
+ filter === "all" ||
+ (filter === "confirmed" && pair.confirmed) ||
+ (filter === "unconfirmed" && !pair.confirmed);
+
+ return matchesSearch && matchesFilter;
+ });
+
+ return (
+
+
+
+ 🛠️ Gestion des binômes de tentes
+
+
+
+ {/* 🔎 Barre de recherche */}
+
+ setSearch(e.target.value)}
+ className="w-full border rounded-lg pl-10 pr-4 py-2 focus:ring-2 focus:ring-blue-500 transition"
+ />
+
+
+
+ {/* 🔽 Filtre état */}
+
setFilter(e.target.value as "all" | "confirmed" | "unconfirmed")}
+ className="border rounded-lg p-2 focus:ring-2 focus:ring-blue-500 transition"
+ >
+ 📋 Tous
+ ✅ Validés
+ ❌ Non validés
+
+
+
+ {loading ? "Chargement..." : "🔄 Rafraîchir"}
+
+
+
+ {filteredPairs.length === 0 ? (
+
Aucun binôme trouvé.
+ ) : (
+
+
+
+
+ 👤 Nom 1
+ 📧 Email 1
+ 👤 Nom 2
+ 📧 Email 2
+ ✅ État
+ ⚡ Action
+
+
+
+ {filteredPairs.map((pair, index) => (
+
+ {pair.user1_first_name} {pair.user1_last_name}
+ {pair.user1_email}
+ {pair.user2_first_name} {pair.user2_last_name}
+ {pair.user2_email}
+
+ {pair.confirmed ? (
+
+ Validée
+
+ ) : (
+
+ Non validée
+
+ )}
+
+
+ handleToggle(pair)}
+ className={
+ pair.confirmed
+ ? "bg-red-600 hover:bg-red-700 text-white"
+ : "bg-green-600 hover:bg-green-700 text-white"
+ }
+ >
+ {pair.confirmed ? "❌ Dévalider" : "✅ Valider"}
+
+
+
+ ))}
+
+
+
+ )}
+
+
+ );
+};
diff --git a/frontend/src/components/Admin/adminUser.tsx b/frontend/src/components/Admin/adminUser.tsx
index 535adfc..794b319 100644
--- a/frontend/src/components/Admin/adminUser.tsx
+++ b/frontend/src/components/Admin/adminUser.tsx
@@ -1,5 +1,6 @@
import { useEffect, useState } from "react";
import Select from "react-select";
+import Swal from "sweetalert2";
import { Card, CardHeader, CardTitle, CardContent } from "../ui/card";
import { Input } from "../ui/input";
import { Button } from "../ui/button";
@@ -19,25 +20,25 @@ const permissionOptions = [
const branchOptions = [
{ value: "TC", label: "Tronc Commun" },
- { value: "RT", label: "Réseaux et Télcommunications" },
+ { value: "RT", label: "Réseaux et Télécommunications" },
{ value: "ISI", label: "Informatique et Systèmes d'Information" },
{ value: "GM", label: "Génie Mécanique" },
{ value: "GI", label: "Génie Industriel" },
- { value: "MTE", label: "Matériaux : Technologie et Economie" },
+ { value: "MTE", label: "Matériaux : Technologie et Économie" },
{ value: "A2I", label: "Automatique & Informatique Industrielle" },
{ value: "GI_APPR", label: "Génie Industriel en Apprentissage" },
{ value: "GM_APPR", label: "Génie Mécanique en Apprentissage" },
- { value: "SN_APPR", label: "Systeme Numérique en Apprentissage" },
+ { value: "SN_APPR", label: "Système Numérique en Apprentissage" },
{ value: "Branch", label: "Branche" },
{ value: "MM", label: "Mécanique et Matériaux" },
- { value : "MA", label: "Master"},
- { value: "RI", label: "Ressources International" },
+ { value: "MA", label: "Master" },
+ { value: "RI", label: "Ressources Internationales" },
];
const majeurOptions = [
- { value: true, label: "Majeur" },
- { value: false, label: "Mineur" },
- ];
+ { value: true, label: "Majeur" },
+ { value: false, label: "Mineur" },
+];
export const AdminUser = () => {
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
+
+
+
opt.value === formData.majeur)
- : null
+ majeurOptions.find((opt) => opt.value === formData.majeur) || null
}
- onChange={handleMajeurChange}
+ onChange={handleSelectChange("majeur")}
isClearable
/>
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
/>
-
+
💾 Sauvegarder
-
+
🗑 Supprimer
@@ -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
+
+
-
-
+
+
Choisir une date de vérification de majorité :
{
onChange={(e) => setSelectedDate(e.target.value)}
className="w-full px-4 py-2 border rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-400"
/>
-
-
-
-
- {loading ? "Chargement..." : "Synchro SIEP"}
-
-
- {error && {error}
}
- {message && {message}
}
-
+
+
+ {loading ? "Chargement..." : "🚀 Lancer la synchro"}
+
+
+
+
);
};
-
diff --git a/frontend/src/components/Plannings/planningSection.tsx b/frontend/src/components/Plannings/planningSection.tsx
new file mode 100644
index 0000000..87dab9c
--- /dev/null
+++ b/frontend/src/components/Plannings/planningSection.tsx
@@ -0,0 +1,110 @@
+import { useEffect, useState } from "react";
+
+type Planning = {
+ name: string;
+ url: string;
+};
+
+const plannings: Planning[] = [
+ {
+ name: "Planning TC",
+ url: "https://integration.utt.fr/api/uploads/plannings/tc.pdf",
+ },
+ {
+ name: "Planning Bachelor IA",
+ url: "https://integration.utt.fr/api/uploads/plannings/bachelor.pdf",
+ },
+ {
+ name: "Planning Branche (non-alternant)",
+ url: "https://integration.utt.fr/api/uploads/plannings/branche.pdf",
+ },
+ {
+ name: "Planning Branche FISEA (alternants)",
+ url: "https://integration.utt.fr/api/uploads/plannings/fisea.pdf",
+ },
+ {
+ name: "Planning Master",
+ url: "https://integration.utt.fr/api/uploads/plannings/master.pdf",
+ },
+];
+
+export const PlanningSection = () => {
+ const [availablePlannings, setAvailablePlannings] = useState<
+ Record
+ >({});
+
+ useEffect(() => {
+ const checkAvailability = async () => {
+ const availability: Record = {};
+ for (const planning of plannings) {
+ try {
+ const response = await fetch(planning.url, { method: "HEAD" });
+ availability[planning.name] = response.ok;
+ } catch {
+ availability[planning.name] = false;
+ }
+ }
+ setAvailablePlannings(availability);
+ };
+
+ checkAvailability();
+ }, []);
+
+ return (
+
+
+ {/* Titre principal */}
+
+
+ 📅 Consulte les plannings de la semaine d'Intégration
+
+
+ Retrouve ici tous les plannings (TC, Branche, FISEA, Master) et
+ télécharge-les.
+
+
+
+ {/* Affichage un par un */}
+ {plannings.map((planning) => (
+
+ {/* Titre planning centré */}
+
+ {planning.name}
+
+
+ {availablePlannings[planning.name] ? (
+ <>
+ {/* PDF scrollable horizontal */}
+
+
+
+
+ {/* Bouton centré */}
+
+ >
+ ) : (
+
+ 🚫 Ce planning n’est pas encore disponible.
+
+ )}
+
+ ))}
+
+
+ );
+};
diff --git a/frontend/src/components/WEI_SDI_Food/foodSection.tsx b/frontend/src/components/WEI_SDI_Food/foodSection.tsx
index 9c20bb2..c6452a5 100644
--- a/frontend/src/components/WEI_SDI_Food/foodSection.tsx
+++ b/frontend/src/components/WEI_SDI_Food/foodSection.tsx
@@ -1,8 +1,13 @@
import { useEffect, useState } from "react";
import { checkFoodStatus } from "../../services/requests/event.service";
+import { getPermission } from "../../services/requests/user.service";
export const FoodSection = () => {
const [isFoodOpen, setIsFoodOpen] = useState(false);
+ const [isMenuAvailable, setIsMenuAvailable] = useState(false);
+
+ const permission = getPermission();
+ const menuUrl = "https://integration.utt.fr/api/uploads/foodmenu/FoodMenu.pdf";
useEffect(() => {
const script = document.createElement("script");
@@ -11,6 +16,7 @@ export const FoodSection = () => {
document.body.appendChild(script);
fetchStatus();
+ checkMenuAvailability();
}, []);
const fetchStatus = async () => {
@@ -18,7 +24,18 @@ export const FoodSection = () => {
const status = await checkFoodStatus();
setIsFoodOpen(status);
} catch (error) {
- alert("Erreur lors de la récupération du statut de SDI.");
+ alert("Erreur lors de la récupération du statut de la nourriture.");
+ }
+ };
+
+ const checkMenuAvailability = async () => {
+ try {
+ const response = await fetch(menuUrl, { method: "HEAD" });
+ if (response.ok) {
+ setIsMenuAvailable(true);
+ }
+ } catch (error) {
+ // Ne rien faire si le fichier n'est pas disponible
}
};
@@ -34,6 +51,26 @@ export const FoodSection = () => {
+ {/* Visualiseur PDF si disponible */}
+ {isMenuAvailable && (
+
+ )}
+
+ {/* Billetterie */}
{!isFoodOpen ? (
@@ -44,13 +81,29 @@ export const FoodSection = () => {
) : (
-
-
-
+ <>
+
+
+
+ {(permission === "Student" || permission === "Admin") && (
+
+
+ La billetterie du repas test est réservée aux Chefs d'Equipe et Organisateurs de l'Intégration 2025.
+
+
+
+
+
+ )}
+ >
)}
diff --git a/frontend/src/components/challenge/challengeList.tsx b/frontend/src/components/challenge/challengeList.tsx
index 88521e4..0f04ae7 100644
--- a/frontend/src/components/challenge/challengeList.tsx
+++ b/frontend/src/components/challenge/challengeList.tsx
@@ -4,17 +4,37 @@ import { getAllChallenges, getFactionsPoints } from "../../services/requests/cha
import { Challenge } from "../../interfaces/challenge.interface";
import { getAllFactionsUser } from "../../services/requests/faction.service";
import { Faction } from "../../interfaces/faction.interface";
+import { checkChallengeStatus } from "../../services/requests/event.service";
+import Swal from "sweetalert2";
-export const ChallengeList = () => {
+export const UserChallengeList = () => {
const [availableChallenges, setAvailableChallenges] = useState([]);
const [factions, setFactions] = useState([]);
const [factionPoints, setFactionPoints] = useState<{ [key: number]: number }>({});
const [selectedCategory, setSelectedCategory] = useState("Tous");
const [sortOrder, setSortOrder] = useState<"asc" | "desc">("asc");
+ const [isChallOpen, setIsChallOpen] = useState(false);
+ const [loading, setLoading] = useState(true);
- useEffect(() => {
- fetchInitialData();
- }, []);
+ useEffect(() => {
+ const init = async () => {
+ try {
+ await fetchInitialData();
+ const status = await checkChallengeStatus();
+ setIsChallOpen(status);
+ } catch (error) {
+ console.error("Erreur lors de la récupération des données :", error);
+ await Swal.fire({
+ icon: "error",
+ title: "Oups...",
+ text: "Une erreur est survenue lors de la récupération des données.",
+ });
+ } finally {
+ setLoading(false);
+ }
+ };
+ init();
+}, []);
const fetchInitialData = async () => {
await Promise.all([fetchChallenges(), fetchFactions()]);
@@ -44,7 +64,7 @@ export const ChallengeList = () => {
const points: { [key: number]: number } = {};
const fetchedFactions = await getAllFactionsUser();
await Promise.all(
- fetchedFactions.map(async (faction : Faction) => {
+ fetchedFactions.map(async (faction: Faction) => {
const res = await getFactionsPoints(faction.factionId);
points[faction.factionId] = res.points ?? 0;
})
@@ -67,7 +87,9 @@ export const ChallengeList = () => {
challenges = challenges.filter(c => c.category === selectedCategory);
}
- challenges.sort((a, b) => sortOrder === "asc" ? a.points - b.points : b.points - a.points);
+ challenges.sort((a, b) =>
+ sortOrder === "asc" ? a.points - b.points : b.points - a.points
+ );
return challenges;
}, [availableChallenges, selectedCategory, sortOrder]);
@@ -96,8 +118,13 @@ export const ChallengeList = () => {
🏆 Challenges disponibles
- {
- availableChallenges.length === 0 ? (
+ {loading ? (
+ Chargement en cours...
+ ) : !isChallOpen ? (
+
+ 🚫 Les challenges ne sont pas encore ouverts.
+
+ ) : availableChallenges.length === 0 ? (
Aucun challenge disponible pour le moment.
) : (
<>
diff --git a/frontend/src/components/navbar.tsx b/frontend/src/components/navbar.tsx
index ba48a32..e73fcc1 100644
--- a/frontend/src/components/navbar.tsx
+++ b/frontend/src/components/navbar.tsx
@@ -1,247 +1,270 @@
-import { Link, useLocation, useNavigate } from "react-router-dom";
-import { useEffect, useState } from "react";
-import { getToken, decodeToken } from "../services/requests/auth.service";
-import clsx from "clsx";
+// src/components/Navbar.tsx
+import { NavLink, useLocation } from "react-router-dom";
+import { useEffect, useState, Fragment } from "react";
+import { HomeIcon, CogIcon, UsersIcon, Bars4Icon } from "@heroicons/react/24/outline";
+import { XMarkIcon, } from "@heroicons/react/24/solid";
+
import { motion, AnimatePresence } from "framer-motion";
+import { getToken, decodeToken } from "../services/requests/auth.service";
-interface DecodedToken {
- userPermission?: string;
- userRoles?: { roleName: string }[];
+interface NavItem {
+ label: string;
+ to: string;
+ icon?: React.ComponentType>;
+ rolesAllowed?: string[]; // ["Admin", "Respo CE", ...]
+ children?: NavItem[]; // pour dropdown
}
export const Navbar = () => {
- const navigate = useNavigate();
- const location = useLocation();
+ const { pathname } = useLocation();
const token = getToken();
-
- const [isMobileMenuOpen, setMobileMenuOpen] = useState(false);
- const [isAdminOpen, setIsAdminOpen] = useState(false);
- const [isEventsOpen, setIsEventsOpen] = useState(false);
+ const [menuOpen, setMenuOpen] = useState(false);
useEffect(() => {
- setMobileMenuOpen(false);
- setIsAdminOpen(false);
- setIsEventsOpen(false);
- }, [location]);
-
- if (!token) {
- navigate("/");
- return null;
- }
+ setMenuOpen(false);
+ }, [pathname]);
+
+ if (!token) return null;
+ const { userPermission, userRoles = [] } = decodeToken(token);
+ const roles = [userPermission, ...userRoles.map(r => r.roleName)];
+
+ 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: "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: "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: "Mon compte", to: "/Profil", icon: UsersIcon },
+ {
+ label: "Admin",
+ to: "#",
+ icon: CogIcon,
+ children: [
+ { label: "Users", to: "/admin/users", rolesAllowed: ["Admin"] },
+ { label: "Roles", to: "/admin/roles", rolesAllowed: ["Admin"] },
+ { label: "Teams", to: "/admin/teams", rolesAllowed: ["Admin", "Respo CE"] },
+ { label: "Factions", to: "/admin/factions", rolesAllowed: ["Admin", "Respo CE"] },
+ { label: "Events", to: "/admin/events", rolesAllowed: ["Admin"] },
+ { label: "Permanences", to: "/admin/permanences", rolesAllowed: ["Admin"] },
+ { label: "Challenge", to: "/admin/challenge", rolesAllowed: ["Admin", "Arbitre"] },
+ { label: "Export / Import", to: "/admin/export-import", rolesAllowed: ["Admin"] },
+ { label: "Email", to: "/admin/email", rolesAllowed: ["Admin"] },
+ { label: "News", to: "/admin/news", rolesAllowed: ["Admin", "Communication"] },
+ { label: "Tentes", to: "/admin/tent", rolesAllowed: ["Admin"] },
+ { label: "Games", to: "/admin/games", rolesAllowed: ["Admin"] },
+ ],
+ },
+
+];
+
- let decoded: DecodedToken;
- try {
- decoded = decodeToken(token);
- } catch {
- navigate("/");
- return null;
+ // helper d’autorisation
+ const isAllowed = (item: NavItem): boolean => {
+ // Si l'item a une restriction directe
+ if (item.rolesAllowed) {
+ return item.rolesAllowed.some(r => roles.includes(r));
}
- const permission = decoded.userPermission;
- const roles = decoded.userRoles?.map((r) => r.roleName) || [];
+ // 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));
+ }
- const isAdmin = permission === "Admin";
- const isStudent = permission === "Student";
- const isRespoCE = roles.includes("Respo CE");
- const isArbitre = roles.includes("Arbitre");
- const isComm = roles.includes("Communication");
+ // Sinon accessible par défaut
+ return true;
+};
const handleLogout = () => {
localStorage.removeItem("authToken");
window.location.href = "/";
};
- const isActive = (path: string) => location.pathname === path;
-
- const MenuItem = ({ to, label }: { to: string; label: string }) => (
-
- {label}
-
- );
-
- const dropdownVariants = {
- hidden: { opacity: 0, y: -5 },
- visible: { opacity: 1, y: 0, transition: { duration: 0.2 } },
- };
-
- const adminLinks = [
- ["Users", "/admin/users"],
- ["Roles", "/admin/roles"],
- ["Teams", "/admin/teams"],
- ["Factions", "/admin/factions"],
- ["Events", "/admin/events"],
- ["Permanences", "/admin/permanences"],
- ["Challenge", "/admin/challenge"],
- ["Export / Import", "/admin/export-import"],
- ["Email", "/admin/email"],
- ["News", "/admin/news"],
- ["Games", "/admin/games"],
- ];
-
- const canAccessAdminLink = (path: string) => {
- if (isAdmin) return true;
- if (isRespoCE && ["/admin/teams", "/admin/factions"].includes(path)) return true;
- if (isArbitre && ["/admin/challenge"].includes(path)) return true;
- if (isComm && ["/admin/news"].includes(path)) return true;
- return false;
- };
-
return (
-
-
+
+
{/* Logo */}
-
- UTT
Integration
-
+
+ UTT Integration
+
- {/* Hamburger */}
+ {/* Hamburger mobile */}
setMobileMenuOpen(!isMobileMenuOpen)}
+ onClick={() => setMenuOpen(!menuOpen)}
+ className="lg:hidden p-2 focus:outline-none"
aria-label="Toggle menu"
>
-
- {isMobileMenuOpen ? (
-
- ) : (
-
- )}
-
+ {menuOpen ? (
+
+ ) : (
+
+ )}
- {/* Desktop Menu */}
-
-
-
-
-
- {(isStudent || isAdmin) &&
}
-
- {/* Events Dropdown */}
-
+ {/* Menu desktop */}
+
+ {navItems.map(item =>
+ isAllowed(item) ? (
+
+ {item.children ? (
+
+ ) : (
+
+ )}
+
+ ) : null
+ )}
+
setIsEventsOpen((prev) => !prev)}
- className="px-3 py-2 text-sm hover:text-gray-200"
+ onClick={handleLogout}
+ className="text-sm hover:text-gray-200 transition"
>
- Events ▾
+ Déconnexion
-
- {isEventsOpen && (
-
- {(isStudent || isAdmin) && (
-
- Shotgun
-
- )}
-
- WEI
-
-
- SDI
-
-
- Repas
-
-
- )}
-
-
-
- {/* Admin Dropdown */}
- {(isAdmin || isRespoCE || isArbitre || isComm) && (
-
-
setIsAdminOpen((prev) => !prev)}
- className="px-3 py-2 text-sm hover:text-gray-200"
- >
- Admin ▾
-
-
- {isAdminOpen && (
-
- {adminLinks
- .filter(([_, path]) => canAccessAdminLink(path))
- .map(([label, path]) => (
-
- {label}
-
- ))}
-
- )}
-
-
- )}
-
-
-
-
- Déconnexion
-
-
+
+
- {/* Mobile Drawer */}
+ {/* Menu mobile */}
- {isMobileMenuOpen && (
-
-
-
-
-
-
-
- {(isStudent || isAdmin) && }
- {(isStudent || isAdmin) && }
-
-
-
- {(isAdmin || isRespoCE || isArbitre || isComm) && (
- <>
- Admin
- {adminLinks
- .filter(([_, path]) => canAccessAdminLink(path))
- .map(([label, path]) => (
-
- ))}
- >
- )}
-
-
-
+ {navItems.map(item =>
+ isAllowed(item) ? (
+
+ {!item.children ? (
+
+ ) : (
+
+ )}
+
+ ) : null
+ )}
+
+
Déconnexion
-
-
+
+
)}
);
};
+
+// 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";
+ return (
+
+ {item.icon && (
+
+ )}
+ {item.label}
+
+ );
+};
+
+// Composant Dropdown (desktop & mobile)
+// Composant Dropdown (desktop & mobile)
+const Dropdown = ({
+ item,
+ mobile = false,
+}: {
+ item: NavItem;
+ mobile?: boolean;
+}) => {
+ 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 (
+
+
setOpen(!open)}
+ className={`${trigger} flex items-center justify-between`}
+ aria-expanded={open}
+ aria-controls={`submenu-${item.label}`}
+ >
+ {item.icon && }
+ {item.label} ▾
+
+
+ {open && (
+
+ )}
+
+
+ );
+};
diff --git a/frontend/src/components/news/newsSection.tsx b/frontend/src/components/news/newsSection.tsx
index b5f60c6..e90de76 100644
--- a/frontend/src/components/news/newsSection.tsx
+++ b/frontend/src/components/news/newsSection.tsx
@@ -75,7 +75,7 @@ export const MyNews = () => {
/>
)}
{news.title}
-
{news.description}
+
{news.description}
Publié le {new Date(news.created_at).toLocaleDateString("fr-FR")}
diff --git a/frontend/src/components/permanence/appealPerm.tsx b/frontend/src/components/permanence/appealPerm.tsx
new file mode 100644
index 0000000..3ae8e1b
--- /dev/null
+++ b/frontend/src/components/permanence/appealPerm.tsx
@@ -0,0 +1,186 @@
+import { useEffect, useState } from "react";
+import { Button } from "../ui/button";
+import { Card } from "../ui/card";
+import { respoDetails, claimedMember } from "../../services/requests/permanence.service";
+import { Permanence } from "../../interfaces/permanence.interface";
+import { format } from "date-fns";
+import { fr } from "date-fns/locale";
+import { CheckCircle, XCircle, Circle } from "lucide-react";
+import Swal from "sweetalert2";
+
+interface Member {
+ id: number;
+ first_name: string;
+ last_name: string;
+ email: string;
+ claimed: boolean; // Ici : true = Présent, false = Absent, undefined = non noté
+}
+
+interface PermanenceWithMembers extends Permanence {
+ members: Member[];
+}
+
+export const RespoPresenceManagement = () => {
+ const [permanences, setPermanences] = useState
([]);
+ const [loading, setLoading] = useState(true);
+ const [expandedPermId, setExpandedPermId] = useState(null);
+
+ useEffect(() => {
+ fetchRespoPermanences();
+ }, []);
+
+ const fetchRespoPermanences = async () => {
+ try {
+ const result = await respoDetails();
+
+ const formatted: PermanenceWithMembers[] = result.data
+ .filter((item: any) => item.permanence)
+ .map((item: any) => ({
+ ...item.permanence,
+ members: item.members || [],
+ }));
+
+ // tri par date début
+ formatted.sort(
+ (a, b) => new Date(a.start_at).getTime() - new Date(b.start_at).getTime()
+ );
+
+ setPermanences(formatted);
+ } catch (err) {
+ Swal.fire({
+ icon: 'error',
+ title: 'Chargement échoué',
+ text: "Impossible de récupérer les permanences du responsable.",
+ });
+
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const handlePresence = async (userId: number, permId: number, present: boolean) => {
+ try {
+ await claimedMember(userId, permId, present);
+ fetchRespoPermanences();
+ } catch (err) {
+ Swal.fire({
+ icon: 'error',
+ title: 'Mise à jour échouée',
+ text: "Une erreur est survenue lors de la mise à jour de la présence.",
+ });
+
+ }
+ };
+
+ const toggleExpand = (permId: number) => {
+ setExpandedPermId((prev) => (prev === permId ? null : permId));
+ };
+
+ // Regroupement par jour
+ const groupedByDay = permanences.reduce((acc: any, perm) => {
+ const day = format(new Date(perm.start_at), "EEEE dd MMMM", { locale: fr });
+ if (!acc[day]) acc[day] = [];
+ acc[day].push(perm);
+ return acc;
+ }, {});
+
+ return (
+
+
+
+ ✅ Gestion des présences
+
+
+ {loading ? (
+ Chargement...
+ ) : Object.keys(groupedByDay).length === 0 ? (
+ Aucune permanence trouvée.
+ ) : (
+ Object.keys(groupedByDay).map((day) => (
+
+
+ 📅 {day}
+
+
+ {groupedByDay[day].map((perm: PermanenceWithMembers) => (
+
+ {/* Header collapsible */}
+ toggleExpand(perm.id)}
+ className="w-full flex justify-between items-center px-6 py-4 bg-white hover:bg-gray-100 transition"
+ >
+
+
+ 📍 {perm.name ?? "Nom inconnu"}
+
+
+ {perm.location ?? "Lieu inconnu"} —{" "}
+ {format(new Date(perm.start_at), "HH:mm", { locale: fr })} →{" "}
+ {format(new Date(perm.end_at), "HH:mm", { locale: fr })}
+
+
+
+ {expandedPermId === perm.id ? "▲" : "▼"}
+
+
+
+ {/* Liste des membres */}
+ {expandedPermId === perm.id && (
+
+ {perm.members.length === 0 ? (
+
+ Aucun membre inscrit.
+
+ ) : (
+
+ {perm.members.map((member) => (
+
+
+ {member.claimed === true && (
+
+ )}
+ {member.claimed === false && (
+
+ )}
+ {member.claimed === undefined && (
+
+ )}
+
+ {member.first_name} {member.last_name}
+
+
+
+ handlePresence(member.id, perm.id, true)}
+ className="bg-green-600 hover:bg-green-700 text-white text-xs px-3 py-1"
+ >
+ Présent
+
+ handlePresence(member.id, perm.id, false)}
+ className="bg-red-600 hover:bg-red-700 text-white text-xs px-3 py-1"
+ >
+ Absent
+
+
+
+ ))}
+
+ )}
+
+ )}
+
+ ))}
+
+ ))
+ )}
+
+
+ );
+};
\ No newline at end of file
diff --git a/frontend/src/components/permanence/permForm.tsx b/frontend/src/components/permanence/permForm.tsx
deleted file mode 100644
index 04bb8e4..0000000
--- a/frontend/src/components/permanence/permForm.tsx
+++ /dev/null
@@ -1,171 +0,0 @@
-import { useEffect, useState } from "react";
-import { Button } from "../ui/button";
-import {
- getOpenPermanences,
- getMyPermanences,
- applyToPermanence,
- cancelPermanence,
-} from "../../services/requests/permanence.service";
-import { Permanence } from "../../interfaces/permanence.interface";
-import { formatDateForDisplay } from "../utils/datetime_utils";
-
-export const PagePermanence: React.FC = () => {
- const [permanences, setPermanences] = useState([]);
- const [myPermanences, setMyPermanences] = useState([]);
- const [isSubmitting, setIsSubmitting] = useState(false);
-
- useEffect(() => {
- refreshAllPermanences();
- }, []);
-
- const refreshAllPermanences = () => {
- fetchOpenPermanences();
- fetchMyPermanences();
- };
-
- const fetchOpenPermanences = async () => {
- try {
- const perms = await getOpenPermanences();
- setPermanences(perms);
- } catch (err) {
- console.error("Erreur lors du chargement des permanences ouvertes", err);
- }
- };
-
- const fetchMyPermanences = async () => {
- try {
- const perms = await getMyPermanences();
- setMyPermanences(perms);
- } catch (err) {
- console.error("Erreur lors du chargement des permanences de l'utilisateur", err);
- }
- };
-
- const handleApplyToPermanence = async (permId: number) => {
- if (isSubmitting) return;
- setIsSubmitting(true);
- try {
- const response = await applyToPermanence(permId);
- alert(response.message);
- refreshAllPermanences();
- } catch (err) {
- console.error("Erreur lors de l'inscription à la permanence", err);
- } finally {
- setIsSubmitting(false);
- }
- };
-
- const handleCancelPermanence = async (permId: number) => {
- const selectedPermanence = myPermanences.find((perm) => perm.id === permId);
- if (!selectedPermanence) {
- alert("Permanence non trouvée.");
- return;
- }
-
- const now = new Date();
- const deadline = new Date(selectedPermanence.start_at);
- deadline.setDate(deadline.getDate() - 1);
-
- if (now > deadline) {
- alert("La désinscription n'est plus possible.");
- return;
- }
-
- if (!confirm("Êtes-vous sûr de vouloir vous désinscrire ?")) return;
-
- try {
- const response = await cancelPermanence(permId);
- alert(response.message);
- refreshAllPermanences();
- } catch (err) {
- console.error("Erreur lors de l'annulation de la permanence", err);
- }
- };
-
- const sortedOpenPermanences = [...permanences].sort(
- (a, b) => new Date(a.start_at).getTime() - new Date(b.start_at).getTime()
- );
-
- const sortedMyPermanences = [...myPermanences].sort(
- (a, b) => new Date(a.start_at).getTime() - new Date(b.start_at).getTime()
- );
-
- return (
-
-
- {/* Permanences disponibles */}
-
-
- 📅 Permanences disponibles
-
- {sortedOpenPermanences.length > 0 ? (
-
- {sortedOpenPermanences.map((perm) => {
- const isAlreadyRegistered = myPermanences.some((myPerm) => myPerm.id === perm.id);
- if (isAlreadyRegistered) return null;
-
- return (
-
-
-
{perm.name}
-
📍 Lieu : {perm.location}
-
🕒 Début : {formatDateForDisplay(perm.start_at)}
-
🕔 Fin : {formatDateForDisplay(perm.end_at)}
-
👥 Capacité restante : {perm.capacity}
-
-
handleApplyToPermanence(perm.id)}
- disabled={perm.capacity <= 0 || isSubmitting}
- className={`w-full py-2 mt-2 text-white rounded-md ${
- perm.capacity <= 0
- ? "bg-gray-400 cursor-not-allowed"
- : "bg-blue-600 hover:bg-blue-700"
- }`}
- >
- {perm.capacity <= 0 ? "Complet" : "S'inscrire"}
-
-
- );
- })}
-
- ) : (
- Aucune permanence disponible.
- )}
-
-
- {/* Mes permanences */}
-
-
- ✅ Mes permanences
-
- {sortedMyPermanences.length > 0 ? (
-
- {sortedMyPermanences.map((perm) => (
-
-
{perm.name}
-
📍 Lieu : {perm.location}
-
🕒 Début : {formatDateForDisplay(perm.start_at)}
-
🕔 Fin : {formatDateForDisplay(perm.end_at)}
-
handleCancelPermanence(perm.id)}
- className="w-full bg-red-600 hover:bg-red-700 text-white py-2 rounded-md"
- >
- Se désinscrire
-
-
- ))}
-
- ) : (
- Aucune permanence à laquelle vous êtes inscrit.
- )}
-
-
-
- );
-};
diff --git a/frontend/src/components/permanence/permList.tsx b/frontend/src/components/permanence/permList.tsx
new file mode 100644
index 0000000..aca3c42
--- /dev/null
+++ b/frontend/src/components/permanence/permList.tsx
@@ -0,0 +1,82 @@
+import { Button } from "../ui/button";
+import { Permanence } from "../../interfaces/permanence.interface";
+import { format } from "date-fns";
+import { fr } from "date-fns/locale";
+
+interface AvailablePermanencesListProps {
+ permanences: Permanence[];
+ myPermanences: Permanence[];
+ isSubmitting: boolean;
+ onApply: (permId: number) => void;
+}
+
+export const AvailablePermanencesList: React.FC = ({
+ permanences,
+ myPermanences,
+ isSubmitting,
+ onApply,
+}) => {
+ // Tri global par date
+ const sortedOpenPermanences = [...permanences]
+ .sort((a, b) => new Date(a.start_at).getTime() - new Date(b.start_at).getTime())
+ .filter((perm) => !myPermanences.some((myPerm) => myPerm.id === perm.id));
+
+ // Groupement par jour
+ const groupedByDay = sortedOpenPermanences.reduce((groups: Record, perm) => {
+ const dateKey = format(new Date(perm.start_at), "EEEE dd MMMM", { locale: fr }); // Exemple : "lundi 23 août"
+ if (!groups[dateKey]) groups[dateKey] = [];
+ groups[dateKey].push(perm);
+ return groups;
+ }, {});
+
+ return (
+
+
+ 📅 Permanences disponibles
+
+
+ {sortedOpenPermanences.length > 0 ? (
+
+ {Object.entries(groupedByDay).map(([day, perms]) => (
+
+
+ {day.charAt(0).toUpperCase() + day.slice(1)} {/* met la majuscule */}
+
+
+ {perms.map((perm) => (
+
+
+
{perm.name}
+
📍 Lieu : {perm.location}
+
🕒 Début : {format(new Date(perm.start_at), "HH:mm")}
+
🕔 Fin : {format(new Date(perm.end_at), "HH:mm")}
+
👥 Capacité restante : {perm.capacity}
+
+
onApply(perm.id)}
+ disabled={perm.capacity <= 0 || isSubmitting}
+ className={`w-full py-2 mt-2 text-white rounded-md ${
+ perm.capacity <= 0
+ ? "bg-gray-400 cursor-not-allowed"
+ : "bg-blue-600 hover:bg-blue-700"
+ }`}
+ >
+ {perm.capacity <= 0 ? "Complet" : "S'inscrire"}
+
+
+ ))}
+
+
+ ))}
+
+ ) : (
+
+ Aucune permanence disponible.
+
+ )}
+
+ );
+};
diff --git a/frontend/src/components/permanence/permUser.tsx b/frontend/src/components/permanence/permUser.tsx
new file mode 100644
index 0000000..f033a8b
--- /dev/null
+++ b/frontend/src/components/permanence/permUser.tsx
@@ -0,0 +1,85 @@
+import { Button } from "../ui/button";
+import { Permanence } from "../../interfaces/permanence.interface";
+import { format } from "date-fns";
+import { fr } from "date-fns/locale";
+
+interface MyPermanencesListProps {
+ myPermanences: Permanence[];
+ onCancel: (permId: number) => void;
+}
+
+export const MyPermanencesList: React.FC = ({
+ myPermanences,
+ onCancel,
+}) => {
+ // Tri chronologique
+ const sortedMyPermanences = [...myPermanences].sort(
+ (a, b) => new Date(a.start_at).getTime() - new Date(b.start_at).getTime()
+ );
+
+ // Regroupement par jour
+ const groupedByDay = sortedMyPermanences.reduce(
+ (groups: Record, perm) => {
+ const dateKey = format(new Date(perm.start_at), "EEEE dd MMMM", {
+ locale: fr,
+ });
+ if (!groups[dateKey]) groups[dateKey] = [];
+ groups[dateKey].push(perm);
+ return groups;
+ },
+ {}
+ );
+
+ return (
+
+
+ ✅ Mes permanences
+
+
+ {sortedMyPermanences.length > 0 ? (
+
+ {Object.entries(groupedByDay).map(([day, perms]) => (
+
+
+ {day.charAt(0).toUpperCase() + day.slice(1)}
+
+
+ {perms.map((perm) => (
+
+
+ {perm.name}
+
+
+ 📍 Lieu : {perm.location}
+
+
+ 🕒 Début : {" "}
+ {format(new Date(perm.start_at), "HH:mm")}
+
+
+ 🕔 Fin : {" "}
+ {format(new Date(perm.end_at), "HH:mm")}
+
+
onCancel(perm.id)}
+ className="w-full bg-red-600 hover:bg-red-700 text-white py-2 rounded-md"
+ >
+ Se désinscrire
+
+
+ ))}
+
+
+ ))}
+
+ ) : (
+
+ Aucune permanence à laquelle vous êtes inscrit.
+
+ )}
+
+ );
+};
diff --git a/frontend/src/components/tent/tentSection.tsx b/frontend/src/components/tent/tentSection.tsx
new file mode 100644
index 0000000..a1c7ec2
--- /dev/null
+++ b/frontend/src/components/tent/tentSection.tsx
@@ -0,0 +1,211 @@
+import { useState, useEffect } from "react";
+import { createTent, getUserTent, cancelTent } from "../../services/requests/tent.service";
+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(null);
+ const [tentInfo, setTentInfo] = useState(null);
+ const [users, setUsers] = useState([]);
+ const [isWEIOpen, setIsWEIOpen] = useState(false);
+
+ useEffect(() => {
+ const fetchUsers = async () => {
+ try {
+ const result = await getUsers();
+ setUsers(result);
+ } catch {
+ Swal.fire("Erreur", "Impossible de charger les utilisateurs", "error");
+ }
+ };
+
+ const fetchTent = async () => {
+ try {
+ const result = await getUserTent();
+ if (result?.data && result.data.length > 0) {
+ setTentInfo(result.data[0]);
+ }
+ } catch {
+ Swal.fire("Erreur", "Impossible de récupérer la tente", "error");
+ }
+ };
+
+ const fetchWEIStatus = async () => {
+ try {
+ const status = await checkWEIStatus();
+ setIsWEIOpen(status);
+ } catch {
+ Swal.fire("Erreur", "Impossible de récupérer le statut du WEI", "error");
+ }
+ };
+
+ fetchUsers();
+ fetchTent();
+ fetchWEIStatus();
+ }, []);
+
+ const token = getToken();
+ if (!token) return null;
+ const { userId } = decodeToken(token);
+
+ const handleCreate = async () => {
+ if (!userId2) {
+ return Swal.fire("Erreur", "Sélectionne ton binôme", "error");
+ }
+
+ try {
+ Swal.fire({
+ title: "Création...",
+ text: "Ta tente est en cours de création",
+ allowOutsideClick: false,
+ didOpen: () => Swal.showLoading(),
+ });
+
+ await createTent(userId2);
+
+ Swal.fire("✅ Succès", "Ta tente a été créée avec succès !", "success");
+ handleGetTent();
+ } catch (err: any) {
+ Swal.fire("Erreur", err.message || "Impossible de créer la tente", "error");
+ }
+ };
+
+ const handleCancel = async () => {
+ const confirm = await Swal.fire({
+ title: "Annuler la tente ?",
+ text: "Tu ne pourras pas revenir en arrière.",
+ icon: "warning",
+ showCancelButton: true,
+ confirmButtonColor: "#d33",
+ cancelButtonColor: "#3085d6",
+ confirmButtonText: "Oui, annuler",
+ cancelButtonText: "Non, garder",
+ });
+
+ if (!confirm.isConfirmed) return;
+
+ try {
+ await cancelTent();
+ setTentInfo(null);
+ setUserId2(null);
+ Swal.fire("🛑 Annulée", "Ta tente a bien été annulée", "success");
+ } catch {
+ Swal.fire("Erreur", "Impossible d'annuler la tente", "error");
+ }
+ };
+
+ const handleGetTent = async () => {
+ try {
+ const result = await getUserTent();
+ if (result?.data && result.data.length > 0) {
+ setTentInfo(result.data[0]);
+ }
+ } catch {
+ Swal.fire("Erreur", "Impossible de récupérer la tente", "error");
+ }
+ };
+
+ return isWEIOpen ? (
+
+
+
🏕️ Réserve ta tente
+
+ {!tentInfo ? (
+ <>
+
+ Choisis ton binôme :
+ user.userId !== userId)
+ .map((user: User) => ({
+ value: user.userId,
+ label: `${user.firstName} ${user.lastName}`,
+ }))}
+ value={
+ userId2
+ ? {
+ value: userId2,
+ label: `${users.find((u) => u.userId === userId2)?.firstName || ""} ${
+ users.find((u) => u.userId === userId2)?.lastName || ""
+ }`,
+ }
+ : null
+ }
+ onChange={(option) => setUserId2(option?.value || null)}
+ isClearable
+ className="shadow-sm"
+ />
+
+
+
+
+ ✅ Créer
+
+
+ >
+ ) : (
+
+
🎫 Ta tente
+
+ Binôme avec {" "}
+
+ {
+ users.find(
+ (user) =>
+ user.userId ===
+ (tentInfo.user_id_1 === userId ? tentInfo.user_id_2 : tentInfo.user_id_1)
+ )?.firstName
+ } {" "}
+ {
+ users.find(
+ (user) =>
+ user.userId ===
+ (tentInfo.user_id_1 === userId ? tentInfo.user_id_2 : tentInfo.user_id_1)
+ )?.lastName
+ }
+
+
+
+
+ {tentInfo.confirmed ? (
+
✅ Ta tente est confirmée !
+ ) : (
+
+ ⏳ En attente de confirmation – tu recevras un mail bientôt.
+
+ )}
+
+
+
+
+ ❌ Annuler
+
+
+
+ )}
+
+
+ ) : (
+
+
🚫 Réservations fermées
+
+ La réservation de tentes pour le WEI n’est pas encore disponible.
+ 🔔 Reste connecté, elle ouvrira bientôt !
+
+
+ );
+};
\ No newline at end of file
diff --git a/frontend/src/components/ui/accordion.tsx b/frontend/src/components/ui/accordion.tsx
new file mode 100644
index 0000000..0633286
--- /dev/null
+++ b/frontend/src/components/ui/accordion.tsx
@@ -0,0 +1,64 @@
+import * as React from "react"
+import * as AccordionPrimitive from "@radix-ui/react-accordion"
+import { ChevronDownIcon } from "lucide-react"
+
+import { cn } from "../../lib/utils"
+
+function Accordion({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function AccordionItem({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function AccordionTrigger({
+ className,
+ children,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ svg]:rotate-180",
+ className
+ )}
+ {...props}
+ >
+ {children}
+
+
+
+ )
+}
+
+function AccordionContent({
+ className,
+ children,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ {children}
+
+ )
+}
+
+export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
diff --git a/frontend/src/components/ui/calendar.tsx b/frontend/src/components/ui/calendar.tsx
deleted file mode 100644
index 3d033cf..0000000
--- a/frontend/src/components/ui/calendar.tsx
+++ /dev/null
@@ -1,73 +0,0 @@
-import * as React from "react"
-import { ChevronLeft, ChevronRight } from "lucide-react"
-import { DayPicker } from "react-day-picker"
-
-import { cn } from "../../lib/utils"
-import { buttonVariants } from "./button"
-
-function Calendar({
- className,
- classNames,
- showOutsideDays = true,
- ...props
-}: React.ComponentProps) {
- return (
- .day-range-end)]:rounded-r-md [&:has(>.day-range-start)]:rounded-l-md first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md"
- : "[&:has([aria-selected])]:rounded-md"
- ),
- day: cn(
- buttonVariants({ variant: "ghost" }),
- "size-8 p-0 font-normal aria-selected:opacity-100"
- ),
- day_range_start:
- "day-range-start aria-selected:bg-primary aria-selected:text-primary-foreground",
- day_range_end:
- "day-range-end aria-selected:bg-primary aria-selected:text-primary-foreground",
- day_selected:
- "bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground",
- day_today: "bg-accent text-accent-foreground",
- day_outside:
- "day-outside text-muted-foreground aria-selected:text-muted-foreground",
- day_disabled: "text-muted-foreground opacity-50",
- day_range_middle:
- "aria-selected:bg-accent aria-selected:text-accent-foreground",
- day_hidden: "invisible",
- ...classNames,
- }}
- components={{
- IconLeft: ({ className, ...props }) => (
-
- ),
- IconRight: ({ className, ...props }) => (
-
- ),
- }}
- {...props}
- />
- )
-}
-
-export { Calendar }
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/interfaces/permanence.interface.ts b/frontend/src/interfaces/permanence.interface.ts
index cde3d42..0880fb9 100644
--- a/frontend/src/interfaces/permanence.interface.ts
+++ b/frontend/src/interfaces/permanence.interface.ts
@@ -1,12 +1,17 @@
+import { User } from "./user.interface";
+
export interface Permanence {
id: number;
- name: string;
+ name: string;
+ description : string;
start_at: string;
end_at: string;
location: string;
capacity: number;
- isOpen: boolean;
+ is_open: boolean;
createdAt: string;
- updatedAt: string;
- }
+ updatedAt: string;
+ difficulty : number;
+ respo: User;
+}
\ No newline at end of file
diff --git a/frontend/src/interfaces/tent.interface.ts b/frontend/src/interfaces/tent.interface.ts
new file mode 100644
index 0000000..4483438
--- /dev/null
+++ b/frontend/src/interfaces/tent.interface.ts
@@ -0,0 +1,6 @@
+
+export interface Tent {
+ user_id_1: number;
+ user_id_2: number;
+ confirmed: boolean;
+}
\ No newline at end of file
diff --git a/frontend/src/pages/Admin.tsx b/frontend/src/pages/Admin.tsx
deleted file mode 100644
index 51f1f13..0000000
--- a/frontend/src/pages/Admin.tsx
+++ /dev/null
@@ -1,159 +0,0 @@
-import { AdminRoleManagement, AdminRolePreferences } from "../components/Admin/adminRole";
-import { AdminEvents } from "../components/Admin/adminEvent";
-import { AdminTeamManagement, DistributeTeam } from "../components/Admin/adminTeam"; // Importer le composant
-import { AdminLayout } from "../components/Admin/adminLayout";
-import { AdminExportConnect, ImportPermCSV } from "../components/Admin/adminExportImport";
-import { AdminFactionManagement } from "../components/Admin/adminFaction";
-import { AdminPermanence } from "../components/Admin/adminPerm";
-import { AdminChallengeAddPointsForm, AdminChallengeForm, AdminValidatedChallengesList } from "../components/Admin/adminChallenge";
-import { AdminEmail } from "../components/Admin/adminEmail";
-import { AdminSyncNewStudent, AdminUser } from "../components/Admin/adminUser";
-import { AdminNews } from "../components/Admin/adminNews";
-import { AdminRolePointsManager } from "../components/Admin/adminGames";
-
-
-
-export const AdminPageTeam: React.FC = () => {
- return (
-
-
-
- );
-};
-
-export const AdminPageFaction: React.FC = () => {
- return (
-
-
-
- );
-};
-
-export const AdminPageRole: React.FC = () => {
- return (
-
-
-
- );
-};
-
-export const AdminPageEvents: React.FC = () => {
- return (
-
-
-
- );
-};
-
-export const AdminPageExport: React.FC = () => {
- return (
-
-
-
- );
-};
-
-
-export const AdminPagePerm: React.FC = () => {
- return (
-
-
-
- );
-};
-
-export const AdminPageChall: React.FC = () => {
- return (
-
-
-
- < AdminChallengeForm/>
- < AdminChallengeAddPointsForm/>
-
-
-
-
- );
-};
-
-export const AdminPageEmail: React.FC = () => {
- return (
-
-
-
-
-
- );
-};
-
-export const AdminPageUser: React.FC = () => {
- return (
-
-
-
-
- < AdminSyncNewStudent/>
-
-
-
- );
-};
-
-export const AdminPageNews: React.FC = () => {
- return (
-
-
-
-
-
- );
-};
-
-export const AdminPageGames: React.FC = () => {
- return (
-
-
-
- < AdminRolePointsManager/>
-
-
-
- );
-};
\ No newline at end of file
diff --git a/frontend/src/pages/Event.tsx b/frontend/src/pages/Event.tsx
deleted file mode 100644
index e69de29..0000000
diff --git a/frontend/src/pages/Perm.tsx b/frontend/src/pages/Perm.tsx
deleted file mode 100644
index 6238fd8..0000000
--- a/frontend/src/pages/Perm.tsx
+++ /dev/null
@@ -1,17 +0,0 @@
-import { Navbar } from "../components/navbar";
-import { PagePermanence } from "../components/permanence/permForm";
-
-export const PermPage = () => (
-
-
-);
diff --git a/frontend/src/pages/admin.tsx b/frontend/src/pages/admin.tsx
new file mode 100644
index 0000000..c15656f
--- /dev/null
+++ b/frontend/src/pages/admin.tsx
@@ -0,0 +1,368 @@
+import { motion } from "framer-motion";
+
+import { AdminRoleManagement, AdminRolePreferences } from "../components/Admin/adminRole";
+import { AdminEvents } from "../components/Admin/adminEvent";
+import { AdminTeamManagement, DistributeTeam } from "../components/Admin/adminTeam";
+import { AdminLayout } from "../components/Admin/adminLayout";
+import { AdminExportConnect, AdminImportFoodMenu, AdminImportPlannings } from "../components/Admin/adminExportImport";
+import { AdminFactionManagement } from "../components/Admin/adminFaction";
+import { AdminEmail } from "../components/Admin/adminEmail";
+import { AdminSyncNewStudent, AdminUser } from "../components/Admin/adminUser";
+import { AdminNews } from "../components/Admin/adminNews";
+import { AdminRolePointsManager } from "../components/Admin/adminGames";
+
+//--------------Challenge Import--------------//
+import ChallengeEditor from "../components/Admin/AdminChallenge/adminChallengeEditor";
+import AdminChallengeList from "../components/Admin/AdminChallenge/adminChalengeList";
+import { useEffect, useRef, useState } from "react";
+import { Challenge } from "../interfaces/challenge.interface";
+import { getAllChallenges } from "../services/requests/challenge.service";
+import { AdminChallengeAddPointsForm } from "../components/Admin/AdminChallenge/adminChallengeAddPointsForm";
+import { AdminValidatedChallengesList } from "../components/Admin/AdminChallenge/adminChallengeValidatedList";
+import { TentAdmin } from "../components/Admin/adminTent";
+
+
+//--------------Perm Import--------------//
+import PermanenceActions from "../components/Admin/AdminPerm/adminPermAction";
+import PermanenceForm from "../components/Admin/AdminPerm/adminPermForm";
+import { ImportPermCSV } from "../components/Admin/AdminPerm/adminPermImport";
+import PermanenceList from "../components/Admin/AdminPerm/adminPermList";
+import { Permanence } from "../interfaces/permanence.interface";
+import { User } from "../interfaces/user.interface";
+import { getAllPermanences } from "../services/requests/permanence.service";
+import { getUsersAdmin } from "../services/requests/user.service";
+
+
+
+export const AdminPageTeam: React.FC = () => {
+ return (
+
+
+
+ );
+};
+
+export const AdminPageFaction: React.FC = () => {
+ return (
+
+
+
+ );
+};
+
+export const AdminPageRole: React.FC = () => {
+ return (
+
+
+
+ );
+};
+
+export const AdminPageEvents: React.FC = () => {
+ return (
+
+
+
+ );
+};
+
+export const AdminPageExport: React.FC = () => {
+ return (
+
+
+
+ );
+};
+
+
+export const AdminPagePerm: React.FC = () => {
+ const [permanences, setPermanences] = useState([]);
+ const [users, setUsers] = useState([]);
+ const [editMode, setEditMode] = useState(false);
+ const [editPermanence, setEditPermanence] = useState(null);
+
+ const editorRef = useRef(null);
+
+ useEffect(() => {
+ void fetchPermanences();
+ void fetchUsers();
+ }, []);
+
+ const fetchPermanences = async () => {
+ const res = await getAllPermanences();
+ setPermanences(res.data as Permanence[]);
+ };
+
+ const fetchUsers = async () => {
+ const res = await getUsersAdmin();
+ setUsers(res as User[]);
+ };
+
+ return (
+
+
+
+ {/* Formulaire (créer/éditer) */}
+
+
+ {editMode ? "✏️ Modifier une permanence" : "➕ Créer une permanence"}
+
+
+ {
+ setEditMode(false);
+ setEditPermanence(null);
+ }}
+ />
+
+
+ {/* Liste des permanences */}
+
+
+ 📋 Permanences existantes
+
+ {
+ setEditMode(true);
+ setEditPermanence(perm);
+ setTimeout(() => {
+ editorRef.current?.scrollIntoView({ behavior: "smooth" });
+ }, 100);
+ }}
+ />
+
+
+ {/* Actions globales */}
+
+
+ ⚡ Actions rapides
+
+
+
+
+ {/* Import CSV (si dispo) */}
+
+
+ 📂 Importer des permanences (CSV)
+
+
+
+
+
+ );
+};
+
+
+
+
+export const AdminPageChall: React.FC = () => {
+ const [challenges, setChallenges] = useState([]);
+ const [editingChallenge, setEditingChallenge] = useState(null);
+ const editorRef = useRef(null);
+
+ const fetchChallenges = async () => {
+ try {
+ const res = await getAllChallenges();
+ setChallenges(res);
+ } catch (err) {
+ console.error("Erreur chargement challenges", err);
+ }
+ };
+
+ useEffect(() => {
+ fetchChallenges();
+ }, []);
+
+ const handleEdit = (challenge: Challenge) => {
+ setEditingChallenge(challenge);
+ editorRef.current?.scrollIntoView({ behavior: "smooth" });
+ };
+
+ return (
+
+
+
+ {/* Formulaire création / édition */}
+
+
+
+
+ {/* Liste des challenges */}
+
+
+
+
+ {/* Ajout de points */}
+
+
+
+
+
+ {/* Liste des challenges validés */}
+
+
+
+
+ );
+};
+
+export const AdminPageEmail: React.FC = () => {
+ return (
+
+
+
+
+
+ );
+};
+
+export const AdminPageUser: React.FC = () => {
+ return (
+
+
+
+
+ < AdminSyncNewStudent/>
+
+
+
+ );
+};
+
+export const AdminPageNews: React.FC = () => {
+ return (
+
+
+
+
+
+ );
+};
+
+export const AdminPageTent: React.FC = () => {
+ return (
+
+
+
+
+
+ );
+};
+
+export const AdminPageGames: React.FC = () => {
+ return (
+
+
+
+ < AdminRolePointsManager/>
+
+
+
+ );
+};
\ No newline at end of file
diff --git a/frontend/src/pages/Auth.tsx b/frontend/src/pages/auth.tsx
similarity index 100%
rename from frontend/src/pages/Auth.tsx
rename to frontend/src/pages/auth.tsx
diff --git a/frontend/src/pages/Challenge.tsx b/frontend/src/pages/challenge.tsx
similarity index 79%
rename from frontend/src/pages/Challenge.tsx
rename to frontend/src/pages/challenge.tsx
index e2ae459..502f20a 100644
--- a/frontend/src/pages/Challenge.tsx
+++ b/frontend/src/pages/challenge.tsx
@@ -1,4 +1,4 @@
-import { ChallengeList } from "../components/challenge/challengeList";
+import { UserChallengeList } from "../components/challenge/challengeList";
import { Navbar } from "../components/navbar";
@@ -8,7 +8,7 @@ export const ChallPage = () => (
diff --git a/frontend/src/pages/Discord.tsx b/frontend/src/pages/discord.tsx
similarity index 100%
rename from frontend/src/pages/Discord.tsx
rename to frontend/src/pages/discord.tsx
diff --git a/frontend/src/pages/Food.tsx b/frontend/src/pages/food.tsx
similarity index 100%
rename from frontend/src/pages/Food.tsx
rename to frontend/src/pages/food.tsx
diff --git a/frontend/src/pages/Games.tsx b/frontend/src/pages/games.tsx
similarity index 100%
rename from frontend/src/pages/Games.tsx
rename to frontend/src/pages/games.tsx
diff --git a/frontend/src/pages/Home.tsx b/frontend/src/pages/home.tsx
similarity index 100%
rename from frontend/src/pages/Home.tsx
rename to frontend/src/pages/home.tsx
diff --git a/frontend/src/pages/News.tsx b/frontend/src/pages/news.tsx
similarity index 100%
rename from frontend/src/pages/News.tsx
rename to frontend/src/pages/news.tsx
diff --git a/frontend/src/pages/Parrainage.tsx b/frontend/src/pages/parrainage.tsx
similarity index 100%
rename from frontend/src/pages/Parrainage.tsx
rename to frontend/src/pages/parrainage.tsx
diff --git a/frontend/src/pages/perm.tsx b/frontend/src/pages/perm.tsx
new file mode 100644
index 0000000..9e35979
--- /dev/null
+++ b/frontend/src/pages/perm.tsx
@@ -0,0 +1,201 @@
+// src/pages/PageAvailablePermanences.tsx
+import { useEffect, useState } from "react";
+import Swal from "sweetalert2";
+import {
+ getOpenPermanences,
+ getMyPermanences,
+ applyToPermanence,
+ isUserRespo,
+ cancelPermanence,
+} from "../services/requests/permanence.service";
+import { Permanence } from "../interfaces/permanence.interface";
+import { AvailablePermanencesList } from "../components/permanence/permList";
+import { Navigate } from "react-router-dom";
+import { decodeToken, getToken } from "../services/requests/auth.service";
+import { DecodedToken } from "../interfaces/token.interfaces";
+import { Navbar } from "../components/navbar";
+import { RespoPresenceManagement } from "../components/permanence/appealPerm";
+import { MyPermanencesList } from "../components/permanence/permUser";
+
+export const AvailablePermanencesPage: React.FC = () => {
+ const [permanences, setPermanences] = useState([]);
+ const [myPermanences, setMyPermanences] = useState([]);
+ const [isSubmitting, setIsSubmitting] = useState(false);
+
+ useEffect(() => {
+ fetchData();
+ }, []);
+
+ const fetchData = async () => {
+ try {
+ const [openPerms, myPerms] = await Promise.all([
+ getOpenPermanences(),
+ getMyPermanences(),
+ ]);
+ setPermanences(openPerms);
+ setMyPermanences(myPerms);
+ } catch (err) {
+ console.error("Erreur lors du chargement des permanences", err);
+ Swal.fire("Erreur", "Impossible de charger les permanences.", "error");
+ }
+ };
+
+ const handleApplyToPermanence = async (permId: number) => {
+ if (isSubmitting) return;
+ setIsSubmitting(true);
+
+ try {
+ const response = await applyToPermanence(permId);
+ await Swal.fire("Succès ✅", response.message, "success");
+ fetchData();
+ } catch (err) {
+ console.error("Erreur lors de l'inscription", err);
+ Swal.fire("Erreur", "Impossible de s’inscrire à la permanence.", "error");
+ } finally {
+ setIsSubmitting(false);
+ }
+ };
+
+ return (
+
+ );
+};
+
+
+export const MyPermanencesPage: React.FC = () => {
+ const [myPermanences, setMyPermanences] = useState([]);
+
+ useEffect(() => {
+ fetchMyPermanences();
+ }, []);
+
+ const fetchMyPermanences = async () => {
+ try {
+ const perms = await getMyPermanences();
+ setMyPermanences(perms);
+ } catch (err) {
+ console.error("Erreur lors du chargement", err);
+ Swal.fire("Erreur", "Impossible de charger vos permanences.", "error");
+ }
+ };
+
+ const handleCancelPermanence = async (permId: number) => {
+ const selectedPermanence = myPermanences.find((perm) => perm.id === permId);
+ if (!selectedPermanence) {
+ Swal.fire("Erreur", "Permanence non trouvée.", "error");
+ return;
+ }
+
+ const now = new Date();
+ const deadline = new Date(selectedPermanence.start_at);
+ deadline.setDate(deadline.getDate() - 1);
+
+ if (now > deadline) {
+ Swal.fire("Info", "La désinscription n'est plus possible.", "info");
+ return;
+ }
+
+ const result = await Swal.fire({
+ title: "Êtes-vous sûr ?",
+ text: "Vous ne pourrez plus revenir en arrière.",
+ icon: "warning",
+ showCancelButton: true,
+ confirmButtonText: "Oui, me désinscrire",
+ cancelButtonText: "Annuler",
+ confirmButtonColor: "#d33",
+ cancelButtonColor: "#3085d6",
+ });
+
+ if (!result.isConfirmed) return;
+
+ try {
+ const response = await cancelPermanence(permId);
+ Swal.fire("Succès ✅", response.message, "success");
+ fetchMyPermanences();
+ } catch (err) {
+ console.error("Erreur lors de l'annulation", err);
+ Swal.fire("Erreur", "Impossible de vous désinscrire.", "error");
+ }
+ };
+
+ return (
+
+ );
+};
+
+
+export const RespoCallPage = () => {
+ const [isRespo, setIsRespo] = useState(null);
+
+ useEffect(() => {
+ const checkRespoStatus = async () => {
+ try {
+ const token = getToken();
+
+ if (!token) {
+ return ;
+ }
+
+ let decoded: DecodedToken;
+ try {
+ decoded = decodeToken(token);
+ } catch (err) {
+ return ;
+ }
+
+ const result = await isUserRespo(Number(decoded.userId));
+ setIsRespo(result.data === true);
+ } catch (err) {
+ console.error("Erreur lors de la vérification du rôle respo", err);
+ setIsRespo(false);
+ }
+ };
+
+ checkRespoStatus();
+ }, []);
+
+ return (
+
+
+
+
+ {isRespo === null ? (
+
Chargement...
+ ) : isRespo === false ? (
+
+ ❌ Accès refusé : vous n'êtes pas responsable d'une permanence.
+
+ ) : (
+
+ )}
+
+
+
+
+ );
+};
+
diff --git a/frontend/src/pages/Wei.tsx b/frontend/src/pages/plannings.tsx
similarity index 59%
rename from frontend/src/pages/Wei.tsx
rename to frontend/src/pages/plannings.tsx
index e63d203..ca8f0bb 100644
--- a/frontend/src/pages/Wei.tsx
+++ b/frontend/src/pages/plannings.tsx
@@ -1,20 +1,17 @@
import { Navbar } from "../components/navbar";
-import { WeiSection } from "../components/WEI_SDI_Food/weiSection";
+import { PlanningSection } from "../components/Plannings/planningSection";
-export const WeiPage = () => {
-
- return(
+export const PlanningsPage = () => (
+
© 2025 Semaine d'Intégration UTT
+
- );
-}
\ No newline at end of file
+);
\ No newline at end of file
diff --git a/frontend/src/pages/Profil.tsx b/frontend/src/pages/profil.tsx
similarity index 99%
rename from frontend/src/pages/Profil.tsx
rename to frontend/src/pages/profil.tsx
index 12fa7e0..43afa08 100644
--- a/frontend/src/pages/Profil.tsx
+++ b/frontend/src/pages/profil.tsx
@@ -13,7 +13,7 @@ export const ProfilPage = () => {
if (!permission) {
navigate("/");
return null;
-}
+ }
return (
diff --git a/frontend/src/pages/Register.tsx b/frontend/src/pages/register.tsx
similarity index 100%
rename from frontend/src/pages/Register.tsx
rename to frontend/src/pages/register.tsx
diff --git a/frontend/src/pages/ResetPassword.tsx b/frontend/src/pages/resetPassword.tsx
similarity index 100%
rename from frontend/src/pages/ResetPassword.tsx
rename to frontend/src/pages/resetPassword.tsx
diff --git a/frontend/src/pages/Sdi.tsx b/frontend/src/pages/sdi.tsx
similarity index 100%
rename from frontend/src/pages/Sdi.tsx
rename to frontend/src/pages/sdi.tsx
diff --git a/frontend/src/pages/Shotgun.tsx b/frontend/src/pages/shotgun.tsx
similarity index 100%
rename from frontend/src/pages/Shotgun.tsx
rename to frontend/src/pages/shotgun.tsx
diff --git a/frontend/src/pages/wei.tsx b/frontend/src/pages/wei.tsx
new file mode 100644
index 0000000..88377e5
--- /dev/null
+++ b/frontend/src/pages/wei.tsx
@@ -0,0 +1,31 @@
+import { Navbar } from "../components/navbar";
+import { WeiSection } from "../components/WEI_SDI_Food/weiSection";
+import { TentPublic } from "../components/tent/tentSection";
+import { useNavigate } from "react-router-dom";
+import { getPermission } from "../services/requests/user.service";
+
+export const WeiPage = () => {
+
+ const navigate = useNavigate();
+ const permission = getPermission();
+
+ if (!permission) {
+ navigate("/");
+ return null;
+ }
+
+ return (
+
+
+
+
+
+ {(permission === "Nouveau" || permission === "Admin") && }
+
+
+
+
+ );
+};
\ No newline at end of file
diff --git a/frontend/src/services/requests/event.service.ts b/frontend/src/services/requests/event.service.ts
index 23a30de..670a864 100644
--- a/frontend/src/services/requests/event.service.ts
+++ b/frontend/src/services/requests/event.service.ts
@@ -35,6 +35,13 @@ export const checkFoodStatus = async () => {
};
+export const checkChallengeStatus = async () => {
+
+ const response = await api.get("/event/user/challstatus");
+ return response.data.data;
+
+};
+
export const attemptShotgun = async () => {
const response = await api.post("event/user/shotgunattempt");
@@ -79,4 +86,12 @@ export const toggleFood = async (foodOpen: boolean) => {
return response.data;
+};
+
+export const toggleChallenge = async (challOpen: boolean) => {
+
+ const response = await api.post(`event/admin/challtoggle`, {challOpen});
+ return response.data;
+
+
};
\ No newline at end of file
diff --git a/frontend/src/services/requests/export.service.ts b/frontend/src/services/requests/export.service.ts
deleted file mode 100644
index e86ce81..0000000
--- a/frontend/src/services/requests/export.service.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-import api from '../api';
-
-// Fonction pour initier la connexion à Google (ce sera l'API backend qui gérera l'OAuth)
-export const exportDb = async () => {
-
- const response = await api.post('/export/admin/export');
- return response.data
-
-};
\ No newline at end of file
diff --git a/frontend/src/services/requests/im_export.service.ts b/frontend/src/services/requests/im_export.service.ts
new file mode 100644
index 0000000..4b47f8f
--- /dev/null
+++ b/frontend/src/services/requests/im_export.service.ts
@@ -0,0 +1,27 @@
+import api from "../api";
+
+// Fonction export
+export const exportDb = async () => {
+ const response = await api.post('/imexport/admin/exportgsheet');
+ return response.data;
+};
+
+// Fonction import
+export const importFoodMenu = async (formData: FormData) => {
+ const response = await api.post('/imexport/admin/foodimport', formData, {
+ headers: { "Content-Type": "multipart/form-data" },
+ });
+ return response.data;
+};
+
+export const importPlannings = async (formData: FormData) => {
+ const response = await api.post('/imexport/admin/plannings', formData, {
+ headers: { "Content-Type": "multipart/form-data" },
+ });
+ return response.data;
+};
+
+export const exportBus = async () => {
+ const response = await api.get('/imexport/admin/exportbus');
+ return response.data;
+};
diff --git a/frontend/src/services/requests/permanence.service.ts b/frontend/src/services/requests/permanence.service.ts
index 7f9aa95..2a88bf7 100644
--- a/frontend/src/services/requests/permanence.service.ts
+++ b/frontend/src/services/requests/permanence.service.ts
@@ -32,6 +32,8 @@ export const createPermanence = async (permanenceData: {
start_at: string;
end_at: string;
capacity: number;
+ difficulty : number;
+ respoId: number | null;
}) => {
const response = await api.post("/permanence/admin/permanence", {
@@ -41,6 +43,8 @@ export const createPermanence = async (permanenceData: {
start_at: permanenceData.start_at,
end_at: permanenceData.end_at,
capacity: permanenceData.capacity,
+ difficulty : permanenceData.difficulty,
+ respoId : permanenceData.respoId
});
return response.data; // La réponse est de type Permanent
@@ -87,6 +91,8 @@ export const updatePermanence = async ( permId: number, permanenceData: {
start_at: string;
end_at: string;
capacity: number;
+ difficulty : number;
+ respoId: number | null;
}) => {
const response = await api.post("/permanence/admin/updatepermanence", {
@@ -97,6 +103,8 @@ export const updatePermanence = async ( permId: number, permanenceData: {
start_at: permanenceData.start_at,
end_at: permanenceData.end_at,
capacity: permanenceData.capacity,
+ difficulty: permanenceData.difficulty,
+ respoId : permanenceData.respoId
});
return response.data; // La réponse est de type Permanent
@@ -140,3 +148,28 @@ export const importPermanenceCSV = async(formData : FormData) => {
}
+export const isUserRespo = async(userId : number) => {
+
+ const response = await api.get(`/permanence/user/isrespo`, {params : {userId}});
+ return response.data;
+
+}
+
+export const respoDetails = async() => {
+
+ const response = await api.get(`/permanence/respo/respodetails`);
+ return response.data;
+
+}
+
+export const claimedMember = async (userId: number, permId: number, claimed : boolean) => {
+ const response = await api.post(`/permanence/respo/claimedmember`, {
+ userId,
+ permId,
+ claimed
+ });
+ return response.data;
+};
+
+
+
diff --git a/frontend/src/services/requests/tent.service.ts b/frontend/src/services/requests/tent.service.ts
new file mode 100644
index 0000000..f22345c
--- /dev/null
+++ b/frontend/src/services/requests/tent.service.ts
@@ -0,0 +1,39 @@
+import api from "../api";
+
+// Créer une tente (binôme)
+export const createTent = async (userId2: number) => {
+ const response = await api.post("/tent/user/tent", { userId2 });
+ return response.data;
+};
+
+// Annuler une tente
+export const cancelTent = async () => {
+ const response = await api.delete("/tent/user/tent", );
+ return response.data;
+};
+
+// Récupérer la tente d’un utilisateur
+export const getUserTent = async () => {
+ const response = await api.get(`/tent/user/tent`);
+ return response.data;
+};
+
+// Récupérer tous les binômes (admin)
+export const getAllTentPairs = async () => {
+ const response = await api.get("/tent/admin/tents");
+ return response.data;
+};
+
+// Valider ou dévalider une tente (admin)
+export const toggleTentConfirmation = async (
+ userId1 : number,
+ userId2: number,
+ confirmed: boolean
+) => {
+ const response = await api.post("/tent/admin/toggleconfirmation", {
+ userId1,
+ userId2,
+ confirmed,
+ });
+ return response.data;
+};
diff --git a/frontend/src/services/requests/user.service.ts b/frontend/src/services/requests/user.service.ts
index 91649ad..9f5d316 100644
--- a/frontend/src/services/requests/user.service.ts
+++ b/frontend/src/services/requests/user.service.ts
@@ -24,7 +24,7 @@ export const isAdmin = (): boolean => {
export const getUsers = async () => {
- const response = await api.get("/user/admin/getusers");
+ const response = await api.get("/user/user/getusers");
const users = response.data.data;
return users;
From 119f9e943e2add110395e6c54efddd04054f5557 Mon Sep 17 00:00:00 2001
From: Maxime Letoffet
Date: Tue, 26 Aug 2025 20:56:22 +0200
Subject: [PATCH 07/10] Fix : - Open only the perm closed and the perm with a
date superior at today
---
frontend/src/components/Admin/AdminPerm/adminPermAction.tsx | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/frontend/src/components/Admin/AdminPerm/adminPermAction.tsx b/frontend/src/components/Admin/AdminPerm/adminPermAction.tsx
index e44ec01..55593b8 100644
--- a/frontend/src/components/Admin/AdminPerm/adminPermAction.tsx
+++ b/frontend/src/components/Admin/AdminPerm/adminPermAction.tsx
@@ -32,10 +32,12 @@ const PermanenceActions: React.FC = ({ permanences, onRe
});
if (!confirm.isConfirmed) return;
+ const today = normalizeDate(new Date()).getTime();
const threshold = inSevenDays().getTime();
+
const toOpen = permanences.filter((p) => {
const permDate = normalizeDate(new Date(p.start_at)).getTime();
- return permDate <= threshold;
+ return permDate > today && permDate <= threshold && !p.is_open;
});
try {
From 7e22435c695ab627953b0b19159d46736b5c8a48 Mon Sep 17 00:00:00 2001
From: Arthur Dodin
Date: Wed, 27 Aug 2025 10:26:25 +0200
Subject: [PATCH 08/10] update: add description
---
frontend/src/App.tsx | 1 +
.../{roadbookLinks.tsx => roadbookCard.tsx} | 18 ++++++++++++++----
.../src/pages/{Roadbook.tsx => roadbook.tsx} | 4 ++--
3 files changed, 17 insertions(+), 6 deletions(-)
rename frontend/src/components/roadbook/{roadbookLinks.tsx => roadbookCard.tsx} (53%)
rename frontend/src/pages/{Roadbook.tsx => roadbook.tsx} (81%)
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index 095774f..e0d74c9 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -36,6 +36,7 @@ import PrivateRoute from './components/utils/privateroute';
import { GamesPage } from './pages/games';
import { FoodPage } from './pages/food';
import { PlanningsPage } from './pages/plannings';
+import { Roadbook } from './pages/roadbook';
const App: React.FC = () => {
diff --git a/frontend/src/components/roadbook/roadbookLinks.tsx b/frontend/src/components/roadbook/roadbookCard.tsx
similarity index 53%
rename from frontend/src/components/roadbook/roadbookLinks.tsx
rename to frontend/src/components/roadbook/roadbookCard.tsx
index e4fc046..b209d8a 100644
--- a/frontend/src/components/roadbook/roadbookLinks.tsx
+++ b/frontend/src/components/roadbook/roadbookCard.tsx
@@ -2,7 +2,7 @@ import { Button } from "../ui/button";
import { Card } from "../ui/card";
import { Link } from "react-router-dom";
-export const RoadBookLinks = () => {
+export const RoadBookCard = () => {
return (
@@ -15,18 +15,28 @@ export const RoadBookLinks = () => {
-
Lorem ipsum dolor sit amet consectetur adipisicing elit. Quae veritatis, ratione eaque exercitationem laborum nisi at, neque modi vel culpa nam corporis et alias reiciendis voluptatibus ullam. Sequi, iure vero! Lorem ipsum dolor sit amet consectetur adipisicing elit. Beatae fugit iusto illo. Laboriosam modi distinctio accusamus provident ipsum esse delectus voluptatum. Illum, ab distinctio. Ut deleniti at iste cupiditate consectetur. Lorem ipsum dolor sit amet consectetur adipisicing elit. Sapiente accusamus illum dolor expedita sint deleniti sed, iure aperiam. Eligendi ipsam commodi dicta hic, modi mollitia molestias repellat quam repellendus fugit.
+
+ C'est dans ce livret, que vous pourrez retrouver les informations les plus importantes pour naviguer au travers de l'Intégration. Vous y trouverez les contacts des super-orgas, de l'équipe prévention, de l'infirmerie ainsi que du téléphone d'astreinte , que vous pourrez appeler en cas de problème.
+
+ Un texte résumant toute la prévention et les bons gestes à adopter se trouve à l'intérieur.
+
+ Il y aura de même à votre disposition les plannings pour être toujours à l'heure, ainsi qu'une super description de chaque activité.
+
+ On vous rappelle que LES ACTIVITES NE SONT PAS OBLIGATOIRES .
+
+ Bonne Intégration à tous !
+
-
+
🔗
Accéder à la version Française
{/*
-
+
English Version
*/}
diff --git a/frontend/src/pages/Roadbook.tsx b/frontend/src/pages/roadbook.tsx
similarity index 81%
rename from frontend/src/pages/Roadbook.tsx
rename to frontend/src/pages/roadbook.tsx
index 905678f..5d1e136 100644
--- a/frontend/src/pages/Roadbook.tsx
+++ b/frontend/src/pages/roadbook.tsx
@@ -1,12 +1,12 @@
import React from "react";
-import { RoadBookLinks } from "../components/roadbook/roadbookLinks";
+import { RoadBookCard } from "../components/roadbook/roadbookCard";
export const Roadbook: React.FC = () => {
return (
From 058d8f02723f7bff3c6671c7a82e78515e3197cc Mon Sep 17 00:00:00 2001
From: Arthur Dodin
Date: Wed, 27 Aug 2025 10:44:28 +0200
Subject: [PATCH 09/10] fix: add new env variables in Dockerfile
---
frontend/Dockerfile | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/frontend/Dockerfile b/frontend/Dockerfile
index 9c29e08..6e2667d 100644
--- a/frontend/Dockerfile
+++ b/frontend/Dockerfile
@@ -10,7 +10,8 @@ ENV VITE_CAS_LOGIN_URL=${VITE_CAS_LOGIN_URL}
ENV VITE_SERVICE_URL=${VITE_SERVICE_URL}
ENV VITE_API_URL=${VITE_API_URL}
ENV VITE_ANALYTICS_WEBSITE_ID=${VITE_ANALYTICS_WEBSITE_ID}
-#localhost:4001 in local version
+ENV VITE_ROADBOOK_URL_FRENCH=${VITE_ROADBOOK_URL_FRENCH}
+ENV VITE_ROADBOOK_URL_ENGLISH=${VITE_ROADBOOK_URL_ENGLISH}
COPY package.json package-lock.json ./
RUN npm install -g npm@latest
From 6363f11d3028b75c29a8501dd8caf8c861accaa3 Mon Sep 17 00:00:00 2001
From: Arthur Dodin
Date: Wed, 27 Aug 2025 10:45:05 +0200
Subject: [PATCH 10/10] fix: add new arg in Dockerfile
---
frontend/Dockerfile | 2 ++
1 file changed, 2 insertions(+)
diff --git a/frontend/Dockerfile b/frontend/Dockerfile
index 6e2667d..f81b53e 100644
--- a/frontend/Dockerfile
+++ b/frontend/Dockerfile
@@ -5,6 +5,8 @@ ARG VITE_CAS_LOGIN_URL="https://cas.utt.fr/cas/login"
ARG VITE_SERVICE_URL="https://integration.utt.fr/"
ARG VITE_API_URL="https://integration.utt.fr/api"
ARG VITE_ANALYTICS_WEBSITE_ID=""
+ARG VITE_ROADBOOK_URL_FRENCH=""
+ARG VITE_ROADBOOK_URL_ENGLISH=""
ENV VITE_CAS_LOGIN_URL=${VITE_CAS_LOGIN_URL}
ENV VITE_SERVICE_URL=${VITE_SERVICE_URL}