From 20ef735ae19ed7a457cd8b62fd79ce6e9d09e691 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?No=C3=ABl=20Martinon?= Date: Fri, 10 Jul 2026 13:13:40 -0400 Subject: [PATCH 1/2] =?UTF-8?q?Correction=20de=20l'inscription=20multiple?= =?UTF-8?q?=20par=20email=20sur=20un=20=C3=A9v=C3=A8nement?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app.py | 12 ++++++++++++ backend/core/exceptions.py | 4 ++++ backend/core/models.py | 12 ++++++++++++ docs/changelog.md | 19 ++++++++++++------- 4 files changed, 40 insertions(+), 7 deletions(-) diff --git a/backend/app.py b/backend/app.py index 46304d39..0b001770 100644 --- a/backend/app.py +++ b/backend/app.py @@ -19,6 +19,7 @@ UserEventNbExcededAdmin, NotBookable, ParticipantNbExceded, + UserEventAlreadyRegistered, ) mail = None @@ -121,6 +122,17 @@ def handle_participant_nb_exceded_error(e): 422, ) + @app.errorhandler(UserEventAlreadyRegistered) + def handle_user_event_already_registered_error(e): + return ( + jsonify( + { + "error": "Vous ne pouvez pas vous inscrire plusieurs fois sur une animation" + } + ), + 422, + ) + @app.template_filter() def format_date(value): format_string = "EEEE d MMMM" diff --git a/backend/core/exceptions.py b/backend/core/exceptions.py index e24d3c30..abda9da6 100644 --- a/backend/core/exceptions.py +++ b/backend/core/exceptions.py @@ -20,3 +20,7 @@ class UserEventNbExcededUser(Exception): class ParticipantNbExceded(Exception): pass + + +class UserEventAlreadyRegistered(Exception): + pass diff --git a/backend/core/models.py b/backend/core/models.py index 20f7fb22..c9a9e022 100644 --- a/backend/core/models.py +++ b/backend/core/models.py @@ -14,6 +14,7 @@ UserEventNbExceded, NotBookable, ParticipantNbExceded, + UserEventAlreadyRegistered, ) @@ -184,6 +185,17 @@ def is_reservation_possible_for(self, nb_people, email, skip_nb_max_per_user=Fal if not self.bookable: raise NotBookable + # Test si utilisateur n'a pas déjà une inscription sur + # l'évènement en cours + query = select(func.count(TReservations.id_reservation)).where( + TReservations.email == email, + TReservations.id_event == self.id, + ) + nb_reservation_current_event = db.session.scalar(query) + + if nb_reservation_current_event: + raise UserEventAlreadyRegistered + # Test nombre de reservation par utilisateur # Selection animations par utilisateur from datetime import datetime diff --git a/docs/changelog.md b/docs/changelog.md index 8b44395b..aa4bb977 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -1,8 +1,13 @@ # Changelog - + +## (2026-07-10) + +**🐛 Corrections** + - Correction de l'inscription multiple par email sur un évènement (#103) + ## 0.4.0 (2026-05-21) - + **🚀 Nouveautés** - Le titre de l'application qui s'affiche dans la bare de menu correspond à la valeur de la variable `VITE_APP_TITLE` du fichier `.env` (#74) - Ajout d'un menu automatique "Évènements" pointant vers les évènements de geotrek-rando. Choix d'afficher/masquer via paramètre `DISPLAY_GTR_EVENTS_MENU` (#72) @@ -20,10 +25,10 @@ - Mise à jour des dépendances backend (#91) - Mise à jour des librairies frontend (#92). Notamment primevue 4 et tailwindcss 4. - Mise à jour des actions github (#91) - - + + ## 0.3.0 (2024-06-24) - + **🚀 Nouveautés** - Mise à jour vers SQLAlchemy 1.4 (#63)P @@ -31,9 +36,9 @@ - Utilisation de MenuBar pour la barre de menu (#65) - Style page des statistiques (#65) - Utilisation de toast pour afficher les messages d'erreur du formulaire d'inscription aux animations (#66) -- [BACK] Ajout paramètre `NB_PARTICIPANTS_MAX_PER_ANIM_PER_USER` qui permet d'indiquer le nombre maximal de participants que l'on peut enregistrer lors de la création d'une réservation +- [BACK] Ajout paramètre `NB_PARTICIPANTS_MAX_PER_ANIM_PER_USER` qui permet d'indiquer le nombre maximal de participants que l'on peut enregistrer lors de la création d'une réservation - [BACK] Ajout paramètre `NB_ANIM_MAX_PER_USER` qui permet d'indiquer le nombre maximal d'animations auxquelles un utilisateur peut s'inscrire par an. - + **🐛 Corrections** From 2dcc8b061c3090d83e5adae65cbef5a09eb9a5f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?No=C3=ABl=20Martinon?= Date: Fri, 10 Jul 2026 14:40:11 -0400 Subject: [PATCH 2/2] =?UTF-8?q?Ajout=20de=20la=20visualisation=20des=20ema?= =?UTF-8?q?ils=20envoy=C3=A9s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/core/routes.py | 51 ++++++++ docs/changelog.md | 3 + front-vite/src/App.vue | 7 + front-vite/src/router/index.ts | 16 ++- front-vite/src/utils/appli_api.ts | 6 + front-vite/src/views/LogsView.vue | 210 ++++++++++++++++++++++++++++++ 6 files changed, 291 insertions(+), 2 deletions(-) create mode 100644 front-vite/src/views/LogsView.vue diff --git a/backend/core/routes.py b/backend/core/routes.py index 53d4c79e..1788c7e3 100644 --- a/backend/core/routes.py +++ b/backend/core/routes.py @@ -1,6 +1,8 @@ from datetime import datetime from functools import wraps import secrets +import re +from pathlib import Path from email_validator import validate_email, EmailNotValidError, EmailSyntaxError from flask import jsonify, request, Blueprint, render_template, session, current_app @@ -820,3 +822,52 @@ def send_event_cancellation_emails(event_id): ) db.session.commit() return "", 200 + +@login_admin_required +@app_routes.route("/logs", methods=["GET"]) +def get_logs(): + content = [] + log_dir = Path("var/log/") + + # Traitement de tous les fichiers du répertoire log + for file in log_dir.iterdir(): + with open(file, "r") as f: + f = f.readlines() + for line in f: + if "Email envoyé " in line: + m = re.findall(r'^(\w+) (\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2},\d{3}).*core.utils (.*) \"\[.*\] (.*)\" à \[\'(.*)\'\]', line) + log_type = m[0][0] + log_date_en = m[0][1] + log_info = m[0][2] + log_subject = m[0][3] + log_email = m[0][4] + + # Ajout du statut selon le contenu du sujet + if "connexion" in log_subject: + log_status = "connexion" + elif "c'est demain !" in log_subject: + log_status = "rdv" + elif "réservation est confirmée" in log_subject: + log_status = "confirmation" + elif "Lien pour confirmer" in log_subject: + log_status = "à confirmer" + elif "liste d'attente" in log_subject: + log_status = "attente" + elif "a bien été annulée" in log_subject: + log_status = "annulation" + elif "a été annulée" in log_subject: + log_status = "info admin" + else: + log_status = "" + + # Suppression des apostrophes pour email multi-destinataires (aux admins) + log_email = log_email.replace("'", "") + + # Ajout des informations de logs + content.append({"type":log_type, + "date":log_date_en, + "info":log_info, + "subject":log_subject, + "email":log_email, + "status":log_status}) + return jsonify(content) diff --git a/docs/changelog.md b/docs/changelog.md index aa4bb977..de499712 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -3,6 +3,9 @@ ## (2026-07-10) +**🚀 Nouveautés** + - Ajout d'un menu "Journaux" pour permettre aux administrateurs de visualiser les emails envoyés (#105) + **🐛 Corrections** - Correction de l'inscription multiple par email sur un évènement (#103) diff --git a/front-vite/src/App.vue b/front-vite/src/App.vue index 63a146a8..b60bb1a7 100644 --- a/front-vite/src/App.vue +++ b/front-vite/src/App.vue @@ -92,6 +92,13 @@ const items = ref([ isAuth: true, isAdmin: false, route: '/resalisting' + }, + { + label: 'Journaux', + icon: 'pi pi-file', + isAuth: true, + isAdmin: true, + route: '/logs' } ]) diff --git a/front-vite/src/router/index.ts b/front-vite/src/router/index.ts index 0e412a42..a14e15a5 100644 --- a/front-vite/src/router/index.ts +++ b/front-vite/src/router/index.ts @@ -7,6 +7,7 @@ import LoginView from '@/views/LoginView.vue' import LoginCallbackView from '@/views/LoginCallbackView.vue' import LogoutView from '@/views/LogoutView.vue' import InfoAdminView from '@/views/InfoAdminView.vue' +import LogsView from '@/views/LogsView.vue' export const ROUTES_NAMES = { HOME: 'HOME', @@ -19,7 +20,8 @@ export const ROUTES_NAMES = { RESA_FORM: 'RESA_FORM', RESA_LISTING: 'RESA_LISTING', RESA_CONFIRM: 'RESA_CONFIRM', - INFO_ADMIN: 'INFO_ADMIN' + INFO_ADMIN: 'INFO_ADMIN', + LOGS: 'LOGS' } export const ROUTES_PATHS = { HOME: '/', @@ -32,7 +34,8 @@ export const ROUTES_PATHS = { RESA_FORM: '/resa/:geotrekid', RESA_LISTING: '/resalisting', RESA_CONFIRM: '/resaconfirm', - INFO_ADMIN: '/info_admin' + INFO_ADMIN: '/info_admin', + LOGS: '/logs' } const routes = [ @@ -127,6 +130,15 @@ const routes = [ requiresAuth: true, requiresAdmin: true } + }, + { + path: ROUTES_PATHS.LOGS, + name: ROUTES_NAMES.LOGS, + component: LogsView, + meta: { + requiresAuth: true, + requiresAdmin: true + } } ] diff --git a/front-vite/src/utils/appli_api.ts b/front-vite/src/utils/appli_api.ts index 86e59f16..3a5bf018 100644 --- a/front-vite/src/utils/appli_api.ts +++ b/front-vite/src/utils/appli_api.ts @@ -81,3 +81,9 @@ export const getGlobalStats = (data: any) => export const getGraphStats = (url: string, data: any) => getApiData(CONFIGURATION.URL_APPLICATION, url, data) + +/** + * Journaux + */ +export const getLogs = () => + getApiData(CONFIGURATION.URL_APPLICATION, 'logs') diff --git a/front-vite/src/views/LogsView.vue b/front-vite/src/views/LogsView.vue new file mode 100644 index 00000000..9ccd0b31 --- /dev/null +++ b/front-vite/src/views/LogsView.vue @@ -0,0 +1,210 @@ + + +