Refactoring
This commit is contained in:
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
## 📋 Vue d'Ensemble
|
## 📋 Vue d'Ensemble
|
||||||
|
|
||||||
Ce guide décrit le processus de déploiement de l'API AfterWork sur le VPS via `lionesctl pipeline`.
|
Ce guide décrit le processus de déploiement de l'API AfterWork sur le VPS via `lionsctl pipeline`.
|
||||||
|
|
||||||
**URL de l'API** : `https://api.lions.dev/afterwork`
|
**URL de l'API** : `https://api.lions.dev/afterwork`
|
||||||
|
|
||||||
@@ -14,7 +14,7 @@ Ce guide décrit le processus de déploiement de l'API AfterWork sur le VPS via
|
|||||||
- Java 17 (JDK)
|
- Java 17 (JDK)
|
||||||
- Maven 3.9+
|
- Maven 3.9+
|
||||||
- Docker 20.10+
|
- Docker 20.10+
|
||||||
- `lionesctl` CLI installé et configuré
|
- `lionsctl` CLI installé et configuré
|
||||||
|
|
||||||
### Environnement Serveur
|
### Environnement Serveur
|
||||||
- PostgreSQL 15+
|
- PostgreSQL 15+
|
||||||
@@ -86,31 +86,66 @@ docker push registry.lions.dev/afterwork-api:latest
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🚢 Déploiement avec lionesctl
|
## 🚢 Déploiement avec lionsctl pipeline
|
||||||
|
|
||||||
### Commande de Déploiement
|
La commande **`lionsctl pipeline`** clone le repo Git, compile (Maven), construit l'image Docker, déploie sur Kubernetes et envoie une notification email. Il n'y a pas de sous-commande `deploy` : tout est inclus dans `lionsctl pipeline`.
|
||||||
|
|
||||||
|
### Commande de déploiement
|
||||||
|
|
||||||
|
Remplacez `<org>` par votre organisation Git (ex. `lionsdev`, `developer`) et `<email>` par l'adresse de notification.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Déploiement via lionesctl pipeline
|
# Déploiement en dev (clone + build + image + déploiement K8s)
|
||||||
lionesctl pipeline deploy \
|
lionsctl pipeline \
|
||||||
--app afterwork-api \
|
-u https://git.lions.dev/<org>/mic-after-work-server-impl-quarkus-main \
|
||||||
--image registry.lions.dev/afterwork-api:1.0.0 \
|
-b develop \
|
||||||
--namespace applications \
|
-j 17 \
|
||||||
--port 8080 \
|
-e dev \
|
||||||
--replicas 2
|
-c k1 \
|
||||||
|
-m <email>
|
||||||
|
|
||||||
# Ou avec le fichier de configuration
|
# Déploiement en production sur le cluster k2
|
||||||
lionesctl pipeline deploy -f kubernetes/afterwork-deployment.yaml
|
lionsctl pipeline \
|
||||||
|
-u https://git.lions.dev/<org>/mic-after-work-server-impl-quarkus-main \
|
||||||
|
-b main \
|
||||||
|
-j 17 \
|
||||||
|
-e production \
|
||||||
|
-c k2 \
|
||||||
|
-m <email> \
|
||||||
|
-p prod
|
||||||
|
|
||||||
|
# Avec déploiement Helm (charts générés automatiquement)
|
||||||
|
lionsctl pipeline \
|
||||||
|
-u https://git.lions.dev/<org>/mic-after-work-server-impl-quarkus-main \
|
||||||
|
-b develop \
|
||||||
|
-j 17 \
|
||||||
|
-e dev \
|
||||||
|
-c k1 \
|
||||||
|
-m <email> \
|
||||||
|
--use-helm
|
||||||
```
|
```
|
||||||
|
|
||||||
### Vérification du Déploiement
|
**Options principales :**
|
||||||
|
|
||||||
|
| Option | Description | Exemple |
|
||||||
|
|--------|-------------|---------|
|
||||||
|
| `-u`, `--url` | URL du repo Git (obligatoire) | `https://git.lions.dev/.../mic-after-work-server-impl-quarkus-main` |
|
||||||
|
| `-b`, `--branch` | Branche à déployer | `develop`, `main` |
|
||||||
|
| `-j`, `--java-version` | Version Java (8–21) | `17` |
|
||||||
|
| `-e`, `--environment` | Environnement (dev / staging / production) | `dev`, `production` |
|
||||||
|
| `-c`, `--cluster` | Cluster Kubernetes (k1 ou k2) (obligatoire) | `k1`, `k2` |
|
||||||
|
| `-m`, `--mail` | Email(s) pour les notifications | `admin@lions.dev` |
|
||||||
|
| `-p`, `--profile` | Profil Maven | `prod` pour production |
|
||||||
|
| `--use-helm` | Déployer via Helm | — |
|
||||||
|
|
||||||
|
### Vérification du déploiement
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Status du déploiement
|
# Pods et statut (nom d'app dérivé du repo, ex. mic-after-work-server-impl-quarkus-main)
|
||||||
lionesctl pipeline status --app afterwork-api
|
kubectl get pods -n applications -l app=mic-after-work-server-impl-quarkus-main
|
||||||
|
|
||||||
# Logs en temps réel
|
# Logs en temps réel
|
||||||
lionesctl pipeline logs --app afterwork-api --follow
|
kubectl logs -n applications -l app=mic-after-work-server-impl-quarkus-main -f
|
||||||
|
|
||||||
# Health check
|
# Health check
|
||||||
curl https://api.lions.dev/afterwork/q/health/ready
|
curl https://api.lions.dev/afterwork/q/health/ready
|
||||||
@@ -325,8 +360,8 @@ kubectl apply -f kubernetes/afterwork-deployment.yaml
|
|||||||
kubectl apply -f kubernetes/afterwork-service.yaml
|
kubectl apply -f kubernetes/afterwork-service.yaml
|
||||||
kubectl apply -f kubernetes/afterwork-ingress.yaml
|
kubectl apply -f kubernetes/afterwork-ingress.yaml
|
||||||
|
|
||||||
# Ou via lionesctl pipeline
|
# Ou via lionsctl pipeline (clone + build + déploiement)
|
||||||
lionesctl pipeline deploy -f kubernetes/
|
lionsctl pipeline -u https://git.lions.dev/<org>/mic-after-work-server-impl-quarkus-main -b develop -j 17 -e dev -c k1 -m <email>
|
||||||
```
|
```
|
||||||
|
|
||||||
### Étape 5 : Vérification
|
### Étape 5 : Vérification
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ cd C:\Users\dadyo\PersonalProjects\mic-after-work-server-impl-quarkus-main
|
|||||||
.\deploy.ps1 -Action deploy # Déploiement K8s
|
.\deploy.ps1 -Action deploy # Déploiement K8s
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3️⃣ Déployer via lionesctl (Alternative)
|
### 3️⃣ Déployer via lionsctl pipeline (Alternative)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd C:\Users\dadyo\PersonalProjects\mic-after-work-server-impl-quarkus-main
|
cd C:\Users\dadyo\PersonalProjects\mic-after-work-server-impl-quarkus-main
|
||||||
@@ -99,7 +99,7 @@ docker build -f Dockerfile.prod -t registry.lions.dev/afterwork-api:1.0.0 .
|
|||||||
docker push registry.lions.dev/afterwork-api:1.0.0
|
docker push registry.lions.dev/afterwork-api:1.0.0
|
||||||
|
|
||||||
# Déploiement
|
# Déploiement
|
||||||
lionesctl pipeline deploy -f kubernetes/
|
lionsctl pipeline -u https://git.lions.dev/<org>/mic-after-work-server-impl-quarkus-main -b develop -j 17 -e dev -c k1 -m <email>
|
||||||
```
|
```
|
||||||
|
|
||||||
### 4️⃣ Vérifier le Déploiement
|
### 4️⃣ Vérifier le Déploiement
|
||||||
|
|||||||
@@ -48,18 +48,15 @@ kubectl get pods -n applications -l app=afterwork-api
|
|||||||
kubectl logs -n applications -l app=afterwork-api -f
|
kubectl logs -n applications -l app=afterwork-api -f
|
||||||
```
|
```
|
||||||
|
|
||||||
### Option 3 : Déploiement via lionesctl
|
### Option 3 : Déploiement via lionsctl pipeline
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd C:\Users\dadyo\PersonalProjects\mic-after-work-server-impl-quarkus-main
|
cd C:\Users\dadyo\PersonalProjects\mic-after-work-server-impl-quarkus-main
|
||||||
|
|
||||||
# Build local
|
# Le pipeline clone le repo, build Maven, construit l’image Docker et déploie sur K8s. Remplacer <org> et <email>.
|
||||||
mvn clean package -DskipTests
|
|
||||||
docker build -f docker/Dockerfile.prod -t registry.lions.dev/afterwork-api:1.0.0 .
|
|
||||||
docker push registry.lions.dev/afterwork-api:1.0.0
|
|
||||||
|
|
||||||
# Déploiement
|
# Déploiement
|
||||||
lionesctl pipeline deploy -f kubernetes/
|
lionsctl pipeline -u https://git.lions.dev/<org>/mic-after-work-server-impl-quarkus-main -b develop -j 17 -e dev -c k1 -m <email>
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
35
README.md
35
README.md
@@ -53,6 +53,36 @@ You can then execute your native executable with: `./target/mic-after-work-serve
|
|||||||
|
|
||||||
If you want to learn more about building native executables, please consult <https://quarkus.io/guides/maven-tooling>.
|
If you want to learn more about building native executables, please consult <https://quarkus.io/guides/maven-tooling>.
|
||||||
|
|
||||||
|
## Fonctionnalités métier (AfterWork)
|
||||||
|
|
||||||
|
### Notifications
|
||||||
|
|
||||||
|
- **Service** : `NotificationService` — création, lecture, pagination, marquage lu/suppression des notifications en base.
|
||||||
|
- **Déclencheurs** : Notifications créées automatiquement pour les demandes d’amitié (destinataire), les likes/commentaires sur les posts (auteur du post), les nouvelles notes d’établissement (manager).
|
||||||
|
- **API** : `GET/POST /notifications/user/{userId}`, pagination, marquer lu, supprimer. Voir [SECURITY.md](SECURITY.md) pour l’usage en production (userId issu de l’auth).
|
||||||
|
|
||||||
|
### Jobs planifiés (Quarkus Scheduler)
|
||||||
|
|
||||||
|
- **Stories** : Désactivation des stories expirées (cron : toutes les heures).
|
||||||
|
- **Tokens** : Suppression des tokens de réinitialisation de mot de passe expirés (tous les jours à 3h).
|
||||||
|
- **Abonnements** : Expiration des abonnements établissements et désactivation des établissements non payés (toutes les heures).
|
||||||
|
- **Rappels événements** : Notifications en base pour les participants (J-1 et H-1), exécution toutes les 15 minutes.
|
||||||
|
- **Avertissement abonnement** : Envoi d’emails J-3 avant expiration aux managers (tous les jours à 9h).
|
||||||
|
|
||||||
|
Configuration : `quarkus.scheduler.enabled=true` (désactivé en test via `%test.quarkus.scheduler.enabled=false`).
|
||||||
|
|
||||||
|
### Emails transactionnels
|
||||||
|
|
||||||
|
- **EmailService** : Réinitialisation mot de passe, bienvenue, confirmation de paiement Wave, rappel événement, avertissement expiration abonnement, confirmation de réservation, échec de paiement Wave.
|
||||||
|
- Configuration SMTP via variables d’environnement (`MAILER_HOST`, `MAILER_USERNAME`, `MAILER_PASSWORD`, etc.) ; en test le mailer peut être en mode mock.
|
||||||
|
|
||||||
|
### Paiement Wave (établissements)
|
||||||
|
|
||||||
|
- Initiation de paiement (abonnement mensuel/annuel), webhook `POST /webhooks/wave` pour `payment.completed`, `payment.refunded`, `payment.failed`, etc.
|
||||||
|
- Vérification optionnelle de la signature du webhook (header `X-Wave-Signature`, HMAC-SHA256) si `wave.webhook.secret` est configuré. Voir [SECURITY.md](SECURITY.md).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Related Guides
|
## Related Guides
|
||||||
|
|
||||||
- Hibernate ORM ([guide](https://quarkus.io/guides/hibernate-orm)): Define your persistent model with Hibernate ORM and Jakarta Persistence
|
- Hibernate ORM ([guide](https://quarkus.io/guides/hibernate-orm)): Define your persistent model with Hibernate ORM and Jakarta Persistence
|
||||||
@@ -61,6 +91,11 @@ If you want to learn more about building native executables, please consult <htt
|
|||||||
- Logging JSON ([guide](https://quarkus.io/guides/logging#json-logging)): Add JSON formatter for console logging
|
- Logging JSON ([guide](https://quarkus.io/guides/logging#json-logging)): Add JSON formatter for console logging
|
||||||
- JDBC Driver - PostgreSQL ([guide](https://quarkus.io/guides/datasource)): Connect to the PostgreSQL database via JDBC
|
- JDBC Driver - PostgreSQL ([guide](https://quarkus.io/guides/datasource)): Connect to the PostgreSQL database via JDBC
|
||||||
|
|
||||||
|
## Sécurité et déploiement
|
||||||
|
|
||||||
|
- **Sécurité** : Voir [SECURITY.md](SECURITY.md) (auth, webhook Wave, secrets, validation).
|
||||||
|
- **Docker** : Voir [docker/README.md](docker/README.md) pour lancer l’app et les dépendances (PostgreSQL, etc.).
|
||||||
|
|
||||||
## Provided Code
|
## Provided Code
|
||||||
|
|
||||||
### Hibernate ORM
|
### Hibernate ORM
|
||||||
|
|||||||
33
SECURITY.md
Normal file
33
SECURITY.md
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
# Sécurité – AfterWork Backend
|
||||||
|
|
||||||
|
## Authentification et autorisation
|
||||||
|
|
||||||
|
- **Super Admin** : Les opérations réservées au super administrateur (stats admin, modification de rôle utilisateur, impersonation) exigent le header `X-Super-Admin-Key` dont la valeur doit correspondre à la propriété `afterwork.super-admin.api-key` (ou `SUPER_ADMIN_API_KEY` en production). À configurer uniquement côté serveur, jamais exposée au client.
|
||||||
|
- **Utilisateurs / rôles** : À ce jour, l’API ne repose pas sur JWT/OAuth pour les endpoints métier. En production, il est recommandé d’ajouter un filtre ou une ressource qui dérive l’identité (userId) du token (JWT/session) et de **ne pas faire confiance au `userId` passé dans l’URL** (ex. `GET /notifications/user/{userId}`). L’`userId` utilisé doit être celui de l’utilisateur authentifié.
|
||||||
|
|
||||||
|
## Endpoints sensibles
|
||||||
|
|
||||||
|
- **Notifications** (`/notifications/user/{userId}`) : En l’état, tout appelant peut demander les notifications d’un autre utilisateur en changeant `userId`. En production, remplacer `userId` par l’identifiant issu du contexte d’authentification (JWT/subject).
|
||||||
|
- **Admin** : `AdminStatsResource` et les endpoints de modification de rôle dans `UsersResource` sont protégés par `X-Super-Admin-Key`.
|
||||||
|
|
||||||
|
## Webhook Wave
|
||||||
|
|
||||||
|
- **Signature** : Si la propriété `wave.webhook.secret` (ou `WAVE_WEBHOOK_SECRET`) est renseignée, le endpoint `/webhooks/wave` vérifie le header `X-Wave-Signature` (HMAC-SHA256 du body avec ce secret). Sans secret configuré, la vérification est désactivée (acceptable uniquement en dev/test).
|
||||||
|
- **Production** : Configurer systématiquement `WAVE_WEBHOOK_SECRET` avec le secret fourni par Wave pour éviter les appels forgés.
|
||||||
|
|
||||||
|
## Secrets et configuration
|
||||||
|
|
||||||
|
- **Base de données** : Utiliser les variables d’environnement (ex. `DB_USERNAME`, `DB_PASSWORD`) ou le profil Quarkus ; ne pas committer de mots de passe en clair.
|
||||||
|
- **Wave** : `WAVE_API_KEY` et `WAVE_WEBHOOK_SECRET` via variables d’environnement.
|
||||||
|
- **Email (SMTP)** : `MAILER_USERNAME`, `MAILER_PASSWORD` (et optionnellement `MAILER_FROM`, `MAILER_HOST`, etc.) via variables d’environnement.
|
||||||
|
- **Super Admin** : `SUPER_ADMIN_EMAIL`, `SUPER_ADMIN_PASSWORD`, `SUPER_ADMIN_API_KEY` pour la production.
|
||||||
|
|
||||||
|
## Validation des entrées
|
||||||
|
|
||||||
|
- Les DTOs utilisent Bean Validation (`@Valid`, `@NotNull`, `@Size`, `@Email`, `@Pattern`) sur les endpoints principaux (création utilisateur, authentification, établissements, abonnements, etc.). Conserver et étendre ces contraintes sur tout nouvel endpoint.
|
||||||
|
|
||||||
|
## Bonnes pratiques
|
||||||
|
|
||||||
|
- Répondre par des codes HTTP adaptés (401 si non autorisé, 403 si interdit, 404 si ressource absente).
|
||||||
|
- Ne pas logger de secrets (tokens, mots de passe, clés API).
|
||||||
|
- En production, utiliser HTTPS et limiter l’exposition des headers sensibles (CORS, sécurisation des headers).
|
||||||
15
pom.xml
15
pom.xml
@@ -96,6 +96,11 @@
|
|||||||
<groupId>io.quarkus</groupId>
|
<groupId>io.quarkus</groupId>
|
||||||
<artifactId>quarkus-flyway</artifactId>
|
<artifactId>quarkus-flyway</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<!-- Scheduler pour jobs planifiés (nettoyage stories, tokens, rappels événements) -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.quarkus</groupId>
|
||||||
|
<artifactId>quarkus-scheduler</artifactId>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.projectlombok</groupId>
|
<groupId>org.projectlombok</groupId>
|
||||||
<artifactId>lombok</artifactId>
|
<artifactId>lombok</artifactId>
|
||||||
@@ -107,11 +112,21 @@
|
|||||||
<artifactId>bcrypt</artifactId>
|
<artifactId>bcrypt</artifactId>
|
||||||
<version>0.10.2</version>
|
<version>0.10.2</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<!-- Email Service pour réinitialisation de mot de passe -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.quarkus</groupId>
|
||||||
|
<artifactId>quarkus-mailer</artifactId>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>io.quarkus</groupId>
|
<groupId>io.quarkus</groupId>
|
||||||
<artifactId>quarkus-junit5</artifactId>
|
<artifactId>quarkus-junit5</artifactId>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.quarkus</groupId>
|
||||||
|
<artifactId>quarkus-junit5-mockito</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>io.rest-assured</groupId>
|
<groupId>io.rest-assured</groupId>
|
||||||
<artifactId>rest-assured</artifactId>
|
<artifactId>rest-assured</artifactId>
|
||||||
|
|||||||
171
src/main/java/com/lions/dev/config/ScheduledJobs.java
Normal file
171
src/main/java/com/lions/dev/config/ScheduledJobs.java
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
package com.lions.dev.config;
|
||||||
|
|
||||||
|
import com.lions.dev.entity.events.Events;
|
||||||
|
import com.lions.dev.entity.establishment.Establishment;
|
||||||
|
import com.lions.dev.entity.establishment.EstablishmentSubscription;
|
||||||
|
import com.lions.dev.entity.users.Users;
|
||||||
|
import com.lions.dev.repository.EstablishmentRepository;
|
||||||
|
import com.lions.dev.repository.EstablishmentSubscriptionRepository;
|
||||||
|
import com.lions.dev.repository.EventsRepository;
|
||||||
|
import com.lions.dev.repository.PasswordResetTokenRepository;
|
||||||
|
import com.lions.dev.repository.StoryRepository;
|
||||||
|
import com.lions.dev.service.EmailService;
|
||||||
|
import com.lions.dev.service.NotificationService;
|
||||||
|
import io.quarkus.scheduler.Scheduled;
|
||||||
|
import jakarta.enterprise.context.ApplicationScoped;
|
||||||
|
import jakarta.inject.Inject;
|
||||||
|
import jakarta.transaction.Transactional;
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.temporal.ChronoUnit;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Jobs planifiés (Quarkus Scheduler) pour :
|
||||||
|
* - Nettoyage des stories expirées (24h)
|
||||||
|
* - Nettoyage des tokens de reset password expirés
|
||||||
|
* - Expiration des abonnements établissements
|
||||||
|
* - Désactivation des établissements non payés
|
||||||
|
* - Rappels d'événements (J-1, H-1)
|
||||||
|
*/
|
||||||
|
@ApplicationScoped
|
||||||
|
public class ScheduledJobs {
|
||||||
|
|
||||||
|
private static final Logger LOG = Logger.getLogger(ScheduledJobs.class);
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
StoryRepository storyRepository;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
PasswordResetTokenRepository passwordResetTokenRepository;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
EstablishmentSubscriptionRepository subscriptionRepository;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
EstablishmentRepository establishmentRepository;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
EventsRepository eventsRepository;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
NotificationService notificationService;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
EmailService emailService;
|
||||||
|
|
||||||
|
/** Nettoyage des stories expirées : toutes les heures. */
|
||||||
|
@Scheduled(cron = "0 0 * * * ?")
|
||||||
|
@Transactional
|
||||||
|
public void deactivateExpiredStories() {
|
||||||
|
int count = storyRepository.deactivateExpiredStories();
|
||||||
|
if (count > 0) {
|
||||||
|
LOG.info("[ScheduledJobs] Stories expirées désactivées : " + count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Nettoyage des tokens de reset password expirés : tous les jours à 3h. */
|
||||||
|
@Scheduled(cron = "0 0 3 * * ?")
|
||||||
|
@Transactional
|
||||||
|
public void deleteExpiredPasswordResetTokens() {
|
||||||
|
long count = passwordResetTokenRepository.deleteExpiredTokens();
|
||||||
|
if (count > 0) {
|
||||||
|
LOG.info("[ScheduledJobs] Tokens de reset password supprimés : " + count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Expiration des abonnements et désactivation des établissements non payés : toutes les heures. */
|
||||||
|
@Scheduled(cron = "0 5 * * * ?")
|
||||||
|
@Transactional
|
||||||
|
public void expireSubscriptionsAndDisableEstablishments() {
|
||||||
|
List<EstablishmentSubscription> expired = subscriptionRepository.findExpiredActiveSubscriptions();
|
||||||
|
for (EstablishmentSubscription sub : expired) {
|
||||||
|
sub.setStatus(EstablishmentSubscription.STATUS_EXPIRED);
|
||||||
|
subscriptionRepository.persist(sub);
|
||||||
|
Establishment est = establishmentRepository.findById(sub.getEstablishmentId());
|
||||||
|
if (est != null && Boolean.TRUE.equals(est.getIsActive())) {
|
||||||
|
est.setIsActive(false);
|
||||||
|
establishmentRepository.persist(est);
|
||||||
|
LOG.info("[ScheduledJobs] Établissement désactivé (abonnement expiré) : " + est.getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!expired.isEmpty()) {
|
||||||
|
LOG.info("[ScheduledJobs] Abonnements expirés traités : " + expired.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Rappels d'événements J-1 (dans ~24h) et H-1 (dans ~1h) : toutes les 15 minutes. */
|
||||||
|
@Scheduled(cron = "0 */15 * * * ?")
|
||||||
|
@Transactional
|
||||||
|
public void sendEventReminders() {
|
||||||
|
LocalDateTime now = LocalDateTime.now();
|
||||||
|
// Fenêtre J-1 : début entre 23h30 et 24h30
|
||||||
|
LocalDateTime j1From = now.plus(23, ChronoUnit.HOURS).plus(30, ChronoUnit.MINUTES);
|
||||||
|
LocalDateTime j1To = now.plus(24, ChronoUnit.HOURS).plus(30, ChronoUnit.MINUTES);
|
||||||
|
List<Events> eventsJ1 = eventsRepository.findEventsStartingBetween(j1From, j1To);
|
||||||
|
for (Events event : eventsJ1) {
|
||||||
|
sendReminderToParticipants(event, "J-1", "demain");
|
||||||
|
}
|
||||||
|
// Fenêtre H-1 : début entre 50 min et 1h10
|
||||||
|
LocalDateTime h1From = now.plus(50, ChronoUnit.MINUTES);
|
||||||
|
LocalDateTime h1To = now.plus(70, ChronoUnit.MINUTES);
|
||||||
|
List<Events> eventsH1 = eventsRepository.findEventsStartingBetween(h1From, h1To);
|
||||||
|
for (Events event : eventsH1) {
|
||||||
|
sendReminderToParticipants(event, "H-1", "dans 1 heure");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Avertissement expiration abonnement (J-3) : email au manager. */
|
||||||
|
@Scheduled(cron = "0 0 9 * * ?")
|
||||||
|
@Transactional
|
||||||
|
public void sendSubscriptionExpirationWarningEmails() {
|
||||||
|
LocalDateTime now = LocalDateTime.now();
|
||||||
|
LocalDateTime in3DaysStart = now.plusDays(3);
|
||||||
|
LocalDateTime in3DaysEnd = now.plusDays(3).plusHours(23).plusMinutes(59);
|
||||||
|
List<EstablishmentSubscription> expiring = subscriptionRepository.findActiveSubscriptionsExpiringBetween(in3DaysStart, in3DaysEnd);
|
||||||
|
for (EstablishmentSubscription sub : expiring) {
|
||||||
|
Establishment est = establishmentRepository.findById(sub.getEstablishmentId());
|
||||||
|
if (est == null) continue;
|
||||||
|
Users manager = est.getManager();
|
||||||
|
if (manager == null || manager.getEmail() == null) continue;
|
||||||
|
try {
|
||||||
|
emailService.sendSubscriptionExpirationWarningEmail(
|
||||||
|
manager.getEmail(),
|
||||||
|
manager.getFirstName(),
|
||||||
|
est.getName(),
|
||||||
|
sub.getExpiresAt()
|
||||||
|
);
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOG.warn("[ScheduledJobs] Email expiration abonnement échoué pour " + est.getId() + ": " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!expiring.isEmpty()) {
|
||||||
|
LOG.info("[ScheduledJobs] Emails avertissement expiration envoyés : " + expiring.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendReminderToParticipants(Events event, String reminderType, String whenText) {
|
||||||
|
Set<Users> participants = event.getParticipants();
|
||||||
|
if (participants == null) return;
|
||||||
|
Users creator = event.getCreator();
|
||||||
|
String title = "Rappel événement " + reminderType + " : " + event.getTitle();
|
||||||
|
String message = "L'événement « " + event.getTitle() + " » commence " + whenText + ".";
|
||||||
|
for (Users participant : participants) {
|
||||||
|
if (participant == null || participant.getId() == null) continue;
|
||||||
|
try {
|
||||||
|
notificationService.createNotification(title, message, "reminder", participant.getId(), event.getId());
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOG.warn("[ScheduledJobs] Impossible de créer rappel pour participant " + participant.getId() + ": " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (creator != null && creator.getId() != null && (participants.isEmpty() || !participants.stream().anyMatch(p -> p.getId().equals(creator.getId())))) {
|
||||||
|
try {
|
||||||
|
notificationService.createNotification(title, message, "reminder", creator.getId(), event.getId());
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOG.warn("[ScheduledJobs] Impossible de créer rappel pour créateur: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,6 +13,5 @@ public abstract class Exceptions extends Exception {
|
|||||||
*/
|
*/
|
||||||
public Exceptions(String message) {
|
public Exceptions(String message) {
|
||||||
super(message);
|
super(message);
|
||||||
System.out.println("[ERROR] Exception déclenchée : " + message);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ public class Failures {
|
|||||||
*/
|
*/
|
||||||
public Failures(String failureMessage) {
|
public Failures(String failureMessage) {
|
||||||
this.failureMessage = failureMessage;
|
this.failureMessage = failureMessage;
|
||||||
System.out.println("[FAILURE] Échec détecté : " + failureMessage);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ public class BadRequestException extends WebApplicationException {
|
|||||||
*/
|
*/
|
||||||
public BadRequestException(String message) {
|
public BadRequestException(String message) {
|
||||||
super(message, Response.Status.BAD_REQUEST);
|
super(message, Response.Status.BAD_REQUEST);
|
||||||
System.out.println("[ERROR] Requête invalide : " + message);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,5 @@ public class NotFoundException extends WebApplicationException {
|
|||||||
*/
|
*/
|
||||||
public NotFoundException(String message) {
|
public NotFoundException(String message) {
|
||||||
super(message, Response.Status.NOT_FOUND);
|
super(message, Response.Status.NOT_FOUND);
|
||||||
System.out.println("[ERROR] Ressource non trouvée : " + message);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,5 @@ public class ServerException extends RuntimeException {
|
|||||||
*/
|
*/
|
||||||
public ServerException(String message) {
|
public ServerException(String message) {
|
||||||
super(message);
|
super(message);
|
||||||
System.out.println("[ERROR] Erreur serveur : " + message);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,5 @@ public class UnauthorizedException extends WebApplicationException {
|
|||||||
*/
|
*/
|
||||||
public UnauthorizedException(String message) {
|
public UnauthorizedException(String message) {
|
||||||
super(message, Response.Status.UNAUTHORIZED);
|
super(message, Response.Status.UNAUTHORIZED);
|
||||||
System.out.println("[ERROR] Accès non autorisé : " + message);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package com.lions.dev.dto.request.establishment;
|
|||||||
|
|
||||||
import jakarta.validation.constraints.NotBlank;
|
import jakarta.validation.constraints.NotBlank;
|
||||||
import jakarta.validation.constraints.Pattern;
|
import jakarta.validation.constraints.Pattern;
|
||||||
|
import jakarta.validation.constraints.Size;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
@@ -21,5 +22,6 @@ public class InitiateSubscriptionRequestDTO {
|
|||||||
|
|
||||||
/** Numéro de téléphone client au format international (ex. 221771234567). */
|
/** Numéro de téléphone client au format international (ex. 221771234567). */
|
||||||
@NotBlank(message = "Le numéro de téléphone client est obligatoire pour Wave")
|
@NotBlank(message = "Le numéro de téléphone client est obligatoire pour Wave")
|
||||||
|
@Size(max = 25, message = "Le numéro de téléphone ne peut pas dépasser 25 caractères")
|
||||||
private String clientPhone;
|
private String clientPhone;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import lombok.Setter;
|
|||||||
import jakarta.validation.constraints.NotNull;
|
import jakarta.validation.constraints.NotNull;
|
||||||
import jakarta.validation.constraints.Size;
|
import jakarta.validation.constraints.Size;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DTO pour la création d'un événement.
|
* DTO pour la création d'un événement.
|
||||||
@@ -61,7 +62,6 @@ public class EventCreateRequestDTO {
|
|||||||
private String location;
|
private String location;
|
||||||
|
|
||||||
public EventCreateRequestDTO() {
|
public EventCreateRequestDTO() {
|
||||||
System.out.println("[LOG] DTO de requête de création d'événement initialisé.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package com.lions.dev.dto.request.events;
|
|||||||
|
|
||||||
import jakarta.validation.constraints.NotNull;
|
import jakarta.validation.constraints.NotNull;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DTO pour la suppression d'un événement.
|
* DTO pour la suppression d'un événement.
|
||||||
@@ -15,6 +16,5 @@ public class EventDeleteRequestDTO {
|
|||||||
private UUID eventId; // ID de l'événement à supprimer
|
private UUID eventId; // ID de l'événement à supprimer
|
||||||
|
|
||||||
public EventDeleteRequestDTO() {
|
public EventDeleteRequestDTO() {
|
||||||
System.out.println("[LOG] DTO de requête de suppression d'événement initialisé.");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package com.lions.dev.dto.request.events;
|
|||||||
|
|
||||||
import jakarta.validation.constraints.NotNull;
|
import jakarta.validation.constraints.NotNull;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DTO pour lire un événement par son ID.
|
* DTO pour lire un événement par son ID.
|
||||||
@@ -15,6 +16,5 @@ public class EventReadOneByIdRequestDTO {
|
|||||||
private UUID eventId; // ID de l'événement à lire
|
private UUID eventId; // ID de l'événement à lire
|
||||||
|
|
||||||
public EventReadOneByIdRequestDTO() {
|
public EventReadOneByIdRequestDTO() {
|
||||||
System.out.println("[LOG] DTO de requête de lecture d'événement initialisé.");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import jakarta.validation.constraints.Size;
|
|||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DTO pour la création d'un post social.
|
* DTO pour la création d'un post social.
|
||||||
@@ -29,7 +30,6 @@ public class SocialPostCreateRequestDTO {
|
|||||||
private String imageUrl; // URL de l'image (optionnel)
|
private String imageUrl; // URL de l'image (optionnel)
|
||||||
|
|
||||||
public SocialPostCreateRequestDTO() {
|
public SocialPostCreateRequestDTO() {
|
||||||
System.out.println("[LOG] DTO de requête de création de post social initialisé.");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import jakarta.validation.constraints.Size;
|
|||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DTO pour la création d'une story.
|
* DTO pour la création d'une story.
|
||||||
@@ -34,6 +35,5 @@ public class StoryCreateRequestDTO {
|
|||||||
private Integer durationSeconds; // Durée en secondes (optionnel, pour les vidéos)
|
private Integer durationSeconds; // Durée en secondes (optionnel, pour les vidéos)
|
||||||
|
|
||||||
public StoryCreateRequestDTO() {
|
public StoryCreateRequestDTO() {
|
||||||
System.out.println("[LOG] DTO de requête de création de story initialisé.");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package com.lions.dev.dto.request.users;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DTO pour forcer l'activation ou la suspension d'un utilisateur (opération réservée au super admin).
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class SetUserActiveRequestDTO {
|
||||||
|
|
||||||
|
@NotNull(message = "Le champ active est obligatoire")
|
||||||
|
private Boolean active;
|
||||||
|
|
||||||
|
public SetUserActiveRequestDTO(Boolean active) {
|
||||||
|
this.active = active;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package com.lions.dev.dto.response.establishment;
|
||||||
|
|
||||||
|
import com.lions.dev.entity.establishment.BusinessHours;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DTO pour renvoyer les horaires d'ouverture d'un établissement.
|
||||||
|
* Conforme à l'architecture AfterWork v2.0.
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
public class BusinessHoursResponseDTO {
|
||||||
|
|
||||||
|
private String id;
|
||||||
|
private String establishmentId;
|
||||||
|
private String dayOfWeek;
|
||||||
|
private String openTime;
|
||||||
|
private String closeTime;
|
||||||
|
private Boolean isClosed;
|
||||||
|
private Boolean isException;
|
||||||
|
private LocalDateTime exceptionDate;
|
||||||
|
private LocalDateTime createdAt;
|
||||||
|
private LocalDateTime updatedAt;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructeur qui transforme une entité BusinessHours en DTO.
|
||||||
|
* Utilise establishmentId fourni pour éviter LazyInitializationException.
|
||||||
|
*
|
||||||
|
* @param businessHours L'entité à convertir.
|
||||||
|
* @param establishmentId ID de l'établissement (déjà connu par l'appelant).
|
||||||
|
*/
|
||||||
|
public BusinessHoursResponseDTO(BusinessHours businessHours, UUID establishmentId) {
|
||||||
|
this.id = businessHours.getId() != null ? businessHours.getId().toString() : null;
|
||||||
|
this.establishmentId = establishmentId != null ? establishmentId.toString() : null;
|
||||||
|
this.dayOfWeek = businessHours.getDayOfWeek();
|
||||||
|
this.openTime = businessHours.getOpenTime();
|
||||||
|
this.closeTime = businessHours.getCloseTime();
|
||||||
|
this.isClosed = businessHours.getIsClosed();
|
||||||
|
this.isException = businessHours.getIsException();
|
||||||
|
this.exceptionDate = businessHours.getExceptionDate();
|
||||||
|
this.createdAt = businessHours.getCreatedAt();
|
||||||
|
this.updatedAt = businessHours.getUpdatedAt();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package com.lions.dev.dto.response.establishment;
|
||||||
|
|
||||||
|
import com.lions.dev.entity.establishment.EstablishmentAmenity;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DTO pour renvoyer un équipement d'établissement (avec nom du type).
|
||||||
|
* Conforme à l'architecture AfterWork v2.0.
|
||||||
|
* Utilise des paramètres explicites pour éviter LazyInitializationException.
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
public class EstablishmentAmenityResponseDTO {
|
||||||
|
|
||||||
|
private String establishmentId;
|
||||||
|
private String amenityId;
|
||||||
|
private String amenityName;
|
||||||
|
private String category;
|
||||||
|
private String icon;
|
||||||
|
private String details;
|
||||||
|
private LocalDateTime createdAt;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructeur qui transforme une entité EstablishmentAmenity en DTO.
|
||||||
|
* Les champs du type (name, category, icon) sont passés en paramètres car ils peuvent
|
||||||
|
* provenir d'un JOIN FETCH déjà résolu ou être null si le type n'est pas chargé.
|
||||||
|
*
|
||||||
|
* @param ea L'entité à convertir.
|
||||||
|
* @param amenityName Nom du type d'équipement (ex: "WiFi", "Parking").
|
||||||
|
* @param category Catégorie du type (ex: "Comfort", "Accessibility").
|
||||||
|
* @param icon Nom de l'icône (ex: "wifi", "parking").
|
||||||
|
*/
|
||||||
|
public EstablishmentAmenityResponseDTO(EstablishmentAmenity ea, String amenityName, String category, String icon) {
|
||||||
|
this.establishmentId = ea.getEstablishmentId() != null ? ea.getEstablishmentId().toString() : null;
|
||||||
|
this.amenityId = ea.getAmenityId() != null ? ea.getAmenityId().toString() : null;
|
||||||
|
this.amenityName = amenityName;
|
||||||
|
this.category = category;
|
||||||
|
this.icon = icon;
|
||||||
|
this.details = ea.getDetails();
|
||||||
|
this.createdAt = ea.getCreatedAt();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -27,8 +27,24 @@ public class FriendshipCreateOneResponseDTO {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructeur pour mapper l'entité `Friendship` à ce DTO.
|
* Constructeur pour mapper l'entité `Friendship` à ce DTO.
|
||||||
|
* Utilise les IDs fournis pour éviter LazyInitializationException sur user/friend.
|
||||||
*
|
*
|
||||||
* @param friendship L'entité `Friendship` à convertir en DTO.
|
* @param friendship L'entité `Friendship` à convertir en DTO.
|
||||||
|
* @param userId ID de l'utilisateur qui envoie la demande (déjà chargé).
|
||||||
|
* @param friendId ID de l'utilisateur qui reçoit la demande (déjà chargé).
|
||||||
|
*/
|
||||||
|
public FriendshipCreateOneResponseDTO(Friendship friendship, UUID userId, UUID friendId) {
|
||||||
|
this.id = friendship.getId();
|
||||||
|
this.userId = userId;
|
||||||
|
this.friendId = friendId;
|
||||||
|
this.status = friendship.getStatus();
|
||||||
|
this.createdAt = friendship.getCreatedAt();
|
||||||
|
this.updatedAt = friendship.getUpdatedAt();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructeur pour mapper l'entité `Friendship` à ce DTO (charge les associations lazy).
|
||||||
|
* Préférer {@link #FriendshipCreateOneResponseDTO(Friendship, UUID, UUID)} en fin de transaction.
|
||||||
*/
|
*/
|
||||||
public FriendshipCreateOneResponseDTO(Friendship friendship) {
|
public FriendshipCreateOneResponseDTO(Friendship friendship) {
|
||||||
this.id = friendship.getId();
|
this.id = friendship.getId();
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package com.lions.dev.dto.response.users;
|
|||||||
import com.lions.dev.entity.users.Users;
|
import com.lions.dev.entity.users.Users;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DTO pour renvoyer les informations d'un utilisateur.
|
* DTO pour renvoyer les informations d'un utilisateur.
|
||||||
@@ -68,6 +69,5 @@ public class UserCreateResponseDTO {
|
|||||||
this.nom = this.lastName;
|
this.nom = this.lastName;
|
||||||
this.prenoms = this.firstName;
|
this.prenoms = this.firstName;
|
||||||
|
|
||||||
System.out.println("[LOG] DTO créé pour l'utilisateur : " + this.email);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.lions.dev.dto;
|
package com.lions.dev.dto.response.users;
|
||||||
|
|
||||||
import com.lions.dev.entity.users.Users; // Import de l'entité Users
|
import com.lions.dev.entity.users.Users; // Import de l'entité Users
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|||||||
@@ -37,7 +37,6 @@ public abstract class BaseEntity {
|
|||||||
protected void onCreate() {
|
protected void onCreate() {
|
||||||
this.createdAt = LocalDateTime.now();
|
this.createdAt = LocalDateTime.now();
|
||||||
this.updatedAt = LocalDateTime.now();
|
this.updatedAt = LocalDateTime.now();
|
||||||
System.out.println("[LOG] Nouvelle entité créée avec ID : " + this.id + " à " + this.createdAt);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -47,6 +46,5 @@ public abstract class BaseEntity {
|
|||||||
@PreUpdate
|
@PreUpdate
|
||||||
protected void onUpdate() {
|
protected void onUpdate() {
|
||||||
this.updatedAt = LocalDateTime.now();
|
this.updatedAt = LocalDateTime.now();
|
||||||
System.out.println("[LOG] Entité mise à jour avec ID : " + this.id + " à " + this.updatedAt);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,79 @@
|
|||||||
|
package com.lions.dev.entity.auth;
|
||||||
|
|
||||||
|
import com.lions.dev.entity.users.Users;
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Entité représentant un token de réinitialisation de mot de passe.
|
||||||
|
*
|
||||||
|
* Le token est valide pendant 1 heure après sa création.
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
@Table(name = "password_reset_tokens")
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class PasswordResetToken {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||||
|
private UUID id;
|
||||||
|
|
||||||
|
@Column(name = "token", nullable = false, unique = true, length = 64)
|
||||||
|
private String token;
|
||||||
|
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY)
|
||||||
|
@JoinColumn(name = "user_id", nullable = false)
|
||||||
|
private Users user;
|
||||||
|
|
||||||
|
@Column(name = "expires_at", nullable = false)
|
||||||
|
private LocalDateTime expiresAt;
|
||||||
|
|
||||||
|
@Column(name = "used", nullable = false)
|
||||||
|
private boolean used = false;
|
||||||
|
|
||||||
|
@Column(name = "created_at", nullable = false, updatable = false)
|
||||||
|
private LocalDateTime createdAt;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Crée un nouveau token de réinitialisation pour un utilisateur.
|
||||||
|
* Le token expire après 1 heure.
|
||||||
|
*
|
||||||
|
* @param user L'utilisateur pour lequel créer le token
|
||||||
|
*/
|
||||||
|
public PasswordResetToken(Users user) {
|
||||||
|
this.user = user;
|
||||||
|
this.token = generateToken();
|
||||||
|
this.createdAt = LocalDateTime.now();
|
||||||
|
this.expiresAt = this.createdAt.plusHours(1);
|
||||||
|
this.used = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Génère un token aléatoire sécurisé.
|
||||||
|
*/
|
||||||
|
private String generateToken() {
|
||||||
|
return UUID.randomUUID().toString().replace("-", "") +
|
||||||
|
UUID.randomUUID().toString().replace("-", "").substring(0, 32);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vérifie si le token est valide (non expiré et non utilisé).
|
||||||
|
*/
|
||||||
|
public boolean isValid() {
|
||||||
|
return !used && LocalDateTime.now().isBefore(expiresAt);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marque le token comme utilisé.
|
||||||
|
*/
|
||||||
|
public void markAsUsed() {
|
||||||
|
this.used = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -62,7 +62,6 @@ public class Conversation extends BaseEntity {
|
|||||||
this.user1 = user1;
|
this.user1 = user1;
|
||||||
this.user2 = user2;
|
this.user2 = user2;
|
||||||
this.lastMessageTimestamp = LocalDateTime.now();
|
this.lastMessageTimestamp = LocalDateTime.now();
|
||||||
System.out.println("[LOG] Conversation créée entre " + user1.getEmail() + " et " + user2.getEmail());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -80,7 +79,6 @@ public class Conversation extends BaseEntity {
|
|||||||
unreadCountUser1++;
|
unreadCountUser1++;
|
||||||
}
|
}
|
||||||
|
|
||||||
System.out.println("[LOG] Dernier message mis à jour pour la conversation " + this.getId());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -89,10 +87,8 @@ public class Conversation extends BaseEntity {
|
|||||||
public void markAllAsReadForUser(Users user) {
|
public void markAllAsReadForUser(Users user) {
|
||||||
if (user != null && user1 != null && user.getId().equals(user1.getId())) {
|
if (user != null && user1 != null && user.getId().equals(user1.getId())) {
|
||||||
unreadCountUser1 = 0;
|
unreadCountUser1 = 0;
|
||||||
System.out.println("[LOG] Messages marqués comme lus pour user1 dans la conversation " + this.getId());
|
|
||||||
} else if (user != null && user2 != null && user.getId().equals(user2.getId())) {
|
} else if (user != null && user2 != null && user.getId().equals(user2.getId())) {
|
||||||
unreadCountUser2 = 0;
|
unreadCountUser2 = 0;
|
||||||
System.out.println("[LOG] Messages marqués comme lus pour user2 dans la conversation " + this.getId());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -57,7 +57,6 @@ public class Message extends BaseEntity {
|
|||||||
this.messageType = "text";
|
this.messageType = "text";
|
||||||
this.isRead = false;
|
this.isRead = false;
|
||||||
this.isDelivered = false;
|
this.isDelivered = false;
|
||||||
System.out.println("[LOG] Message créé par " + sender.getEmail() + " dans la conversation " + conversation.getId());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -66,7 +65,6 @@ public class Message extends BaseEntity {
|
|||||||
public void markAsRead() {
|
public void markAsRead() {
|
||||||
if (!this.isRead) {
|
if (!this.isRead) {
|
||||||
this.isRead = true;
|
this.isRead = true;
|
||||||
System.out.println("[LOG] Message " + this.getId() + " marqué comme lu");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,7 +74,6 @@ public class Message extends BaseEntity {
|
|||||||
public void markAsDelivered() {
|
public void markAsDelivered() {
|
||||||
if (!this.isDelivered) {
|
if (!this.isDelivered) {
|
||||||
this.isDelivered = true;
|
this.isDelivered = true;
|
||||||
System.out.println("[LOG] Message " + this.getId() + " marqué comme délivré");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -53,15 +53,7 @@ public class Comment extends BaseEntity {
|
|||||||
this.user = user;
|
this.user = user;
|
||||||
this.event = event;
|
this.event = event;
|
||||||
this.text = text;
|
this.text = text;
|
||||||
this.commentDate =
|
this.commentDate = LocalDateTime.now();
|
||||||
LocalDateTime.now(); // La date est définie automatiquement lors de la création
|
|
||||||
System.out.println(
|
|
||||||
"[LOG] Nouveau commentaire ajouté par "
|
|
||||||
+ user.getEmail()
|
|
||||||
+ " sur l'événement : "
|
|
||||||
+ event.getTitle()
|
|
||||||
+ " - Texte : "
|
|
||||||
+ text);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -73,14 +65,7 @@ public class Comment extends BaseEntity {
|
|||||||
* @param newText Le nouveau texte du commentaire.
|
* @param newText Le nouveau texte du commentaire.
|
||||||
*/
|
*/
|
||||||
public void updateComment(String newText) {
|
public void updateComment(String newText) {
|
||||||
System.out.println(
|
|
||||||
"[LOG] Modification du commentaire de "
|
|
||||||
+ user.getEmail()
|
|
||||||
+ " sur l'événement : "
|
|
||||||
+ event.getTitle()
|
|
||||||
+ " - Nouveau texte : "
|
|
||||||
+ newText);
|
|
||||||
this.text = newText;
|
this.text = newText;
|
||||||
this.commentDate = LocalDateTime.now(); // Mise à jour de la date de modification
|
this.commentDate = LocalDateTime.now();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package com.lions.dev.entity.establishment;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Entité représentant un type d'équipement (référence amenity_types).
|
||||||
|
* Utilisée pour afficher le nom/catégorie des équipements d'un établissement.
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
@Table(name = "amenity_types")
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class AmenityType {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@Column(name = "id", nullable = false)
|
||||||
|
private UUID id;
|
||||||
|
|
||||||
|
@Column(name = "name", nullable = false, unique = true, length = 100)
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@Column(name = "category", length = 50)
|
||||||
|
private String category;
|
||||||
|
|
||||||
|
@Column(name = "icon", length = 50)
|
||||||
|
private String icon;
|
||||||
|
|
||||||
|
@Column(name = "created_at", nullable = false, updatable = false)
|
||||||
|
private LocalDateTime createdAt = LocalDateTime.now();
|
||||||
|
}
|
||||||
@@ -45,6 +45,10 @@ public class EstablishmentAmenity {
|
|||||||
@JoinColumn(name = "establishment_id", insertable = false, updatable = false)
|
@JoinColumn(name = "establishment_id", insertable = false, updatable = false)
|
||||||
private Establishment establishment; // L'établissement concerné
|
private Establishment establishment; // L'établissement concerné
|
||||||
|
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY)
|
||||||
|
@JoinColumn(name = "amenity_id", insertable = false, updatable = false)
|
||||||
|
private AmenityType amenityType; // Type d'équipement (nom, catégorie, icône)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructeur pour créer une liaison établissement-équipement.
|
* Constructeur pour créer une liaison établissement-équipement.
|
||||||
*
|
*
|
||||||
@@ -67,32 +71,3 @@ public class EstablishmentAmenity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Classe composite pour la clé primaire de EstablishmentAmenity.
|
|
||||||
*/
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
@NoArgsConstructor
|
|
||||||
class EstablishmentAmenityId implements java.io.Serializable {
|
|
||||||
private UUID establishmentId;
|
|
||||||
private UUID amenityId;
|
|
||||||
|
|
||||||
public EstablishmentAmenityId(UUID establishmentId, UUID amenityId) {
|
|
||||||
this.establishmentId = establishmentId;
|
|
||||||
this.amenityId = amenityId;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) return true;
|
|
||||||
if (o == null || getClass() != o.getClass()) return false;
|
|
||||||
EstablishmentAmenityId that = (EstablishmentAmenityId) o;
|
|
||||||
return establishmentId.equals(that.establishmentId) && amenityId.equals(that.amenityId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return establishmentId.hashCode() + amenityId.hashCode();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package com.lions.dev.entity.establishment;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Classe composite pour la clé primaire de EstablishmentAmenity.
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class EstablishmentAmenityId implements Serializable {
|
||||||
|
|
||||||
|
private UUID establishmentId;
|
||||||
|
private UUID amenityId;
|
||||||
|
|
||||||
|
public EstablishmentAmenityId(UUID establishmentId, UUID amenityId) {
|
||||||
|
this.establishmentId = establishmentId;
|
||||||
|
this.amenityId = amenityId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
EstablishmentAmenityId that = (EstablishmentAmenityId) o;
|
||||||
|
return establishmentId.equals(that.establishmentId) && amenityId.equals(that.amenityId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return establishmentId.hashCode() + amenityId.hashCode();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -117,7 +117,6 @@ public class Events extends BaseEntity {
|
|||||||
*/
|
*/
|
||||||
public void addParticipant(Users user) {
|
public void addParticipant(Users user) {
|
||||||
participants.add(user);
|
participants.add(user);
|
||||||
System.out.println("[LOG] Participant ajouté : " + user.getEmail() + " à l'événement : " + this.title);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -127,7 +126,6 @@ public class Events extends BaseEntity {
|
|||||||
*/
|
*/
|
||||||
public void removeParticipant(Users user) {
|
public void removeParticipant(Users user) {
|
||||||
participants.remove(user);
|
participants.remove(user);
|
||||||
System.out.println("[LOG] Participant supprimé : " + user.getEmail() + " de l'événement : " + this.title);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -137,7 +135,6 @@ public class Events extends BaseEntity {
|
|||||||
*/
|
*/
|
||||||
public int getNumberOfParticipants() {
|
public int getNumberOfParticipants() {
|
||||||
int count = participants.size();
|
int count = participants.size();
|
||||||
System.out.println("[LOG] Nombre de participants à l'événement : " + this.title + " - " + count);
|
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,7 +143,6 @@ public class Events extends BaseEntity {
|
|||||||
*/
|
*/
|
||||||
public void setClosed(boolean closed) {
|
public void setClosed(boolean closed) {
|
||||||
this.status = closed ? "CLOSED" : "OPEN";
|
this.status = closed ? "CLOSED" : "OPEN";
|
||||||
System.out.println("[LOG] Statut de l'événement mis à jour : " + this.title + " - " + this.status);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -189,7 +185,6 @@ public class Events extends BaseEntity {
|
|||||||
* @return Une liste de commentaires.
|
* @return Une liste de commentaires.
|
||||||
*/
|
*/
|
||||||
public List<Comment> getComments() {
|
public List<Comment> getComments() {
|
||||||
System.out.println("[LOG] Récupération des commentaires pour l'événement : " + this.title);
|
|
||||||
return comments;
|
return comments;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -38,12 +38,5 @@ public class Friendship extends BaseEntity {
|
|||||||
*/
|
*/
|
||||||
public void setStatus(FriendshipStatus newStatus) {
|
public void setStatus(FriendshipStatus newStatus) {
|
||||||
this.status = newStatus;
|
this.status = newStatus;
|
||||||
System.out.println(
|
|
||||||
"[LOG] Statut changé pour l'amitié entre "
|
|
||||||
+ this.user.getEmail()
|
|
||||||
+ " et "
|
|
||||||
+ this.friend.getEmail()
|
|
||||||
+ " - Nouveau statut : "
|
|
||||||
+ this.status);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,7 +60,6 @@ public class Notification extends BaseEntity {
|
|||||||
this.type = type;
|
this.type = type;
|
||||||
this.user = user;
|
this.user = user;
|
||||||
this.isRead = false;
|
this.isRead = false;
|
||||||
System.out.println("[LOG] Notification créée : " + title + " pour l'utilisateur " + user.getEmail());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -69,7 +68,6 @@ public class Notification extends BaseEntity {
|
|||||||
public void markAsRead() {
|
public void markAsRead() {
|
||||||
if (!this.isRead) {
|
if (!this.isRead) {
|
||||||
this.isRead = true;
|
this.isRead = true;
|
||||||
System.out.println("[LOG] Notification marquée comme lue : " + this.title);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -52,7 +52,6 @@ public class SocialPost extends BaseEntity {
|
|||||||
this.likesCount = 0;
|
this.likesCount = 0;
|
||||||
this.commentsCount = 0;
|
this.commentsCount = 0;
|
||||||
this.sharesCount = 0;
|
this.sharesCount = 0;
|
||||||
System.out.println("[LOG] Post social créé par l'utilisateur : " + user.getEmail());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -60,7 +59,6 @@ public class SocialPost extends BaseEntity {
|
|||||||
*/
|
*/
|
||||||
public void incrementLikes() {
|
public void incrementLikes() {
|
||||||
this.likesCount++;
|
this.likesCount++;
|
||||||
System.out.println("[LOG] Like ajouté au post ID : " + this.getId() + " (total: " + this.likesCount + ")");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -69,7 +67,6 @@ public class SocialPost extends BaseEntity {
|
|||||||
public void decrementLikes() {
|
public void decrementLikes() {
|
||||||
if (this.likesCount > 0) {
|
if (this.likesCount > 0) {
|
||||||
this.likesCount--;
|
this.likesCount--;
|
||||||
System.out.println("[LOG] Like retiré du post ID : " + this.getId() + " (total: " + this.likesCount + ")");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,7 +75,6 @@ public class SocialPost extends BaseEntity {
|
|||||||
*/
|
*/
|
||||||
public void incrementComments() {
|
public void incrementComments() {
|
||||||
this.commentsCount++;
|
this.commentsCount++;
|
||||||
System.out.println("[LOG] Commentaire ajouté au post ID : " + this.getId() + " (total: " + this.commentsCount + ")");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -86,7 +82,6 @@ public class SocialPost extends BaseEntity {
|
|||||||
*/
|
*/
|
||||||
public void incrementShares() {
|
public void incrementShares() {
|
||||||
this.sharesCount++;
|
this.sharesCount++;
|
||||||
System.out.println("[LOG] Partage ajouté au post ID : " + this.getId() + " (total: " + this.sharesCount + ")");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -70,7 +70,6 @@ public class Story extends BaseEntity {
|
|||||||
this.expiresAt = LocalDateTime.now().plusHours(24); // Expire après 24h
|
this.expiresAt = LocalDateTime.now().plusHours(24); // Expire après 24h
|
||||||
this.isActive = true;
|
this.isActive = true;
|
||||||
this.viewsCount = 0;
|
this.viewsCount = 0;
|
||||||
System.out.println("[LOG] Story créée par l'utilisateur : " + user.getEmail());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -82,7 +81,6 @@ public class Story extends BaseEntity {
|
|||||||
public boolean markAsViewed(UUID viewerId) {
|
public boolean markAsViewed(UUID viewerId) {
|
||||||
if (viewerIds.add(viewerId)) {
|
if (viewerIds.add(viewerId)) {
|
||||||
this.viewsCount++;
|
this.viewsCount++;
|
||||||
System.out.println("[LOG] Story ID : " + this.getId() + " vue par l'utilisateur ID : " + viewerId + " (total: " + this.viewsCount + ")");
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@@ -102,7 +100,6 @@ public class Story extends BaseEntity {
|
|||||||
*/
|
*/
|
||||||
public void deactivate() {
|
public void deactivate() {
|
||||||
this.isActive = false;
|
this.isActive = false;
|
||||||
System.out.println("[LOG] Story ID : " + this.getId() + " désactivée");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -84,7 +84,6 @@ public class Users extends BaseEntity {
|
|||||||
*/
|
*/
|
||||||
public void setPassword(String password) {
|
public void setPassword(String password) {
|
||||||
this.passwordHash = BCrypt.withDefaults().hashToString(12, password.toCharArray());
|
this.passwordHash = BCrypt.withDefaults().hashToString(12, password.toCharArray());
|
||||||
System.out.println("[LOG] Mot de passe haché pour l'utilisateur : " + this.email);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -105,9 +104,7 @@ public class Users extends BaseEntity {
|
|||||||
*/
|
*/
|
||||||
public boolean verifyPassword(String password) {
|
public boolean verifyPassword(String password) {
|
||||||
BCrypt.Result result = BCrypt.verifyer().verify(password.toCharArray(), this.passwordHash);
|
BCrypt.Result result = BCrypt.verifyer().verify(password.toCharArray(), this.passwordHash);
|
||||||
boolean isValid = result.verified;
|
return result.verified;
|
||||||
System.out.println("[LOG] Vérification du mot de passe pour l'utilisateur : " + this.email + " - Résultat : " + isValid);
|
|
||||||
return isValid;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -134,9 +131,7 @@ public class Users extends BaseEntity {
|
|||||||
* @return true si l'utilisateur est un administrateur, false sinon.
|
* @return true si l'utilisateur est un administrateur, false sinon.
|
||||||
*/
|
*/
|
||||||
public boolean isAdmin() {
|
public boolean isAdmin() {
|
||||||
boolean isAdmin = "ADMIN".equalsIgnoreCase(this.role);
|
return "ADMIN".equalsIgnoreCase(this.role);
|
||||||
System.out.println("[LOG] Vérification du rôle ADMIN pour l'utilisateur : " + this.email + " - Résultat : " + isAdmin);
|
|
||||||
return isAdmin;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ManyToMany(fetch = FetchType.LAZY)
|
@ManyToMany(fetch = FetchType.LAZY)
|
||||||
@@ -154,7 +149,6 @@ public class Users extends BaseEntity {
|
|||||||
*/
|
*/
|
||||||
public void addFavoriteEvent(Events event) {
|
public void addFavoriteEvent(Events event) {
|
||||||
favoriteEvents.add(event);
|
favoriteEvents.add(event);
|
||||||
System.out.println("[LOG] Événement ajouté aux favoris pour l'utilisateur : " + this.email);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -164,7 +158,6 @@ public class Users extends BaseEntity {
|
|||||||
*/
|
*/
|
||||||
public void removeFavoriteEvent(Events event) {
|
public void removeFavoriteEvent(Events event) {
|
||||||
favoriteEvents.remove(event);
|
favoriteEvents.remove(event);
|
||||||
System.out.println("[LOG] Événement retiré des favoris pour l'utilisateur : " + this.email);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -183,7 +176,6 @@ public class Users extends BaseEntity {
|
|||||||
* @return Une liste d'événements favoris.
|
* @return Une liste d'événements favoris.
|
||||||
*/
|
*/
|
||||||
public Set<Events> getFavoriteEvents() {
|
public Set<Events> getFavoriteEvents() {
|
||||||
System.out.println("[LOG] Récupération des événements favoris pour l'utilisateur : " + this.email);
|
|
||||||
return favoriteEvents;
|
return favoriteEvents;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -214,7 +206,6 @@ public class Users extends BaseEntity {
|
|||||||
} else {
|
} else {
|
||||||
preferences.remove("preferred_category");
|
preferences.remove("preferred_category");
|
||||||
}
|
}
|
||||||
System.out.println("[LOG] Catégorie préférée définie pour l'utilisateur : " + this.email + " - Catégorie : " + category);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -232,7 +223,6 @@ public class Users extends BaseEntity {
|
|||||||
public void updatePresence() {
|
public void updatePresence() {
|
||||||
this.isOnline = true;
|
this.isOnline = true;
|
||||||
this.lastSeen = java.time.LocalDateTime.now();
|
this.lastSeen = java.time.LocalDateTime.now();
|
||||||
System.out.println("[LOG] Présence mise à jour pour l'utilisateur : " + this.email + " - Online: true");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -241,7 +231,6 @@ public class Users extends BaseEntity {
|
|||||||
public void setOffline() {
|
public void setOffline() {
|
||||||
this.isOnline = false;
|
this.isOnline = false;
|
||||||
this.lastSeen = java.time.LocalDateTime.now();
|
this.lastSeen = java.time.LocalDateTime.now();
|
||||||
System.out.println("[LOG] Utilisateur marqué hors ligne : " + this.email);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,5 @@ public class EventNotFoundException extends WebApplicationException {
|
|||||||
*/
|
*/
|
||||||
public EventNotFoundException(UUID eventId) {
|
public EventNotFoundException(UUID eventId) {
|
||||||
super("Événement non trouvé avec l'ID : " + eventId.toString(), Response.Status.NOT_FOUND);
|
super("Événement non trouvé avec l'ID : " + eventId.toString(), Response.Status.NOT_FOUND);
|
||||||
System.out.println("[ERROR] Événement non trouvé avec l'ID : " + eventId.toString());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,5 @@ public class UserNotFoundException extends WebApplicationException {
|
|||||||
*/
|
*/
|
||||||
public UserNotFoundException(String message) {
|
public UserNotFoundException(String message) {
|
||||||
super(message, Response.Status.NOT_FOUND);
|
super(message, Response.Status.NOT_FOUND);
|
||||||
System.out.println("[ERROR] Utilisateur non trouvé : " + message);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package com.lions.dev.repository;
|
||||||
|
|
||||||
|
import com.lions.dev.entity.establishment.BusinessHours;
|
||||||
|
import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase;
|
||||||
|
import jakarta.enterprise.context.ApplicationScoped;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Repository pour l'entité BusinessHours.
|
||||||
|
* Gère les opérations de lecture sur les horaires d'ouverture des établissements.
|
||||||
|
*/
|
||||||
|
@ApplicationScoped
|
||||||
|
public class BusinessHoursRepository implements PanacheRepositoryBase<BusinessHours, UUID> {
|
||||||
|
|
||||||
|
private static final Logger LOG = Logger.getLogger(BusinessHoursRepository.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Récupère tous les horaires d'ouverture d'un établissement.
|
||||||
|
*
|
||||||
|
* @param establishmentId L'ID de l'établissement.
|
||||||
|
* @return La liste des horaires (triés par jour de la semaine).
|
||||||
|
*/
|
||||||
|
public List<BusinessHours> findByEstablishmentId(UUID establishmentId) {
|
||||||
|
LOG.infof("[LOG] Récupération des horaires pour l'établissement : %s", establishmentId);
|
||||||
|
List<BusinessHours> list = list("establishment.id = ?1 ORDER BY dayOfWeek", establishmentId);
|
||||||
|
LOG.infof("[LOG] Nombre d'horaires trouvés pour l'établissement %s : %d", establishmentId, list.size());
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ import com.lions.dev.entity.users.Users;
|
|||||||
import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase;
|
import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase;
|
||||||
import io.quarkus.panache.common.Sort;
|
import io.quarkus.panache.common.Sort;
|
||||||
import jakarta.enterprise.context.ApplicationScoped;
|
import jakarta.enterprise.context.ApplicationScoped;
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
@@ -27,7 +28,6 @@ public class ConversationRepository implements PanacheRepositoryBase<Conversatio
|
|||||||
* @return La conversation si elle existe, null sinon
|
* @return La conversation si elle existe, null sinon
|
||||||
*/
|
*/
|
||||||
public Conversation findBetweenUsers(Users user1, Users user2) {
|
public Conversation findBetweenUsers(Users user1, Users user2) {
|
||||||
System.out.println("[LOG] Recherche de conversation entre " + user1.getEmail() + " et " + user2.getEmail());
|
|
||||||
|
|
||||||
// Une conversation peut avoir user1 et user2 dans n'importe quel ordre
|
// Une conversation peut avoir user1 et user2 dans n'importe quel ordre
|
||||||
return find("(user1 = ?1 and user2 = ?2) or (user1 = ?2 and user2 = ?1)", user1, user2)
|
return find("(user1 = ?1 and user2 = ?2) or (user1 = ?2 and user2 = ?1)", user1, user2)
|
||||||
@@ -47,9 +47,7 @@ public class ConversationRepository implements PanacheRepositoryBase<Conversatio
|
|||||||
if (conversation == null) {
|
if (conversation == null) {
|
||||||
conversation = new Conversation(user1, user2);
|
conversation = new Conversation(user1, user2);
|
||||||
persist(conversation);
|
persist(conversation);
|
||||||
System.out.println("[LOG] Nouvelle conversation créée avec l'ID : " + conversation.getId());
|
|
||||||
} else {
|
} else {
|
||||||
System.out.println("[LOG] Conversation existante trouvée avec l'ID : " + conversation.getId());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return conversation;
|
return conversation;
|
||||||
@@ -62,7 +60,6 @@ public class ConversationRepository implements PanacheRepositoryBase<Conversatio
|
|||||||
* @return Liste des conversations triées par date du dernier message
|
* @return Liste des conversations triées par date du dernier message
|
||||||
*/
|
*/
|
||||||
public List<Conversation> findByUser(Users user) {
|
public List<Conversation> findByUser(Users user) {
|
||||||
System.out.println("[LOG] Récupération des conversations pour l'utilisateur : " + user.getEmail());
|
|
||||||
return find("user1 = ?1 or user2 = ?1", Sort.by("lastMessageTimestamp", Sort.Direction.Descending), user)
|
return find("user1 = ?1 or user2 = ?1", Sort.by("lastMessageTimestamp", Sort.Direction.Descending), user)
|
||||||
.list();
|
.list();
|
||||||
}
|
}
|
||||||
@@ -74,7 +71,6 @@ public class ConversationRepository implements PanacheRepositoryBase<Conversatio
|
|||||||
* @return Liste des conversations avec des messages non lus
|
* @return Liste des conversations avec des messages non lus
|
||||||
*/
|
*/
|
||||||
public List<Conversation> findConversationsWithUnreadMessages(UUID userId) {
|
public List<Conversation> findConversationsWithUnreadMessages(UUID userId) {
|
||||||
System.out.println("[LOG] Récupération des conversations avec messages non lus pour l'utilisateur ID : " + userId);
|
|
||||||
|
|
||||||
return find(
|
return find(
|
||||||
"(user1.id = ?1 and unreadCountUser1 > 0) or (user2.id = ?1 and unreadCountUser2 > 0)",
|
"(user1.id = ?1 and unreadCountUser1 > 0) or (user2.id = ?1 and unreadCountUser2 > 0)",
|
||||||
@@ -101,7 +97,6 @@ public class ConversationRepository implements PanacheRepositoryBase<Conversatio
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
System.out.println("[LOG] Total de messages non lus pour l'utilisateur " + userId + " : " + total);
|
|
||||||
return total;
|
return total;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,7 +107,6 @@ public class ConversationRepository implements PanacheRepositoryBase<Conversatio
|
|||||||
* @return true si la conversation a été supprimée, false sinon
|
* @return true si la conversation a été supprimée, false sinon
|
||||||
*/
|
*/
|
||||||
public boolean deleteConversation(UUID conversationId) {
|
public boolean deleteConversation(UUID conversationId) {
|
||||||
System.out.println("[LOG] Suppression de la conversation ID : " + conversationId);
|
|
||||||
return deleteById(conversationId);
|
return deleteById(conversationId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package com.lions.dev.repository;
|
||||||
|
|
||||||
|
import com.lions.dev.entity.establishment.EstablishmentAmenity;
|
||||||
|
import com.lions.dev.entity.establishment.EstablishmentAmenityId;
|
||||||
|
import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase;
|
||||||
|
import jakarta.enterprise.context.ApplicationScoped;
|
||||||
|
import jakarta.persistence.EntityManager;
|
||||||
|
import jakarta.persistence.PersistenceContext;
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Repository pour l'entité EstablishmentAmenity.
|
||||||
|
* Gère les opérations de lecture sur les équipements des établissements.
|
||||||
|
*/
|
||||||
|
@ApplicationScoped
|
||||||
|
public class EstablishmentAmenityRepository implements PanacheRepositoryBase<EstablishmentAmenity, EstablishmentAmenityId> {
|
||||||
|
|
||||||
|
@PersistenceContext
|
||||||
|
EntityManager entityManager;
|
||||||
|
|
||||||
|
private static final Logger LOG = Logger.getLogger(EstablishmentAmenityRepository.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Récupère tous les équipements d'un établissement avec le type chargé (nom, catégorie, icône).
|
||||||
|
* Utilise JOIN FETCH pour éviter LazyInitializationException.
|
||||||
|
*
|
||||||
|
* @param establishmentId L'ID de l'établissement.
|
||||||
|
* @return La liste des équipements (avec amenityType chargé).
|
||||||
|
*/
|
||||||
|
public List<EstablishmentAmenity> findByEstablishmentIdWithType(UUID establishmentId) {
|
||||||
|
LOG.infof("[LOG] Récupération des équipements pour l'établissement : %s", establishmentId);
|
||||||
|
List<EstablishmentAmenity> list = entityManager
|
||||||
|
.createQuery(
|
||||||
|
"SELECT ea FROM EstablishmentAmenity ea LEFT JOIN FETCH ea.amenityType WHERE ea.establishmentId = :eid ORDER BY ea.amenityId",
|
||||||
|
EstablishmentAmenity.class)
|
||||||
|
.setParameter("eid", establishmentId)
|
||||||
|
.getResultList();
|
||||||
|
LOG.infof("[LOG] Nombre d'équipements trouvés pour l'établissement %s : %d", establishmentId, list.size());
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import com.lions.dev.entity.establishment.EstablishmentSubscription;
|
|||||||
import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase;
|
import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase;
|
||||||
import jakarta.enterprise.context.ApplicationScoped;
|
import jakarta.enterprise.context.ApplicationScoped;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
@@ -23,4 +24,16 @@ public class EstablishmentSubscriptionRepository implements PanacheRepositoryBas
|
|||||||
return find("establishmentId = ?1 and status = ?2", establishmentId, EstablishmentSubscription.STATUS_ACTIVE)
|
return find("establishmentId = ?1 and status = ?2", establishmentId, EstablishmentSubscription.STATUS_ACTIVE)
|
||||||
.firstResultOptional();
|
.firstResultOptional();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Abonnements actifs dont la date d'expiration est dépassée (pour job de nettoyage). */
|
||||||
|
public List<EstablishmentSubscription> findExpiredActiveSubscriptions() {
|
||||||
|
return list("status = ?1 AND expiresAt IS NOT NULL AND expiresAt <= ?2",
|
||||||
|
EstablishmentSubscription.STATUS_ACTIVE, LocalDateTime.now());
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Abonnements actifs dont l'expiration est dans la fenêtre [from, to] (pour avertissement J-3). */
|
||||||
|
public List<EstablishmentSubscription> findActiveSubscriptionsExpiringBetween(LocalDateTime from, LocalDateTime to) {
|
||||||
|
return list("status = ?1 AND expiresAt IS NOT NULL AND expiresAt >= ?2 AND expiresAt <= ?3",
|
||||||
|
EstablishmentSubscription.STATUS_ACTIVE, from, to);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -100,4 +100,12 @@ public class EventsRepository implements PanacheRepositoryBase<Events, UUID> {
|
|||||||
.setParameter("loc", pattern)
|
.setParameter("loc", pattern)
|
||||||
.getResultList();
|
.getResultList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Récupère les événements dont la date de début est dans la fenêtre [from, to].
|
||||||
|
* Utilisé pour les rappels (J-1, H-1).
|
||||||
|
*/
|
||||||
|
public List<Events> findEventsStartingBetween(LocalDateTime from, LocalDateTime to) {
|
||||||
|
return list("startDate >= ?1 AND startDate <= ?2", from, to);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -136,4 +136,16 @@ public class FriendshipRepository implements PanacheRepositoryBase<Friendship, U
|
|||||||
logger.infof("Nombre de demandes reçues récupérées pour l'utilisateur %s : %d", user.getId(), friendships.size());
|
logger.infof("Nombre de demandes reçues récupérées pour l'utilisateur %s : %d", user.getId(), friendships.size());
|
||||||
return friendships;
|
return friendships;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Récupérer toutes les amitiés acceptées d'un utilisateur (par UUID).
|
||||||
|
* Méthode simplifiée pour les notifications temps réel.
|
||||||
|
*
|
||||||
|
* @param userId L'UUID de l'utilisateur
|
||||||
|
* @return Liste de toutes les amitiés acceptées
|
||||||
|
*/
|
||||||
|
public List<Friendship> findAcceptedFriendships(UUID userId) {
|
||||||
|
return find("(user.id = ?1 OR friend.id = ?1) AND status = ?2", userId, FriendshipStatus.ACCEPTED)
|
||||||
|
.list();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase;
|
|||||||
import io.quarkus.panache.common.Page;
|
import io.quarkus.panache.common.Page;
|
||||||
import io.quarkus.panache.common.Sort;
|
import io.quarkus.panache.common.Sort;
|
||||||
import jakarta.enterprise.context.ApplicationScoped;
|
import jakarta.enterprise.context.ApplicationScoped;
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
@@ -28,7 +29,6 @@ public class MessageRepository implements PanacheRepositoryBase<Message, UUID> {
|
|||||||
* @return Liste paginée des messages triés par date (du plus récent au plus ancien)
|
* @return Liste paginée des messages triés par date (du plus récent au plus ancien)
|
||||||
*/
|
*/
|
||||||
public List<Message> findByConversationId(UUID conversationId, int page, int size) {
|
public List<Message> findByConversationId(UUID conversationId, int page, int size) {
|
||||||
System.out.println("[LOG] Récupération des messages pour la conversation ID : " + conversationId);
|
|
||||||
return find("conversation.id = ?1", Sort.by("createdAt", Sort.Direction.Descending), conversationId)
|
return find("conversation.id = ?1", Sort.by("createdAt", Sort.Direction.Descending), conversationId)
|
||||||
.page(Page.of(page, size))
|
.page(Page.of(page, size))
|
||||||
.list();
|
.list();
|
||||||
@@ -41,7 +41,6 @@ public class MessageRepository implements PanacheRepositoryBase<Message, UUID> {
|
|||||||
* @return Liste de tous les messages triés par date
|
* @return Liste de tous les messages triés par date
|
||||||
*/
|
*/
|
||||||
public List<Message> findByConversation(Conversation conversation) {
|
public List<Message> findByConversation(Conversation conversation) {
|
||||||
System.out.println("[LOG] Récupération de tous les messages pour la conversation ID : " + conversation.getId());
|
|
||||||
return find("conversation = ?1", Sort.by("createdAt", Sort.Direction.Ascending), conversation).list();
|
return find("conversation = ?1", Sort.by("createdAt", Sort.Direction.Ascending), conversation).list();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,7 +63,6 @@ public class MessageRepository implements PanacheRepositoryBase<Message, UUID> {
|
|||||||
* @return Le nombre de messages mis à jour
|
* @return Le nombre de messages mis à jour
|
||||||
*/
|
*/
|
||||||
public int markAllAsRead(UUID conversationId, UUID recipientId) {
|
public int markAllAsRead(UUID conversationId, UUID recipientId) {
|
||||||
System.out.println("[LOG] Marquage de tous les messages comme lus pour la conversation " + conversationId);
|
|
||||||
return update("isRead = true where conversation.id = ?1 and sender.id != ?2 and isRead = false", conversationId, recipientId);
|
return update("isRead = true where conversation.id = ?1 and sender.id != ?2 and isRead = false", conversationId, recipientId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,7 +84,6 @@ public class MessageRepository implements PanacheRepositoryBase<Message, UUID> {
|
|||||||
* @return Le nombre de messages supprimés
|
* @return Le nombre de messages supprimés
|
||||||
*/
|
*/
|
||||||
public long deleteByConversationId(UUID conversationId) {
|
public long deleteByConversationId(UUID conversationId) {
|
||||||
System.out.println("[LOG] Suppression de tous les messages pour la conversation " + conversationId);
|
|
||||||
return delete("conversation.id = ?1", conversationId);
|
return delete("conversation.id = ?1", conversationId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import io.quarkus.panache.common.Sort;
|
|||||||
import jakarta.enterprise.context.ApplicationScoped;
|
import jakarta.enterprise.context.ApplicationScoped;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Repository pour l'entité Notification.
|
* Repository pour l'entité Notification.
|
||||||
@@ -27,9 +28,7 @@ public class NotificationRepository implements PanacheRepositoryBase<Notificatio
|
|||||||
* @return Liste des notifications de l'utilisateur, triées par date de création décroissante
|
* @return Liste des notifications de l'utilisateur, triées par date de création décroissante
|
||||||
*/
|
*/
|
||||||
public List<Notification> findByUserId(UUID userId) {
|
public List<Notification> findByUserId(UUID userId) {
|
||||||
System.out.println("[LOG] Récupération des notifications pour l'utilisateur ID : " + userId);
|
|
||||||
List<Notification> notifications = find("user.id", Sort.by("createdAt", Sort.Direction.Descending), userId).list();
|
List<Notification> notifications = find("user.id", Sort.by("createdAt", Sort.Direction.Descending), userId).list();
|
||||||
System.out.println("[LOG] " + notifications.size() + " notification(s) trouvée(s) pour l'utilisateur ID : " + userId);
|
|
||||||
return notifications;
|
return notifications;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,9 +39,7 @@ public class NotificationRepository implements PanacheRepositoryBase<Notificatio
|
|||||||
* @return Liste des notifications non lues de l'utilisateur
|
* @return Liste des notifications non lues de l'utilisateur
|
||||||
*/
|
*/
|
||||||
public List<Notification> findUnreadByUserId(UUID userId) {
|
public List<Notification> findUnreadByUserId(UUID userId) {
|
||||||
System.out.println("[LOG] Récupération des notifications non lues pour l'utilisateur ID : " + userId);
|
|
||||||
List<Notification> notifications = find("user.id = ?1 AND isRead = false", Sort.by("createdAt", Sort.Direction.Descending), userId).list();
|
List<Notification> notifications = find("user.id = ?1 AND isRead = false", Sort.by("createdAt", Sort.Direction.Descending), userId).list();
|
||||||
System.out.println("[LOG] " + notifications.size() + " notification(s) non lue(s) trouvée(s) pour l'utilisateur ID : " + userId);
|
|
||||||
return notifications;
|
return notifications;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,11 +52,9 @@ public class NotificationRepository implements PanacheRepositoryBase<Notificatio
|
|||||||
* @return Liste paginée des notifications
|
* @return Liste paginée des notifications
|
||||||
*/
|
*/
|
||||||
public List<Notification> findByUserIdWithPagination(UUID userId, int page, int size) {
|
public List<Notification> findByUserIdWithPagination(UUID userId, int page, int size) {
|
||||||
System.out.println("[LOG] Récupération paginée des notifications pour l'utilisateur ID : " + userId + " (page: " + page + ", size: " + size + ")");
|
|
||||||
List<Notification> notifications = find("user.id", Sort.by("createdAt", Sort.Direction.Descending), userId)
|
List<Notification> notifications = find("user.id", Sort.by("createdAt", Sort.Direction.Descending), userId)
|
||||||
.page(Page.of(page, size))
|
.page(Page.of(page, size))
|
||||||
.list();
|
.list();
|
||||||
System.out.println("[LOG] " + notifications.size() + " notification(s) récupérée(s) pour la page " + page);
|
|
||||||
return notifications;
|
return notifications;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,7 +66,6 @@ public class NotificationRepository implements PanacheRepositoryBase<Notificatio
|
|||||||
*/
|
*/
|
||||||
public long countUnreadByUserId(UUID userId) {
|
public long countUnreadByUserId(UUID userId) {
|
||||||
long count = count("user.id = ?1 AND isRead = false", userId);
|
long count = count("user.id = ?1 AND isRead = false", userId);
|
||||||
System.out.println("[LOG] " + count + " notification(s) non lue(s) pour l'utilisateur ID : " + userId);
|
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,9 +76,7 @@ public class NotificationRepository implements PanacheRepositoryBase<Notificatio
|
|||||||
* @return Le nombre de notifications mises à jour
|
* @return Le nombre de notifications mises à jour
|
||||||
*/
|
*/
|
||||||
public int markAllAsReadByUserId(UUID userId) {
|
public int markAllAsReadByUserId(UUID userId) {
|
||||||
System.out.println("[LOG] Marquage de toutes les notifications comme lues pour l'utilisateur ID : " + userId);
|
|
||||||
int updated = update("isRead = true WHERE user.id = ?1", userId);
|
int updated = update("isRead = true WHERE user.id = ?1", userId);
|
||||||
System.out.println("[LOG] " + updated + " notification(s) marquée(s) comme lue(s)");
|
|
||||||
return updated;
|
return updated;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,9 +87,7 @@ public class NotificationRepository implements PanacheRepositoryBase<Notificatio
|
|||||||
* @return Le nombre de notifications supprimées
|
* @return Le nombre de notifications supprimées
|
||||||
*/
|
*/
|
||||||
public long deleteReadByUserId(UUID userId) {
|
public long deleteReadByUserId(UUID userId) {
|
||||||
System.out.println("[LOG] Suppression des notifications lues pour l'utilisateur ID : " + userId);
|
|
||||||
long deleted = delete("user.id = ?1 AND isRead = true", userId);
|
long deleted = delete("user.id = ?1 AND isRead = true", userId);
|
||||||
System.out.println("[LOG] " + deleted + " notification(s) supprimée(s)");
|
|
||||||
return deleted;
|
return deleted;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package com.lions.dev.repository;
|
||||||
|
|
||||||
|
import com.lions.dev.entity.auth.PasswordResetToken;
|
||||||
|
import com.lions.dev.entity.users.Users;
|
||||||
|
import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase;
|
||||||
|
import jakarta.enterprise.context.ApplicationScoped;
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Repository pour la gestion des tokens de réinitialisation de mot de passe.
|
||||||
|
*/
|
||||||
|
@ApplicationScoped
|
||||||
|
public class PasswordResetTokenRepository implements PanacheRepositoryBase<PasswordResetToken, UUID> {
|
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(PasswordResetTokenRepository.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Trouve un token par sa valeur.
|
||||||
|
*
|
||||||
|
* @param token La valeur du token
|
||||||
|
* @return Le token s'il existe
|
||||||
|
*/
|
||||||
|
public Optional<PasswordResetToken> findByToken(String token) {
|
||||||
|
return find("token", token).firstResultOptional();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Trouve un token valide par sa valeur.
|
||||||
|
*
|
||||||
|
* @param token La valeur du token
|
||||||
|
* @return Le token s'il existe, n'est pas expiré et n'a pas été utilisé
|
||||||
|
*/
|
||||||
|
public Optional<PasswordResetToken> findValidToken(String token) {
|
||||||
|
return find("token = ?1 AND used = false AND expiresAt > ?2", token, LocalDateTime.now())
|
||||||
|
.firstResultOptional();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invalide tous les tokens existants pour un utilisateur.
|
||||||
|
*
|
||||||
|
* @param user L'utilisateur dont les tokens doivent être invalidés
|
||||||
|
* @return Le nombre de tokens invalidés
|
||||||
|
*/
|
||||||
|
public long invalidateAllForUser(Users user) {
|
||||||
|
long count = update("used = true WHERE user = ?1 AND used = false", user);
|
||||||
|
if (count > 0) {
|
||||||
|
logger.info("Invalidé " + count + " token(s) existant(s) pour l'utilisateur: " + user.getEmail());
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Supprime les tokens expirés (nettoyage).
|
||||||
|
*
|
||||||
|
* @return Le nombre de tokens supprimés
|
||||||
|
*/
|
||||||
|
public long deleteExpiredTokens() {
|
||||||
|
long count = delete("expiresAt < ?1 OR used = true", LocalDateTime.now().minusDays(7));
|
||||||
|
if (count > 0) {
|
||||||
|
logger.info("Supprimé " + count + " token(s) expiré(s)");
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ import io.quarkus.panache.common.Sort;
|
|||||||
import jakarta.enterprise.context.ApplicationScoped;
|
import jakarta.enterprise.context.ApplicationScoped;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Repository pour l'entité SocialPost.
|
* Repository pour l'entité SocialPost.
|
||||||
@@ -27,11 +28,9 @@ public class SocialPostRepository implements PanacheRepositoryBase<SocialPost, U
|
|||||||
* @return Liste paginée des posts
|
* @return Liste paginée des posts
|
||||||
*/
|
*/
|
||||||
public List<SocialPost> findAllWithPagination(int page, int size) {
|
public List<SocialPost> findAllWithPagination(int page, int size) {
|
||||||
System.out.println("[LOG] Récupération paginée des posts (page: " + page + ", size: " + size + ")");
|
|
||||||
List<SocialPost> posts = findAll(Sort.by("createdAt", Sort.Direction.Descending))
|
List<SocialPost> posts = findAll(Sort.by("createdAt", Sort.Direction.Descending))
|
||||||
.page(Page.of(page, size))
|
.page(Page.of(page, size))
|
||||||
.list();
|
.list();
|
||||||
System.out.println("[LOG] " + posts.size() + " post(s) récupéré(s)");
|
|
||||||
return posts;
|
return posts;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,9 +41,7 @@ public class SocialPostRepository implements PanacheRepositoryBase<SocialPost, U
|
|||||||
* @return Liste des posts de l'utilisateur
|
* @return Liste des posts de l'utilisateur
|
||||||
*/
|
*/
|
||||||
public List<SocialPost> findByUserId(UUID userId) {
|
public List<SocialPost> findByUserId(UUID userId) {
|
||||||
System.out.println("[LOG] Récupération des posts pour l'utilisateur ID : " + userId);
|
|
||||||
List<SocialPost> posts = find("user.id", Sort.by("createdAt", Sort.Direction.Descending), userId).list();
|
List<SocialPost> posts = find("user.id", Sort.by("createdAt", Sort.Direction.Descending), userId).list();
|
||||||
System.out.println("[LOG] " + posts.size() + " post(s) trouvé(s) pour l'utilisateur ID : " + userId);
|
|
||||||
return posts;
|
return posts;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,10 +52,8 @@ public class SocialPostRepository implements PanacheRepositoryBase<SocialPost, U
|
|||||||
* @return Liste des posts correspondant à la recherche
|
* @return Liste des posts correspondant à la recherche
|
||||||
*/
|
*/
|
||||||
public List<SocialPost> searchByContent(String query) {
|
public List<SocialPost> searchByContent(String query) {
|
||||||
System.out.println("[LOG] Recherche de posts avec la requête : " + query);
|
|
||||||
String searchPattern = "%" + query.toLowerCase() + "%";
|
String searchPattern = "%" + query.toLowerCase() + "%";
|
||||||
List<SocialPost> posts = find("LOWER(content) LIKE ?1", Sort.by("createdAt", Sort.Direction.Descending), searchPattern).list();
|
List<SocialPost> posts = find("LOWER(content) LIKE ?1", Sort.by("createdAt", Sort.Direction.Descending), searchPattern).list();
|
||||||
System.out.println("[LOG] " + posts.size() + " post(s) trouvé(s) pour la requête : " + query);
|
|
||||||
return posts;
|
return posts;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,11 +64,9 @@ public class SocialPostRepository implements PanacheRepositoryBase<SocialPost, U
|
|||||||
* @return Liste des posts les plus populaires
|
* @return Liste des posts les plus populaires
|
||||||
*/
|
*/
|
||||||
public List<SocialPost> findPopularPosts(int limit) {
|
public List<SocialPost> findPopularPosts(int limit) {
|
||||||
System.out.println("[LOG] Récupération des " + limit + " posts les plus populaires");
|
|
||||||
List<SocialPost> posts = findAll(Sort.by("likesCount", Sort.Direction.Descending))
|
List<SocialPost> posts = findAll(Sort.by("likesCount", Sort.Direction.Descending))
|
||||||
.page(0, limit)
|
.page(0, limit)
|
||||||
.list();
|
.list();
|
||||||
System.out.println("[LOG] " + posts.size() + " post(s) populaire(s) récupéré(s)");
|
|
||||||
return posts;
|
return posts;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,10 +80,8 @@ public class SocialPostRepository implements PanacheRepositoryBase<SocialPost, U
|
|||||||
* @return Liste paginée des posts des amis
|
* @return Liste paginée des posts des amis
|
||||||
*/
|
*/
|
||||||
public List<SocialPost> findPostsByFriends(UUID userId, List<UUID> friendIds, int page, int size) {
|
public List<SocialPost> findPostsByFriends(UUID userId, List<UUID> friendIds, int page, int size) {
|
||||||
System.out.println("[LOG] Récupération des posts des amis pour l'utilisateur ID : " + userId);
|
|
||||||
|
|
||||||
if (friendIds == null || friendIds.isEmpty()) {
|
if (friendIds == null || friendIds.isEmpty()) {
|
||||||
System.out.println("[LOG] Aucun ami trouvé pour l'utilisateur ID : " + userId);
|
|
||||||
return List.of();
|
return List.of();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,7 +93,6 @@ public class SocialPostRepository implements PanacheRepositoryBase<SocialPost, U
|
|||||||
.page(Page.of(page, size))
|
.page(Page.of(page, size))
|
||||||
.list();
|
.list();
|
||||||
|
|
||||||
System.out.println("[LOG] " + posts.size() + " post(s) récupéré(s) des amis");
|
|
||||||
return posts;
|
return posts;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import jakarta.enterprise.context.ApplicationScoped;
|
|||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Repository pour l'entité Story.
|
* Repository pour l'entité Story.
|
||||||
@@ -36,13 +37,11 @@ public class StoryRepository implements PanacheRepositoryBase<Story, UUID> {
|
|||||||
* @return Liste paginée des stories actives
|
* @return Liste paginée des stories actives
|
||||||
*/
|
*/
|
||||||
public List<Story> findAllActive(int page, int size) {
|
public List<Story> findAllActive(int page, int size) {
|
||||||
System.out.println("[LOG] Récupération de toutes les stories actives (page: " + page + ", size: " + size + ")");
|
|
||||||
List<Story> stories = find("isActive = true AND expiresAt > ?1",
|
List<Story> stories = find("isActive = true AND expiresAt > ?1",
|
||||||
Sort.by("createdAt", Sort.Direction.Descending),
|
Sort.by("createdAt", Sort.Direction.Descending),
|
||||||
LocalDateTime.now())
|
LocalDateTime.now())
|
||||||
.page(page, size)
|
.page(page, size)
|
||||||
.list();
|
.list();
|
||||||
System.out.println("[LOG] " + stories.size() + " story(ies) active(s) récupérée(s)");
|
|
||||||
return stories;
|
return stories;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,14 +64,12 @@ public class StoryRepository implements PanacheRepositoryBase<Story, UUID> {
|
|||||||
* @return Liste paginée des stories actives de l'utilisateur
|
* @return Liste paginée des stories actives de l'utilisateur
|
||||||
*/
|
*/
|
||||||
public List<Story> findActiveByUserId(UUID userId, int page, int size) {
|
public List<Story> findActiveByUserId(UUID userId, int page, int size) {
|
||||||
System.out.println("[LOG] Récupération des stories actives pour l'utilisateur ID : " + userId + " (page: " + page + ", size: " + size + ")");
|
|
||||||
List<Story> stories = find("user.id = ?1 AND isActive = true AND expiresAt > ?2",
|
List<Story> stories = find("user.id = ?1 AND isActive = true AND expiresAt > ?2",
|
||||||
Sort.by("createdAt", Sort.Direction.Descending),
|
Sort.by("createdAt", Sort.Direction.Descending),
|
||||||
userId,
|
userId,
|
||||||
LocalDateTime.now())
|
LocalDateTime.now())
|
||||||
.page(page, size)
|
.page(page, size)
|
||||||
.list();
|
.list();
|
||||||
System.out.println("[LOG] " + stories.size() + " story(ies) active(s) trouvée(s) pour l'utilisateur ID : " + userId);
|
|
||||||
return stories;
|
return stories;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,9 +80,7 @@ public class StoryRepository implements PanacheRepositoryBase<Story, UUID> {
|
|||||||
* @return Liste de toutes les stories de l'utilisateur
|
* @return Liste de toutes les stories de l'utilisateur
|
||||||
*/
|
*/
|
||||||
public List<Story> findAllByUserId(UUID userId) {
|
public List<Story> findAllByUserId(UUID userId) {
|
||||||
System.out.println("[LOG] Récupération de toutes les stories pour l'utilisateur ID : " + userId);
|
|
||||||
List<Story> stories = find("user.id", Sort.by("createdAt", Sort.Direction.Descending), userId).list();
|
List<Story> stories = find("user.id", Sort.by("createdAt", Sort.Direction.Descending), userId).list();
|
||||||
System.out.println("[LOG] " + stories.size() + " story(ies) trouvée(s) pour l'utilisateur ID : " + userId);
|
|
||||||
return stories;
|
return stories;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,9 +91,7 @@ public class StoryRepository implements PanacheRepositoryBase<Story, UUID> {
|
|||||||
* @return Liste des stories expirées
|
* @return Liste des stories expirées
|
||||||
*/
|
*/
|
||||||
public List<Story> findExpiredStories() {
|
public List<Story> findExpiredStories() {
|
||||||
System.out.println("[LOG] Récupération des stories expirées");
|
|
||||||
List<Story> stories = find("isActive = true AND expiresAt <= ?1", LocalDateTime.now()).list();
|
List<Story> stories = find("isActive = true AND expiresAt <= ?1", LocalDateTime.now()).list();
|
||||||
System.out.println("[LOG] " + stories.size() + " story(ies) expirée(s) trouvée(s)");
|
|
||||||
return stories;
|
return stories;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,9 +102,7 @@ public class StoryRepository implements PanacheRepositoryBase<Story, UUID> {
|
|||||||
* @return Le nombre de stories désactivées
|
* @return Le nombre de stories désactivées
|
||||||
*/
|
*/
|
||||||
public int deactivateExpiredStories() {
|
public int deactivateExpiredStories() {
|
||||||
System.out.println("[LOG] Désactivation des stories expirées");
|
|
||||||
int updated = update("isActive = false WHERE isActive = true AND expiresAt <= ?1", LocalDateTime.now());
|
int updated = update("isActive = false WHERE isActive = true AND expiresAt <= ?1", LocalDateTime.now());
|
||||||
System.out.println("[LOG] " + updated + " story(ies) désactivée(s)");
|
|
||||||
return updated;
|
return updated;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,13 +113,11 @@ public class StoryRepository implements PanacheRepositoryBase<Story, UUID> {
|
|||||||
* @return Liste des stories les plus vues
|
* @return Liste des stories les plus vues
|
||||||
*/
|
*/
|
||||||
public List<Story> findMostViewedStories(int limit) {
|
public List<Story> findMostViewedStories(int limit) {
|
||||||
System.out.println("[LOG] Récupération des " + limit + " stories les plus vues");
|
|
||||||
List<Story> stories = find("isActive = true AND expiresAt > ?1",
|
List<Story> stories = find("isActive = true AND expiresAt > ?1",
|
||||||
Sort.by("viewsCount", Sort.Direction.Descending),
|
Sort.by("viewsCount", Sort.Direction.Descending),
|
||||||
LocalDateTime.now())
|
LocalDateTime.now())
|
||||||
.page(0, limit)
|
.page(0, limit)
|
||||||
.list();
|
.list();
|
||||||
System.out.println("[LOG] " + stories.size() + " story(ies) populaire(s) récupérée(s)");
|
|
||||||
return stories;
|
return stories;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import jakarta.persistence.EntityManager;
|
|||||||
import jakarta.persistence.PersistenceContext;
|
import jakarta.persistence.PersistenceContext;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Repository pour l'entité Users.
|
* Repository pour l'entité Users.
|
||||||
@@ -62,12 +63,9 @@ public class UsersRepository implements PanacheRepositoryBase<Users, UUID> {
|
|||||||
* @return Un Optional contenant l'utilisateur s'il est trouvé, sinon un Optional vide.
|
* @return Un Optional contenant l'utilisateur s'il est trouvé, sinon un Optional vide.
|
||||||
*/
|
*/
|
||||||
public Optional<Users> findByEmail(String email) {
|
public Optional<Users> findByEmail(String email) {
|
||||||
System.out.println("[LOG] Recherche de l'utilisateur avec l'email : " + email);
|
|
||||||
Users user = find("email", email).firstResult();
|
Users user = find("email", email).firstResult();
|
||||||
if (user != null) {
|
if (user != null) {
|
||||||
System.out.println("[LOG] Utilisateur trouvé : " + user.getEmail());
|
|
||||||
} else {
|
} else {
|
||||||
System.out.println("[LOG] Aucun utilisateur trouvé avec l'email : " + email);
|
|
||||||
}
|
}
|
||||||
return Optional.ofNullable(user);
|
return Optional.ofNullable(user);
|
||||||
}
|
}
|
||||||
@@ -79,10 +77,8 @@ public class UsersRepository implements PanacheRepositoryBase<Users, UUID> {
|
|||||||
* @return true si un utilisateur avec cet email existe, sinon false.
|
* @return true si un utilisateur avec cet email existe, sinon false.
|
||||||
*/
|
*/
|
||||||
public boolean existsByEmail(String email) {
|
public boolean existsByEmail(String email) {
|
||||||
System.out.println("[LOG] Vérification de l'existence de l'utilisateur avec l'email : " + email);
|
|
||||||
long count = find("email", email).count();
|
long count = find("email", email).count();
|
||||||
boolean exists = count > 0;
|
boolean exists = count > 0;
|
||||||
System.out.println("[LOG] Utilisateur existe : " + exists);
|
|
||||||
return exists;
|
return exists;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,13 +89,10 @@ public class UsersRepository implements PanacheRepositoryBase<Users, UUID> {
|
|||||||
* @return true si l'utilisateur a été supprimé, sinon false.
|
* @return true si l'utilisateur a été supprimé, sinon false.
|
||||||
*/
|
*/
|
||||||
public boolean deleteById(UUID id) {
|
public boolean deleteById(UUID id) {
|
||||||
System.out.println("[LOG] Suppression de l'utilisateur avec l'ID : " + id);
|
|
||||||
long deletedCount = delete("id", id); // Utiliser long pour récupérer le nombre d'enregistrements supprimés
|
long deletedCount = delete("id", id); // Utiliser long pour récupérer le nombre d'enregistrements supprimés
|
||||||
boolean deleted = deletedCount > 0; // Convertir en boolean
|
boolean deleted = deletedCount > 0; // Convertir en boolean
|
||||||
if (deleted) {
|
if (deleted) {
|
||||||
System.out.println("[LOG] Utilisateur avec l'ID " + id + " supprimé avec succès.");
|
|
||||||
} else {
|
} else {
|
||||||
System.out.println("[LOG] Aucune suppression, utilisateur avec l'ID " + id + " introuvable.");
|
|
||||||
}
|
}
|
||||||
return deleted;
|
return deleted;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,6 +44,12 @@ public class EstablishmentRatingResource {
|
|||||||
@PathParam("establishmentId") String establishmentId,
|
@PathParam("establishmentId") String establishmentId,
|
||||||
@QueryParam("userId") String userIdStr,
|
@QueryParam("userId") String userIdStr,
|
||||||
@Valid EstablishmentRatingRequestDTO requestDTO) {
|
@Valid EstablishmentRatingRequestDTO requestDTO) {
|
||||||
|
if (userIdStr == null || userIdStr.isBlank()) {
|
||||||
|
LOG.warn("Soumission de note sans userId pour l'établissement " + establishmentId);
|
||||||
|
return Response.status(Response.Status.BAD_REQUEST)
|
||||||
|
.entity("Le paramètre userId est requis")
|
||||||
|
.build();
|
||||||
|
}
|
||||||
LOG.info("Soumission d'une note pour l'établissement " + establishmentId + " par l'utilisateur " + userIdStr);
|
LOG.info("Soumission d'une note pour l'établissement " + establishmentId + " par l'utilisateur " + userIdStr);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -82,6 +88,12 @@ public class EstablishmentRatingResource {
|
|||||||
@PathParam("establishmentId") String establishmentId,
|
@PathParam("establishmentId") String establishmentId,
|
||||||
@QueryParam("userId") String userIdStr,
|
@QueryParam("userId") String userIdStr,
|
||||||
@Valid EstablishmentRatingRequestDTO requestDTO) {
|
@Valid EstablishmentRatingRequestDTO requestDTO) {
|
||||||
|
if (userIdStr == null || userIdStr.isBlank()) {
|
||||||
|
LOG.warn("Mise à jour de note sans userId pour l'établissement " + establishmentId);
|
||||||
|
return Response.status(Response.Status.BAD_REQUEST)
|
||||||
|
.entity("Le paramètre userId est requis")
|
||||||
|
.build();
|
||||||
|
}
|
||||||
LOG.info("Mise à jour de la note pour l'établissement " + establishmentId + " par l'utilisateur " + userIdStr);
|
LOG.info("Mise à jour de la note pour l'établissement " + establishmentId + " par l'utilisateur " + userIdStr);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -2,9 +2,13 @@ package com.lions.dev.resource;
|
|||||||
|
|
||||||
import com.lions.dev.dto.request.establishment.EstablishmentCreateRequestDTO;
|
import com.lions.dev.dto.request.establishment.EstablishmentCreateRequestDTO;
|
||||||
import com.lions.dev.dto.request.establishment.EstablishmentUpdateRequestDTO;
|
import com.lions.dev.dto.request.establishment.EstablishmentUpdateRequestDTO;
|
||||||
|
import com.lions.dev.dto.response.establishment.BusinessHoursResponseDTO;
|
||||||
|
import com.lions.dev.dto.response.establishment.EstablishmentAmenityResponseDTO;
|
||||||
import com.lions.dev.dto.response.establishment.EstablishmentResponseDTO;
|
import com.lions.dev.dto.response.establishment.EstablishmentResponseDTO;
|
||||||
import com.lions.dev.entity.establishment.Establishment;
|
import com.lions.dev.entity.establishment.Establishment;
|
||||||
import com.lions.dev.entity.users.Users;
|
import com.lions.dev.entity.users.Users;
|
||||||
|
import com.lions.dev.repository.BusinessHoursRepository;
|
||||||
|
import com.lions.dev.repository.EstablishmentAmenityRepository;
|
||||||
import com.lions.dev.repository.EstablishmentRepository;
|
import com.lions.dev.repository.EstablishmentRepository;
|
||||||
import com.lions.dev.repository.UsersRepository;
|
import com.lions.dev.repository.UsersRepository;
|
||||||
import com.lions.dev.service.EstablishmentService;
|
import com.lions.dev.service.EstablishmentService;
|
||||||
@@ -43,6 +47,12 @@ public class EstablishmentResource {
|
|||||||
@Inject
|
@Inject
|
||||||
EstablishmentRepository establishmentRepository;
|
EstablishmentRepository establishmentRepository;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
BusinessHoursRepository businessHoursRepository;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
EstablishmentAmenityRepository establishmentAmenityRepository;
|
||||||
|
|
||||||
private static final Logger LOG = Logger.getLogger(EstablishmentResource.class);
|
private static final Logger LOG = Logger.getLogger(EstablishmentResource.class);
|
||||||
|
|
||||||
// *********** Création d'un établissement ***********
|
// *********** Création d'un établissement ***********
|
||||||
@@ -145,6 +155,58 @@ public class EstablishmentResource {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// *********** Horaires d'ouverture d'un établissement ***********
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("/{id}/business-hours")
|
||||||
|
@Operation(summary = "Récupérer les horaires d'ouverture d'un établissement",
|
||||||
|
description = "Retourne la liste des horaires d'ouverture pour l'établissement donné")
|
||||||
|
public Response getBusinessHoursByEstablishmentId(@PathParam("id") UUID id) {
|
||||||
|
LOG.info("[LOG] Récupération des horaires pour l'établissement : " + id);
|
||||||
|
try {
|
||||||
|
establishmentService.getEstablishmentById(id);
|
||||||
|
var list = businessHoursRepository.findByEstablishmentId(id).stream()
|
||||||
|
.map(bh -> new BusinessHoursResponseDTO(bh, id))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
return Response.ok(list).build();
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
LOG.warn("[WARN] " + e.getMessage());
|
||||||
|
return Response.status(Response.Status.NOT_FOUND).entity(e.getMessage()).build();
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOG.error("[ERROR] Erreur lors de la récupération des horaires", e);
|
||||||
|
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||||
|
.entity("Erreur lors de la récupération des horaires").build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// *********** Équipements d'un établissement ***********
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("/{id}/amenities")
|
||||||
|
@Operation(summary = "Récupérer les équipements d'un établissement",
|
||||||
|
description = "Retourne la liste des équipements (amenities) pour l'établissement donné")
|
||||||
|
public Response getAmenitiesByEstablishmentId(@PathParam("id") UUID id) {
|
||||||
|
LOG.info("[LOG] Récupération des équipements pour l'établissement : " + id);
|
||||||
|
try {
|
||||||
|
establishmentService.getEstablishmentById(id);
|
||||||
|
var list = establishmentAmenityRepository.findByEstablishmentIdWithType(id).stream()
|
||||||
|
.map(ea -> new EstablishmentAmenityResponseDTO(
|
||||||
|
ea,
|
||||||
|
ea.getAmenityType() != null ? ea.getAmenityType().getName() : null,
|
||||||
|
ea.getAmenityType() != null ? ea.getAmenityType().getCategory() : null,
|
||||||
|
ea.getAmenityType() != null ? ea.getAmenityType().getIcon() : null))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
return Response.ok(list).build();
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
LOG.warn("[WARN] " + e.getMessage());
|
||||||
|
return Response.status(Response.Status.NOT_FOUND).entity(e.getMessage()).build();
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOG.error("[ERROR] Erreur lors de la récupération des équipements", e);
|
||||||
|
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||||
|
.entity("Erreur lors de la récupération des équipements").build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// *********** Récupération d'un établissement par ID ***********
|
// *********** Récupération d'un établissement par ID ***********
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package com.lions.dev.resource;
|
package com.lions.dev.resource;
|
||||||
|
|
||||||
import com.lions.dev.core.errors.exceptions.EventNotFoundException;
|
import com.lions.dev.core.errors.exceptions.EventNotFoundException;
|
||||||
import com.lions.dev.dto.UserResponseDTO;
|
|
||||||
import com.lions.dev.dto.request.events.EventCreateRequestDTO;
|
import com.lions.dev.dto.request.events.EventCreateRequestDTO;
|
||||||
import com.lions.dev.dto.request.events.EventReadManyByIdRequestDTO;
|
import com.lions.dev.dto.request.events.EventReadManyByIdRequestDTO;
|
||||||
import com.lions.dev.dto.request.events.EventUpdateRequestDTO;
|
import com.lions.dev.dto.request.events.EventUpdateRequestDTO;
|
||||||
@@ -10,6 +9,7 @@ import com.lions.dev.dto.response.events.EventCreateResponseDTO;
|
|||||||
import com.lions.dev.dto.response.events.EventReadManyByIdResponseDTO;
|
import com.lions.dev.dto.response.events.EventReadManyByIdResponseDTO;
|
||||||
import com.lions.dev.dto.response.events.EventUpdateResponseDTO;
|
import com.lions.dev.dto.response.events.EventUpdateResponseDTO;
|
||||||
import com.lions.dev.dto.response.friends.FriendshipReadFriendDetailsResponseDTO;
|
import com.lions.dev.dto.response.friends.FriendshipReadFriendDetailsResponseDTO;
|
||||||
|
import com.lions.dev.dto.response.users.UserResponseDTO;
|
||||||
import com.lions.dev.entity.comment.Comment;
|
import com.lions.dev.entity.comment.Comment;
|
||||||
import com.lions.dev.entity.events.Events;
|
import com.lions.dev.entity.events.Events;
|
||||||
import com.lions.dev.entity.users.Users;
|
import com.lions.dev.entity.users.Users;
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import jakarta.inject.Inject;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
@Path("/media")
|
@Path("/media")
|
||||||
@@ -81,11 +82,16 @@ public class FileUploadResource {
|
|||||||
// Note: En production, vous devriez utiliser une URL publique (CDN, S3, etc.)
|
// Note: En production, vous devriez utiliser une URL publique (CDN, S3, etc.)
|
||||||
String fileUrl = buildFileUrl(finalFileName, uriInfo);
|
String fileUrl = buildFileUrl(finalFileName, uriInfo);
|
||||||
String thumbnailUrl = null;
|
String thumbnailUrl = null;
|
||||||
|
|
||||||
// Pour les vidéos, on pourrait générer un thumbnail ici
|
|
||||||
if ("video".equalsIgnoreCase(mediaType)) {
|
if ("video".equalsIgnoreCase(mediaType)) {
|
||||||
// TODO: Générer un thumbnail pour les vidéos
|
Optional<java.nio.file.Path> thumbPath = fileService.generateVideoThumbnail(savedFilePath);
|
||||||
thumbnailUrl = fileUrl; // Placeholder
|
if (thumbPath.isPresent()) {
|
||||||
|
thumbnailUrl = buildFileUrl(thumbPath.get().getFileName().toString(), uriInfo);
|
||||||
|
LOG.infof("Thumbnail vidéo généré : %s", thumbnailUrl);
|
||||||
|
} else {
|
||||||
|
thumbnailUrl = fileUrl;
|
||||||
|
LOG.infof("Thumbnail vidéo non généré (FFmpeg absent ou erreur), utilisation de l'URL vidéo en placeholder");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Construire la réponse JSON
|
// Construire la réponse JSON
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ public class NotificationResource {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Récupère toutes les notifications d'un utilisateur.
|
* Récupère toutes les notifications d'un utilisateur.
|
||||||
|
* En production, le userId doit être dérivé du contexte d'authentification (JWT/session), pas de l'URL.
|
||||||
*
|
*
|
||||||
* @param userId L'ID de l'utilisateur
|
* @param userId L'ID de l'utilisateur
|
||||||
* @return Liste des notifications de l'utilisateur
|
* @return Liste des notifications de l'utilisateur
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package com.lions.dev.resource;
|
|||||||
|
|
||||||
import com.lions.dev.dto.PasswordResetRequest;
|
import com.lions.dev.dto.PasswordResetRequest;
|
||||||
import com.lions.dev.dto.request.users.AssignRoleRequestDTO;
|
import com.lions.dev.dto.request.users.AssignRoleRequestDTO;
|
||||||
|
import com.lions.dev.dto.request.users.SetUserActiveRequestDTO;
|
||||||
import com.lions.dev.dto.request.users.UserAuthenticateRequestDTO;
|
import com.lions.dev.dto.request.users.UserAuthenticateRequestDTO;
|
||||||
import com.lions.dev.dto.request.users.UserCreateRequestDTO;
|
import com.lions.dev.dto.request.users.UserCreateRequestDTO;
|
||||||
import com.lions.dev.dto.response.users.UserAuthenticateResponseDTO;
|
import com.lions.dev.dto.response.users.UserAuthenticateResponseDTO;
|
||||||
@@ -9,6 +10,7 @@ import com.lions.dev.dto.response.users.UserCreateResponseDTO;
|
|||||||
import com.lions.dev.dto.response.users.UserDeleteResponseDto;
|
import com.lions.dev.dto.response.users.UserDeleteResponseDto;
|
||||||
import com.lions.dev.entity.users.Users;
|
import com.lions.dev.entity.users.Users;
|
||||||
import com.lions.dev.exception.UserNotFoundException;
|
import com.lions.dev.exception.UserNotFoundException;
|
||||||
|
import com.lions.dev.service.PasswordResetService;
|
||||||
import com.lions.dev.service.UsersService;
|
import com.lions.dev.service.UsersService;
|
||||||
import jakarta.inject.Inject;
|
import jakarta.inject.Inject;
|
||||||
import jakarta.transaction.Transactional;
|
import jakarta.transaction.Transactional;
|
||||||
@@ -42,6 +44,9 @@ public class UsersResource {
|
|||||||
@Inject
|
@Inject
|
||||||
UsersService userService;
|
UsersService userService;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
PasswordResetService passwordResetService;
|
||||||
|
|
||||||
@ConfigProperty(name = "afterwork.super-admin.api-key", defaultValue = "")
|
@ConfigProperty(name = "afterwork.super-admin.api-key", defaultValue = "")
|
||||||
Optional<String> superAdminApiKey;
|
Optional<String> superAdminApiKey;
|
||||||
|
|
||||||
@@ -300,18 +305,8 @@ public class UsersResource {
|
|||||||
LOG.info("Demande de reinitialisation de mot de passe pour l'email : " + request.getEmail());
|
LOG.info("Demande de reinitialisation de mot de passe pour l'email : " + request.getEmail());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Rechercher l'utilisateur par email
|
// Le service gère tout : création du token et envoi de l'email
|
||||||
Users user = userService.findByEmail(request.getEmail());
|
passwordResetService.initiatePasswordReset(request.getEmail());
|
||||||
|
|
||||||
if (user != null) {
|
|
||||||
// En standby : pas encore de service d'envoi de mail. Quand disponible :
|
|
||||||
// - generer un token de reset (table dédiée ou champ user avec expiration)
|
|
||||||
// - appeler emailService.sendPasswordResetEmail(user.getEmail(), resetToken)
|
|
||||||
// Pour l'instant, on retourne success pour ne pas reveler si l'email existe.
|
|
||||||
LOG.info("Utilisateur trouve, email de reinitialisation (en standby - pas de mail service) : " + request.getEmail());
|
|
||||||
} else {
|
|
||||||
LOG.info("Aucun utilisateur trouve avec cet email (ne pas reveler) : " + request.getEmail());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Toujours retourner 200 pour ne pas reveler si l'email existe
|
// Toujours retourner 200 pour ne pas reveler si l'email existe
|
||||||
return Response.ok()
|
return Response.ok()
|
||||||
@@ -326,6 +321,50 @@ public class UsersResource {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Endpoint pour réinitialiser le mot de passe avec un token valide.
|
||||||
|
*
|
||||||
|
* @param token Le token de réinitialisation reçu par email.
|
||||||
|
* @param newPassword Le nouveau mot de passe.
|
||||||
|
* @return Une réponse HTTP indiquant si la réinitialisation a réussi.
|
||||||
|
*/
|
||||||
|
@POST
|
||||||
|
@Path("/reset-password")
|
||||||
|
@Operation(summary = "Réinitialiser le mot de passe avec un token",
|
||||||
|
description = "Réinitialise le mot de passe en utilisant le token reçu par email")
|
||||||
|
@APIResponse(responseCode = "200", description = "Mot de passe réinitialisé avec succès")
|
||||||
|
@APIResponse(responseCode = "400", description = "Token invalide ou expiré")
|
||||||
|
public Response resetPasswordWithToken(
|
||||||
|
@QueryParam("token") String token,
|
||||||
|
@QueryParam("newPassword") String newPassword) {
|
||||||
|
|
||||||
|
if (token == null || token.isBlank()) {
|
||||||
|
return Response.status(Response.Status.BAD_REQUEST)
|
||||||
|
.entity(Map.of("message", "Le token est requis"))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newPassword == null || newPassword.length() < 8) {
|
||||||
|
return Response.status(Response.Status.BAD_REQUEST)
|
||||||
|
.entity(Map.of("message", "Le mot de passe doit contenir au moins 8 caractères"))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean success = passwordResetService.resetPassword(token, newPassword);
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
LOG.info("Mot de passe réinitialisé avec succès via token");
|
||||||
|
return Response.ok()
|
||||||
|
.entity(Map.of("message", "Votre mot de passe a été réinitialisé avec succès"))
|
||||||
|
.build();
|
||||||
|
} else {
|
||||||
|
LOG.warn("Échec de réinitialisation : token invalide ou expiré");
|
||||||
|
return Response.status(Response.Status.BAD_REQUEST)
|
||||||
|
.entity(Map.of("message", "Le lien de réinitialisation est invalide ou a expiré"))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attribue un rôle à un utilisateur (réservé au super administrateur).
|
* Attribue un rôle à un utilisateur (réservé au super administrateur).
|
||||||
* Requiert le header X-Super-Admin-Key correspondant à afterwork.super-admin.api-key.
|
* Requiert le header X-Super-Admin-Key correspondant à afterwork.super-admin.api-key.
|
||||||
@@ -420,4 +459,51 @@ public class UsersResource {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Force l'activation ou la suspension d'un utilisateur (réservé au super administrateur).
|
||||||
|
* Utilisé pour les managers : active = false = suspendu.
|
||||||
|
*
|
||||||
|
* @param id L'ID de l'utilisateur.
|
||||||
|
* @param request Le DTO contenant active (true = forcer l'activation, false = suspendre).
|
||||||
|
* @param apiKeyHeader Valeur du header X-Super-Admin-Key.
|
||||||
|
* @return L'utilisateur mis à jour.
|
||||||
|
*/
|
||||||
|
@PATCH
|
||||||
|
@Path("/{id}/active")
|
||||||
|
@Transactional
|
||||||
|
@Operation(summary = "Forcer activation ou suspendre un utilisateur (super admin)",
|
||||||
|
description = "Modifie le statut actif (isActive) d'un utilisateur. Réservé au super administrateur (header X-Super-Admin-Key).")
|
||||||
|
@APIResponse(responseCode = "200", description = "Statut actif mis à jour")
|
||||||
|
@APIResponse(responseCode = "403", description = "Clé super admin invalide ou absente")
|
||||||
|
@APIResponse(responseCode = "404", description = "Utilisateur non trouvé")
|
||||||
|
public Response setUserActive(
|
||||||
|
@PathParam("id") UUID id,
|
||||||
|
@Valid SetUserActiveRequestDTO request,
|
||||||
|
@HeaderParam(SUPER_ADMIN_KEY_HEADER) String apiKeyHeader) {
|
||||||
|
|
||||||
|
String key = superAdminApiKey.orElse("");
|
||||||
|
if (key.isBlank()) {
|
||||||
|
LOG.warn("Opération setUserActive refusée : afterwork.super-admin.api-key non configurée");
|
||||||
|
return Response.status(Response.Status.FORBIDDEN)
|
||||||
|
.entity("{\"message\": \"Opération non autorisée : clé super admin non configurée.\"}")
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
if (apiKeyHeader == null || !apiKeyHeader.equals(key)) {
|
||||||
|
LOG.warn("Opération setUserActive refusée : clé super admin invalide ou absente");
|
||||||
|
return Response.status(Response.Status.FORBIDDEN)
|
||||||
|
.entity("{\"message\": \"Clé super administrateur invalide ou absente.\"}")
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Users user = userService.setUserActive(id, request.getActive());
|
||||||
|
UserCreateResponseDTO responseDTO = new UserCreateResponseDTO(user);
|
||||||
|
return Response.ok(responseDTO).build();
|
||||||
|
} catch (UserNotFoundException e) {
|
||||||
|
return Response.status(Response.Status.NOT_FOUND)
|
||||||
|
.entity("{\"message\": \"" + e.getMessage() + "\"}")
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,10 +7,17 @@ import jakarta.inject.Inject;
|
|||||||
import jakarta.ws.rs.*;
|
import jakarta.ws.rs.*;
|
||||||
import jakarta.ws.rs.core.MediaType;
|
import jakarta.ws.rs.core.MediaType;
|
||||||
import jakarta.ws.rs.core.Response;
|
import jakarta.ws.rs.core.Response;
|
||||||
|
import org.eclipse.microprofile.config.inject.ConfigProperty;
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
|
|
||||||
|
import javax.crypto.Mac;
|
||||||
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Webhook Wave pour les notifications de paiement (payment.completed, payment.cancelled, etc.).
|
* Webhook Wave pour les notifications de paiement (payment.completed, payment.cancelled, etc.).
|
||||||
|
* Si wave.webhook.secret est configuré, la requête doit contenir le header X-Wave-Signature (HMAC-SHA256 du body).
|
||||||
*/
|
*/
|
||||||
@Path("/webhooks/wave")
|
@Path("/webhooks/wave")
|
||||||
@Consumes(MediaType.APPLICATION_JSON)
|
@Consumes(MediaType.APPLICATION_JSON)
|
||||||
@@ -18,15 +25,32 @@ import org.jboss.logging.Logger;
|
|||||||
public class WaveWebhookResource {
|
public class WaveWebhookResource {
|
||||||
|
|
||||||
private static final Logger LOG = Logger.getLogger(WaveWebhookResource.class);
|
private static final Logger LOG = Logger.getLogger(WaveWebhookResource.class);
|
||||||
|
private static final String SIGNATURE_HEADER = "X-Wave-Signature";
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
WavePaymentService wavePaymentService;
|
WavePaymentService wavePaymentService;
|
||||||
|
|
||||||
|
@ConfigProperty(name = "wave.webhook.secret", defaultValue = "")
|
||||||
|
Optional<String> webhookSecret;
|
||||||
|
|
||||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||||
|
|
||||||
@POST
|
@POST
|
||||||
public Response handleWebhook(String payload) {
|
public Response handleWebhook(
|
||||||
|
@HeaderParam(SIGNATURE_HEADER) String signature,
|
||||||
|
String payload) {
|
||||||
try {
|
try {
|
||||||
|
if (webhookSecret.isPresent() && !webhookSecret.get().isBlank()) {
|
||||||
|
if (signature == null || signature.isBlank()) {
|
||||||
|
LOG.warn("Webhook Wave rejeté : header " + SIGNATURE_HEADER + " absent");
|
||||||
|
return Response.status(Response.Status.UNAUTHORIZED).build();
|
||||||
|
}
|
||||||
|
String expected = hmacSha256(webhookSecret.get(), payload);
|
||||||
|
if (!constantTimeEquals(signature.trim(), expected)) {
|
||||||
|
LOG.warn("Webhook Wave rejeté : signature invalide");
|
||||||
|
return Response.status(Response.Status.UNAUTHORIZED).build();
|
||||||
|
}
|
||||||
|
}
|
||||||
JsonNode node = objectMapper.readTree(payload);
|
JsonNode node = objectMapper.readTree(payload);
|
||||||
wavePaymentService.handleWebhook(node);
|
wavePaymentService.handleWebhook(node);
|
||||||
return Response.ok().build();
|
return Response.ok().build();
|
||||||
@@ -35,4 +59,28 @@ public class WaveWebhookResource {
|
|||||||
return Response.status(Response.Status.BAD_REQUEST).build();
|
return Response.status(Response.Status.BAD_REQUEST).build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static String hmacSha256(String secret, String payload) {
|
||||||
|
try {
|
||||||
|
Mac mac = Mac.getInstance("HmacSHA256");
|
||||||
|
mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
|
||||||
|
byte[] hash = mac.doFinal(payload.getBytes(StandardCharsets.UTF_8));
|
||||||
|
StringBuilder sb = new StringBuilder(hash.length * 2);
|
||||||
|
for (byte b : hash) {
|
||||||
|
sb.append(String.format("%02x", b));
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("HMAC computation failed", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean constantTimeEquals(String a, String b) {
|
||||||
|
if (a.length() != b.length()) return false;
|
||||||
|
int diff = 0;
|
||||||
|
for (int i = 0; i < a.length(); i++) {
|
||||||
|
diff |= a.charAt(i) ^ b.charAt(i);
|
||||||
|
}
|
||||||
|
return diff == 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.lions.dev.service;
|
package com.lions.dev.service;
|
||||||
|
|
||||||
|
import com.lions.dev.dto.events.NotificationEvent;
|
||||||
import com.lions.dev.dto.request.booking.ReservationCreateRequestDTO;
|
import com.lions.dev.dto.request.booking.ReservationCreateRequestDTO;
|
||||||
import com.lions.dev.dto.response.booking.ReservationResponseDTO;
|
import com.lions.dev.dto.response.booking.ReservationResponseDTO;
|
||||||
import com.lions.dev.entity.booking.Booking;
|
import com.lions.dev.entity.booking.Booking;
|
||||||
@@ -11,22 +12,35 @@ import com.lions.dev.repository.UsersRepository;
|
|||||||
import jakarta.enterprise.context.ApplicationScoped;
|
import jakarta.enterprise.context.ApplicationScoped;
|
||||||
import jakarta.inject.Inject;
|
import jakarta.inject.Inject;
|
||||||
import jakarta.transaction.Transactional;
|
import jakarta.transaction.Transactional;
|
||||||
|
import org.eclipse.microprofile.reactive.messaging.Channel;
|
||||||
|
import org.eclipse.microprofile.reactive.messaging.Emitter;
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@ApplicationScoped
|
@ApplicationScoped
|
||||||
public class BookingService {
|
public class BookingService {
|
||||||
|
|
||||||
|
private static final Logger LOG = Logger.getLogger(BookingService.class);
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
BookingRepository bookingRepository;
|
BookingRepository bookingRepository;
|
||||||
@Inject
|
@Inject
|
||||||
EstablishmentRepository establishmentRepository;
|
EstablishmentRepository establishmentRepository;
|
||||||
@Inject
|
@Inject
|
||||||
UsersRepository usersRepository;
|
UsersRepository usersRepository;
|
||||||
|
@Inject
|
||||||
|
EmailService emailService;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
@Channel("notifications")
|
||||||
|
Emitter<NotificationEvent> notificationEmitter;
|
||||||
|
|
||||||
private static final DateTimeFormatter ISO = DateTimeFormatter.ISO_DATE_TIME;
|
private static final DateTimeFormatter ISO = DateTimeFormatter.ISO_DATE_TIME;
|
||||||
|
|
||||||
@@ -58,18 +72,121 @@ public class BookingService {
|
|||||||
booking.setStatus("PENDING");
|
booking.setStatus("PENDING");
|
||||||
booking.setSpecialRequests(dto.getNotes());
|
booking.setSpecialRequests(dto.getNotes());
|
||||||
bookingRepository.persist(booking);
|
bookingRepository.persist(booking);
|
||||||
|
|
||||||
|
try {
|
||||||
|
emailService.sendBookingConfirmationEmail(
|
||||||
|
user.getEmail(),
|
||||||
|
user.getFirstName(),
|
||||||
|
establishment.getName(),
|
||||||
|
booking.getReservationTime(),
|
||||||
|
booking.getGuestCount() != null ? booking.getGuestCount() : 1
|
||||||
|
);
|
||||||
|
} catch (Exception e) {
|
||||||
|
// Ne pas faire échouer la réservation si l'email échoue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notifier le manager en temps réel
|
||||||
|
notifyManagerOfNewBooking(booking, establishment, user);
|
||||||
|
|
||||||
return new ReservationResponseDTO(booking);
|
return new ReservationResponseDTO(booking);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notifie le manager d'un établissement d'une nouvelle réservation.
|
||||||
|
*/
|
||||||
|
private void notifyManagerOfNewBooking(Booking booking, Establishment establishment, Users customer) {
|
||||||
|
try {
|
||||||
|
Users manager = establishment.getManager();
|
||||||
|
if (manager == null) return;
|
||||||
|
|
||||||
|
String customerName = customer.getFirstName() + " " + customer.getLastName();
|
||||||
|
String dateStr = booking.getReservationTime() != null
|
||||||
|
? booking.getReservationTime().format(DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm"))
|
||||||
|
: "";
|
||||||
|
|
||||||
|
Map<String, Object> data = new HashMap<>();
|
||||||
|
data.put("bookingId", booking.getId().toString());
|
||||||
|
data.put("establishmentId", establishment.getId().toString());
|
||||||
|
data.put("establishmentName", establishment.getName());
|
||||||
|
data.put("customerId", customer.getId().toString());
|
||||||
|
data.put("customerName", customerName);
|
||||||
|
data.put("customerEmail", customer.getEmail());
|
||||||
|
data.put("reservationTime", dateStr);
|
||||||
|
data.put("guestCount", booking.getGuestCount());
|
||||||
|
data.put("specialRequests", booking.getSpecialRequests());
|
||||||
|
data.put("status", booking.getStatus());
|
||||||
|
|
||||||
|
NotificationEvent event = new NotificationEvent(
|
||||||
|
manager.getId().toString(),
|
||||||
|
"booking_created",
|
||||||
|
data
|
||||||
|
);
|
||||||
|
|
||||||
|
notificationEmitter.send(event);
|
||||||
|
LOG.debug("[BookingService] Notification nouvelle réservation envoyée au manager : " + manager.getId());
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOG.warn("[BookingService] Échec notification Kafka réservation (non bloquant) : " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
public ReservationResponseDTO cancelReservation(UUID id) {
|
public ReservationResponseDTO cancelReservation(UUID id) {
|
||||||
Booking booking = bookingRepository.findById(id);
|
Booking booking = bookingRepository.findById(id);
|
||||||
if (booking == null) return null;
|
if (booking == null) return null;
|
||||||
booking.setStatus("CANCELLED");
|
booking.setStatus("CANCELLED");
|
||||||
bookingRepository.persist(booking);
|
bookingRepository.persist(booking);
|
||||||
|
|
||||||
|
// Notifier l'utilisateur et le manager
|
||||||
|
notifyBookingCancellation(booking);
|
||||||
|
|
||||||
return new ReservationResponseDTO(booking);
|
return new ReservationResponseDTO(booking);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notifie l'utilisateur et le manager d'une annulation de réservation.
|
||||||
|
*/
|
||||||
|
private void notifyBookingCancellation(Booking booking) {
|
||||||
|
try {
|
||||||
|
Establishment establishment = booking.getEstablishment();
|
||||||
|
Users customer = booking.getUser();
|
||||||
|
if (establishment == null || customer == null) return;
|
||||||
|
|
||||||
|
String dateStr = booking.getReservationTime() != null
|
||||||
|
? booking.getReservationTime().format(DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm"))
|
||||||
|
: "";
|
||||||
|
|
||||||
|
Map<String, Object> data = new HashMap<>();
|
||||||
|
data.put("bookingId", booking.getId().toString());
|
||||||
|
data.put("establishmentName", establishment.getName());
|
||||||
|
data.put("reservationTime", dateStr);
|
||||||
|
data.put("status", "CANCELLED");
|
||||||
|
|
||||||
|
// Notifier le client
|
||||||
|
NotificationEvent customerEvent = new NotificationEvent(
|
||||||
|
customer.getId().toString(),
|
||||||
|
"booking_cancelled",
|
||||||
|
data
|
||||||
|
);
|
||||||
|
notificationEmitter.send(customerEvent);
|
||||||
|
|
||||||
|
// Notifier le manager
|
||||||
|
Users manager = establishment.getManager();
|
||||||
|
if (manager != null) {
|
||||||
|
data.put("customerName", customer.getFirstName() + " " + customer.getLastName());
|
||||||
|
NotificationEvent managerEvent = new NotificationEvent(
|
||||||
|
manager.getId().toString(),
|
||||||
|
"booking_cancelled",
|
||||||
|
data
|
||||||
|
);
|
||||||
|
notificationEmitter.send(managerEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG.debug("[BookingService] Notifications annulation envoyées");
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOG.warn("[BookingService] Échec notification annulation (non bloquant) : " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
public void deleteReservation(UUID id) {
|
public void deleteReservation(UUID id) {
|
||||||
bookingRepository.deleteById(id);
|
bookingRepository.deleteById(id);
|
||||||
|
|||||||
301
src/main/java/com/lions/dev/service/EmailService.java
Normal file
301
src/main/java/com/lions/dev/service/EmailService.java
Normal file
@@ -0,0 +1,301 @@
|
|||||||
|
package com.lions.dev.service;
|
||||||
|
|
||||||
|
import io.quarkus.mailer.Mail;
|
||||||
|
import io.quarkus.mailer.Mailer;
|
||||||
|
import jakarta.enterprise.context.ApplicationScoped;
|
||||||
|
import jakarta.inject.Inject;
|
||||||
|
import org.eclipse.microprofile.config.inject.ConfigProperty;
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service d'envoi d'emails pour l'application AfterWork.
|
||||||
|
*
|
||||||
|
* Gère l'envoi des emails de réinitialisation de mot de passe,
|
||||||
|
* notifications, et autres communications.
|
||||||
|
*/
|
||||||
|
@ApplicationScoped
|
||||||
|
public class EmailService {
|
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(EmailService.class);
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
Mailer mailer;
|
||||||
|
|
||||||
|
@ConfigProperty(name = "afterwork.app.name", defaultValue = "AfterWork")
|
||||||
|
String appName;
|
||||||
|
|
||||||
|
@ConfigProperty(name = "afterwork.app.url", defaultValue = "https://afterwork.lions.dev")
|
||||||
|
String appUrl;
|
||||||
|
|
||||||
|
@ConfigProperty(name = "afterwork.email.from", defaultValue = "noreply@lions.dev")
|
||||||
|
String fromEmail;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Envoie un email de réinitialisation de mot de passe.
|
||||||
|
*
|
||||||
|
* @param toEmail L'adresse email du destinataire
|
||||||
|
* @param firstName Le prénom de l'utilisateur
|
||||||
|
* @param resetToken Le token de réinitialisation
|
||||||
|
*/
|
||||||
|
public void sendPasswordResetEmail(String toEmail, String firstName, String resetToken) {
|
||||||
|
String resetLink = appUrl + "/reset-password?token=" + resetToken;
|
||||||
|
|
||||||
|
String subject = appName + " - Réinitialisation de votre mot de passe";
|
||||||
|
|
||||||
|
String htmlContent = buildPasswordResetHtml(firstName, resetLink);
|
||||||
|
String textContent = buildPasswordResetText(firstName, resetLink);
|
||||||
|
|
||||||
|
try {
|
||||||
|
mailer.send(
|
||||||
|
Mail.withHtml(toEmail, subject, htmlContent)
|
||||||
|
.setText(textContent)
|
||||||
|
);
|
||||||
|
logger.info("Email de réinitialisation envoyé à: " + toEmail);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("Erreur lors de l'envoi de l'email de réinitialisation à: " + toEmail, e);
|
||||||
|
throw new RuntimeException("Impossible d'envoyer l'email de réinitialisation", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construit le contenu HTML de l'email de réinitialisation.
|
||||||
|
*/
|
||||||
|
private String buildPasswordResetHtml(String firstName, String resetLink) {
|
||||||
|
return """
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<style>
|
||||||
|
body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; }
|
||||||
|
.container { max-width: 600px; margin: 0 auto; padding: 20px; }
|
||||||
|
.header { background: linear-gradient(135deg, #667eea 0%%, #764ba2 100%%); padding: 30px; text-align: center; border-radius: 10px 10px 0 0; }
|
||||||
|
.header h1 { color: white; margin: 0; }
|
||||||
|
.content { background: #f9f9f9; padding: 30px; border-radius: 0 0 10px 10px; }
|
||||||
|
.button { display: inline-block; background: #667eea; color: white; padding: 15px 30px; text-decoration: none; border-radius: 5px; margin: 20px 0; }
|
||||||
|
.footer { text-align: center; margin-top: 20px; color: #888; font-size: 12px; }
|
||||||
|
.warning { background: #fff3cd; border: 1px solid #ffc107; padding: 10px; border-radius: 5px; margin-top: 20px; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="header">
|
||||||
|
<h1>%s</h1>
|
||||||
|
</div>
|
||||||
|
<div class="content">
|
||||||
|
<h2>Bonjour %s,</h2>
|
||||||
|
<p>Vous avez demandé la réinitialisation de votre mot de passe.</p>
|
||||||
|
<p>Cliquez sur le bouton ci-dessous pour créer un nouveau mot de passe :</p>
|
||||||
|
<p style="text-align: center;">
|
||||||
|
<a href="%s" class="button">Réinitialiser mon mot de passe</a>
|
||||||
|
</p>
|
||||||
|
<div class="warning">
|
||||||
|
<strong>⚠️ Ce lien expire dans 1 heure.</strong>
|
||||||
|
<p>Si vous n'avez pas demandé cette réinitialisation, ignorez cet email.</p>
|
||||||
|
</div>
|
||||||
|
<p>Si le bouton ne fonctionne pas, copiez ce lien dans votre navigateur :</p>
|
||||||
|
<p style="word-break: break-all; color: #667eea;">%s</p>
|
||||||
|
</div>
|
||||||
|
<div class="footer">
|
||||||
|
<p>© 2025 %s - Tous droits réservés</p>
|
||||||
|
<p>Cet email a été envoyé automatiquement, merci de ne pas y répondre.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
""".formatted(appName, firstName, resetLink, resetLink, appName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construit le contenu texte de l'email de réinitialisation (fallback).
|
||||||
|
*/
|
||||||
|
private String buildPasswordResetText(String firstName, String resetLink) {
|
||||||
|
return """
|
||||||
|
Bonjour %s,
|
||||||
|
|
||||||
|
Vous avez demandé la réinitialisation de votre mot de passe sur %s.
|
||||||
|
|
||||||
|
Pour créer un nouveau mot de passe, cliquez sur ce lien :
|
||||||
|
%s
|
||||||
|
|
||||||
|
⚠️ Ce lien expire dans 1 heure.
|
||||||
|
|
||||||
|
Si vous n'avez pas demandé cette réinitialisation, ignorez simplement cet email.
|
||||||
|
|
||||||
|
Cordialement,
|
||||||
|
L'équipe %s
|
||||||
|
""".formatted(firstName, appName, resetLink, appName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Envoie un email de bienvenue après inscription.
|
||||||
|
*
|
||||||
|
* @param toEmail L'adresse email du destinataire
|
||||||
|
* @param firstName Le prénom de l'utilisateur
|
||||||
|
*/
|
||||||
|
public void sendWelcomeEmail(String toEmail, String firstName) {
|
||||||
|
String subject = "Bienvenue sur " + appName + " !";
|
||||||
|
|
||||||
|
String htmlContent = """
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<style>
|
||||||
|
body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; }
|
||||||
|
.container { max-width: 600px; margin: 0 auto; padding: 20px; }
|
||||||
|
.header { background: linear-gradient(135deg, #667eea 0%%, #764ba2 100%%); padding: 30px; text-align: center; border-radius: 10px 10px 0 0; }
|
||||||
|
.header h1 { color: white; margin: 0; }
|
||||||
|
.content { background: #f9f9f9; padding: 30px; border-radius: 0 0 10px 10px; }
|
||||||
|
.button { display: inline-block; background: #667eea; color: white; padding: 15px 30px; text-decoration: none; border-radius: 5px; margin: 20px 0; }
|
||||||
|
.footer { text-align: center; margin-top: 20px; color: #888; font-size: 12px; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="header">
|
||||||
|
<h1>Bienvenue sur %s !</h1>
|
||||||
|
</div>
|
||||||
|
<div class="content">
|
||||||
|
<h2>Bonjour %s,</h2>
|
||||||
|
<p>Nous sommes ravis de vous accueillir sur %s !</p>
|
||||||
|
<p>Découvrez les meilleurs événements et établissements près de chez vous.</p>
|
||||||
|
<p style="text-align: center;">
|
||||||
|
<a href="%s" class="button">Découvrir l'application</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="footer">
|
||||||
|
<p>© 2025 %s - Tous droits réservés</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
""".formatted(appName, firstName, appName, appUrl, appName);
|
||||||
|
|
||||||
|
try {
|
||||||
|
mailer.send(Mail.withHtml(toEmail, subject, htmlContent));
|
||||||
|
logger.info("Email de bienvenue envoyé à: " + toEmail);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("Erreur lors de l'envoi de l'email de bienvenue à: " + toEmail, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Confirmation de paiement / facture (abonnement établissement Wave).
|
||||||
|
*/
|
||||||
|
public void sendPaymentConfirmationEmail(String toEmail, String firstName, String establishmentName, int amountXof, String plan) {
|
||||||
|
String subject = appName + " - Confirmation de paiement - " + establishmentName;
|
||||||
|
String amount = String.format("%d XOF", amountXof);
|
||||||
|
String planLabel = "MONTHLY".equalsIgnoreCase(plan) ? "Mensuel" : "Annuel";
|
||||||
|
String html = buildTransactionalHtml(
|
||||||
|
"Paiement confirmé",
|
||||||
|
"Bonjour " + firstName + ",",
|
||||||
|
"Votre paiement pour l'abonnement <strong>" + planLabel + "</strong> de l'établissement <strong>" + establishmentName + "</strong> a bien été enregistré.",
|
||||||
|
"Montant : " + amount + ".",
|
||||||
|
"Votre établissement est actif sur " + appName + "."
|
||||||
|
);
|
||||||
|
sendSafe(toEmail, subject, html, "confirmation paiement");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rappel d'événement (J-1 ou H-1).
|
||||||
|
*/
|
||||||
|
public void sendEventReminderEmail(String toEmail, String firstName, String eventTitle, java.time.LocalDateTime startDate) {
|
||||||
|
String subject = appName + " - Rappel : " + eventTitle;
|
||||||
|
String dateStr = startDate != null ? startDate.format(java.time.format.DateTimeFormatter.ofPattern("EEEE d MMMM à HH'h'mm")) : "";
|
||||||
|
String html = buildTransactionalHtml(
|
||||||
|
"Rappel événement",
|
||||||
|
"Bonjour " + firstName + ",",
|
||||||
|
"L'événement <strong>" + eventTitle + "</strong> commence bientôt.",
|
||||||
|
"Date et heure : " + dateStr + ".",
|
||||||
|
"Rendez-vous sur l'application pour plus de détails."
|
||||||
|
);
|
||||||
|
sendSafe(toEmail, subject, html, "rappel événement");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Avertissement expiration abonnement (envoi J-3 avant expiration).
|
||||||
|
*/
|
||||||
|
public void sendSubscriptionExpirationWarningEmail(String toEmail, String firstName, String establishmentName, java.time.LocalDateTime expiresAt) {
|
||||||
|
String subject = appName + " - Votre abonnement expire bientôt - " + establishmentName;
|
||||||
|
String dateStr = expiresAt != null ? expiresAt.format(java.time.format.DateTimeFormatter.ofPattern("d MMMM yyyy")) : "";
|
||||||
|
String html = buildTransactionalHtml(
|
||||||
|
"Abonnement bientôt expiré",
|
||||||
|
"Bonjour " + firstName + ",",
|
||||||
|
"L'abonnement de votre établissement <strong>" + establishmentName + "</strong> expire le <strong>" + dateStr + "</strong>.",
|
||||||
|
"Renouvelez votre abonnement pour continuer à bénéficier des fonctionnalités.",
|
||||||
|
"<a href=\"" + appUrl + "\" style=\"color:#667eea;\">Renouveler sur " + appName + "</a>"
|
||||||
|
);
|
||||||
|
sendSafe(toEmail, subject, html, "expiration abonnement");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Confirmation de réservation (établissement).
|
||||||
|
*/
|
||||||
|
public void sendBookingConfirmationEmail(String toEmail, String firstName, String establishmentName, java.time.LocalDateTime reservationTime, int guestCount) {
|
||||||
|
String subject = appName + " - Confirmation de réservation - " + establishmentName;
|
||||||
|
String dateStr = reservationTime != null ? reservationTime.format(java.time.format.DateTimeFormatter.ofPattern("EEEE d MMMM à HH'h'mm")) : "";
|
||||||
|
String html = buildTransactionalHtml(
|
||||||
|
"Réservation confirmée",
|
||||||
|
"Bonjour " + firstName + ",",
|
||||||
|
"Votre réservation chez <strong>" + establishmentName + "</strong> est confirmée.",
|
||||||
|
"Date et heure : " + dateStr + ". Nombre de convives : " + guestCount + ".",
|
||||||
|
"À bientôt !"
|
||||||
|
);
|
||||||
|
sendSafe(toEmail, subject, html, "confirmation réservation");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notification échec paiement Wave (destiné au manager).
|
||||||
|
*/
|
||||||
|
public void sendPaymentFailureEmail(String toEmail, String firstName, String establishmentName) {
|
||||||
|
String subject = appName + " - Échec du paiement - " + establishmentName;
|
||||||
|
String html = buildTransactionalHtml(
|
||||||
|
"Échec du paiement",
|
||||||
|
"Bonjour " + firstName + ",",
|
||||||
|
"Le paiement pour l'établissement <strong>" + establishmentName + "</strong> n'a pas abouti.",
|
||||||
|
"Veuillez réessayer ou contacter le support si le problème persiste.",
|
||||||
|
"<a href=\"" + appUrl + "\" style=\"color:#667eea;\">Réessayer sur " + appName + "</a>"
|
||||||
|
);
|
||||||
|
sendSafe(toEmail, subject, html, "échec paiement");
|
||||||
|
}
|
||||||
|
|
||||||
|
private String buildTransactionalHtml(String title, String greeting, String paragraph1, String paragraph2, String paragraph3) {
|
||||||
|
return """
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head><meta charset="UTF-8">
|
||||||
|
<style>
|
||||||
|
body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; }
|
||||||
|
.container { max-width: 600px; margin: 0 auto; padding: 20px; }
|
||||||
|
.header { background: linear-gradient(135deg, #667eea 0%%, #764ba2 100%%); padding: 20px; text-align: center; border-radius: 10px 10px 0 0; }
|
||||||
|
.header h1 { color: white; margin: 0; font-size: 1.3em; }
|
||||||
|
.content { background: #f9f9f9; padding: 25px; border-radius: 0 0 10px 10px; }
|
||||||
|
.footer { text-align: center; margin-top: 15px; color: #888; font-size: 12px; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="header"><h1>%s</h1></div>
|
||||||
|
<div class="content">
|
||||||
|
<p>%s</p>
|
||||||
|
<p>%s</p>
|
||||||
|
<p>%s</p>
|
||||||
|
<p>%s</p>
|
||||||
|
</div>
|
||||||
|
<div class="footer">© 2025 %s</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
""".formatted(title, greeting, paragraph1, paragraph2, paragraph3, appName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendSafe(String toEmail, String subject, String htmlContent, String logLabel) {
|
||||||
|
try {
|
||||||
|
mailer.send(Mail.withHtml(toEmail, subject, htmlContent));
|
||||||
|
logger.info("Email " + logLabel + " envoyé à: " + toEmail);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("Erreur envoi email " + logLabel + " à " + toEmail, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -31,6 +31,9 @@ public class EstablishmentRatingService {
|
|||||||
@Inject
|
@Inject
|
||||||
UsersRepository usersRepository;
|
UsersRepository usersRepository;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
com.lions.dev.service.NotificationService notificationService;
|
||||||
|
|
||||||
private static final Logger LOG = Logger.getLogger(EstablishmentRatingService.class);
|
private static final Logger LOG = Logger.getLogger(EstablishmentRatingService.class);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -74,6 +77,24 @@ public class EstablishmentRatingService {
|
|||||||
// Mettre à jour les statistiques de l'établissement
|
// Mettre à jour les statistiques de l'établissement
|
||||||
updateEstablishmentRatingStats(establishmentId);
|
updateEstablishmentRatingStats(establishmentId);
|
||||||
|
|
||||||
|
// Notification au manager de l'établissement (déclencheur automatique)
|
||||||
|
try {
|
||||||
|
Users manager = establishment.getManager();
|
||||||
|
if (manager != null && !manager.getId().equals(userId)) {
|
||||||
|
String raterName = user.getFirstName() + " " + user.getLastName();
|
||||||
|
notificationService.createNotification(
|
||||||
|
"Nouvelle note",
|
||||||
|
raterName + " a noté votre établissement « " + establishment.getName() + " » (" + requestDTO.getRating() + "/5)",
|
||||||
|
"rating",
|
||||||
|
manager.getId(),
|
||||||
|
null
|
||||||
|
);
|
||||||
|
LOG.info("Notification nouvelle note envoyée au manager : " + manager.getId());
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOG.error("Erreur création notification note établissement : " + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
LOG.info("Note soumise avec succès : " + rating.getId());
|
LOG.info("Note soumise avec succès : " + rating.getId());
|
||||||
return rating;
|
return rating;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import java.nio.file.Files;
|
|||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.nio.file.StandardCopyOption;
|
import java.nio.file.StandardCopyOption;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -64,5 +66,63 @@ public class FileService {
|
|||||||
|
|
||||||
return destinationPath;
|
return destinationPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Génère une miniature (thumbnail) à partir d'un fichier vidéo via FFmpeg.
|
||||||
|
* Extrait une frame à 1 seconde. Si FFmpeg n'est pas disponible ou en cas d'erreur,
|
||||||
|
* retourne Optional.empty() sans lever d'exception.
|
||||||
|
*
|
||||||
|
* @param videoPath Chemin absolu du fichier vidéo.
|
||||||
|
* @return Chemin du fichier JPEG généré, ou empty si indisponible.
|
||||||
|
*/
|
||||||
|
public Optional<Path> generateVideoThumbnail(Path videoPath) {
|
||||||
|
if (videoPath == null || !Files.exists(videoPath)) {
|
||||||
|
LOG.warnf("[WARN] Génération thumbnail : fichier vidéo absent ou invalide : %s", videoPath);
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
String baseName = getBaseNameWithoutExtension(videoPath.getFileName().toString());
|
||||||
|
Path parentDir = videoPath.getParent();
|
||||||
|
String thumbFileName = baseName + "_thumb.jpg";
|
||||||
|
Path thumbPath = parentDir != null ? parentDir.resolve(thumbFileName) : Paths.get(thumbFileName);
|
||||||
|
ProcessBuilder pb = new ProcessBuilder(
|
||||||
|
"ffmpeg",
|
||||||
|
"-y",
|
||||||
|
"-i", videoPath.toAbsolutePath().toString(),
|
||||||
|
"-ss", "00:00:01",
|
||||||
|
"-vframes", "1",
|
||||||
|
"-q:v", "2",
|
||||||
|
"-f", "image2",
|
||||||
|
thumbPath.toAbsolutePath().toString()
|
||||||
|
);
|
||||||
|
pb.redirectErrorStream(true);
|
||||||
|
try {
|
||||||
|
Process process = pb.start();
|
||||||
|
boolean finished = process.waitFor(30, TimeUnit.SECONDS);
|
||||||
|
if (!finished) {
|
||||||
|
process.destroyForcibly();
|
||||||
|
LOG.warnf("[WARN] Génération thumbnail : timeout FFmpeg pour %s", videoPath);
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
if (process.exitValue() != 0) {
|
||||||
|
LOG.warnf("[WARN] Génération thumbnail : FFmpeg a échoué (code %d) pour %s", process.exitValue(), videoPath);
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
if (Files.exists(thumbPath)) {
|
||||||
|
LOG.infof("[LOG] Thumbnail vidéo généré : %s", thumbPath);
|
||||||
|
return Optional.of(thumbPath);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOG.warnf("[WARN] FFmpeg non disponible ou erreur I/O pour thumbnail : %s", e.getMessage());
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
LOG.warnf("[WARN] Génération thumbnail interrompue pour %s", videoPath);
|
||||||
|
}
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getBaseNameWithoutExtension(String fileName) {
|
||||||
|
if (fileName == null) return "video";
|
||||||
|
int lastDot = fileName.lastIndexOf('.');
|
||||||
|
return lastDot > 0 ? fileName.substring(0, lastDot) : fileName;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -90,6 +90,21 @@ public class FriendshipService {
|
|||||||
Friendship friendship = new Friendship(user, friend, FriendshipStatus.PENDING);
|
Friendship friendship = new Friendship(user, friend, FriendshipStatus.PENDING);
|
||||||
friendshipRepository.persist(friendship);
|
friendshipRepository.persist(friendship);
|
||||||
|
|
||||||
|
// Notification en base pour le destinataire (déclencheur automatique)
|
||||||
|
try {
|
||||||
|
String senderName = user.getFirstName() + " " + user.getLastName();
|
||||||
|
notificationService.createNotification(
|
||||||
|
"Demande d'amitié",
|
||||||
|
senderName + " vous a envoyé une demande d'amitié",
|
||||||
|
"friend",
|
||||||
|
friend.getId(),
|
||||||
|
null
|
||||||
|
);
|
||||||
|
logger.info("[LOG] Notification demande d'amitié créée pour : " + friend.getId());
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("[ERROR] Erreur création notification demande d'amitié : " + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
// TEMPS RÉEL: Publier dans Kafka (v2.0)
|
// TEMPS RÉEL: Publier dans Kafka (v2.0)
|
||||||
try {
|
try {
|
||||||
Map<String, Object> notificationData = new HashMap<>();
|
Map<String, Object> notificationData = new HashMap<>();
|
||||||
@@ -113,7 +128,8 @@ public class FriendshipService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
logger.info("[LOG] Demande d'amitié envoyée avec succès.");
|
logger.info("[LOG] Demande d'amitié envoyée avec succès.");
|
||||||
return new FriendshipCreateOneResponseDTO(friendship);
|
// Construire le DTO avec les IDs déjà chargés pour éviter LazyInitializationException
|
||||||
|
return new FriendshipCreateOneResponseDTO(friendship, user.getId(), friend.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -209,8 +225,10 @@ public class FriendshipService {
|
|||||||
logger.error("[ERROR] Erreur lors de la création des notifications d'amitié : " + e.getMessage());
|
logger.error("[ERROR] Erreur lors de la création des notifications d'amitié : " + e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retourner la réponse avec les informations de la relation d'amitié
|
// Retourner la réponse avec les IDs déjà chargés (évite LazyInitializationException)
|
||||||
return new FriendshipCreateOneResponseDTO(friendship);
|
Users user = friendship.getUser();
|
||||||
|
Users friendUser = friendship.getFriend();
|
||||||
|
return new FriendshipCreateOneResponseDTO(friendship, user.getId(), friendUser.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import io.smallrye.reactive.messaging.kafka.api.OutgoingKafkaRecordMetadata;
|
|||||||
import jakarta.enterprise.context.ApplicationScoped;
|
import jakarta.enterprise.context.ApplicationScoped;
|
||||||
import jakarta.inject.Inject;
|
import jakarta.inject.Inject;
|
||||||
import jakarta.transaction.Transactional;
|
import jakarta.transaction.Transactional;
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -25,12 +26,12 @@ import java.util.UUID;
|
|||||||
*
|
*
|
||||||
* Ce service contient la logique métier pour l'envoi de messages,
|
* Ce service contient la logique métier pour l'envoi de messages,
|
||||||
* la récupération de conversations, et la gestion des messages non lus.
|
* la récupération de conversations, et la gestion des messages non lus.
|
||||||
*
|
|
||||||
* Tous les logs nécessaires pour la traçabilité sont intégrés.
|
|
||||||
*/
|
*/
|
||||||
@ApplicationScoped
|
@ApplicationScoped
|
||||||
public class MessageService {
|
public class MessageService {
|
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(MessageService.class);
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
MessageRepository messageRepository;
|
MessageRepository messageRepository;
|
||||||
|
|
||||||
@@ -65,7 +66,7 @@ public class MessageService {
|
|||||||
String content,
|
String content,
|
||||||
String messageType,
|
String messageType,
|
||||||
String mediaUrl) {
|
String mediaUrl) {
|
||||||
System.out.println("[LOG] Envoi de message de " + senderId + " à " + recipientId);
|
logger.info("[MessageService] Envoi de message de " + senderId + " à " + recipientId);
|
||||||
|
|
||||||
// Récupérer les utilisateurs
|
// Récupérer les utilisateurs
|
||||||
Users sender = usersRepository.findById(senderId);
|
Users sender = usersRepository.findById(senderId);
|
||||||
@@ -91,7 +92,7 @@ public class MessageService {
|
|||||||
|
|
||||||
// Persister le message
|
// Persister le message
|
||||||
messageRepository.persist(message);
|
messageRepository.persist(message);
|
||||||
System.out.println("[LOG] Message créé avec l'ID : " + message.getId());
|
logger.info("[MessageService] Message créé avec l'ID : " + message.getId());
|
||||||
|
|
||||||
// Mettre à jour la conversation
|
// Mettre à jour la conversation
|
||||||
conversation.updateLastMessage(message);
|
conversation.updateLastMessage(message);
|
||||||
@@ -112,9 +113,9 @@ public class MessageService {
|
|||||||
recipientId,
|
recipientId,
|
||||||
null
|
null
|
||||||
);
|
);
|
||||||
System.out.println("[LOG] Notification créée pour le destinataire");
|
logger.info("[MessageService] Notification créée pour le destinataire");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
System.out.println("[ERROR] Erreur lors de la création de la notification : " + e.getMessage());
|
logger.error("[MessageService] Erreur lors de la création de la notification : " + e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
// TEMPS RÉEL : Publier dans Kafka (v2.0)
|
// TEMPS RÉEL : Publier dans Kafka (v2.0)
|
||||||
@@ -149,12 +150,12 @@ public class MessageService {
|
|||||||
event,
|
event,
|
||||||
() -> java.util.concurrent.CompletableFuture.completedFuture(null), // ack
|
() -> java.util.concurrent.CompletableFuture.completedFuture(null), // ack
|
||||||
throwable -> {
|
throwable -> {
|
||||||
System.out.println("[ERROR] Erreur envoi Kafka: " + throwable.getMessage());
|
logger.error("[MessageService] Erreur envoi Kafka: " + throwable.getMessage());
|
||||||
return java.util.concurrent.CompletableFuture.completedFuture(null); // nack
|
return java.util.concurrent.CompletableFuture.completedFuture(null); // nack
|
||||||
}
|
}
|
||||||
).addMetadata(kafkaMetadata));
|
).addMetadata(kafkaMetadata));
|
||||||
|
|
||||||
System.out.println("[LOG] Message publié dans Kafka: " + message.getId());
|
logger.info("[MessageService] Message publié dans Kafka: " + message.getId());
|
||||||
|
|
||||||
// Envoyer confirmation de délivrance à l'expéditeur (via Kafka aussi)
|
// Envoyer confirmation de délivrance à l'expéditeur (via Kafka aussi)
|
||||||
try {
|
try {
|
||||||
@@ -181,9 +182,9 @@ public class MessageService {
|
|||||||
throwable -> java.util.concurrent.CompletableFuture.completedFuture(null)
|
throwable -> java.util.concurrent.CompletableFuture.completedFuture(null)
|
||||||
).addMetadata(deliveryKafkaMetadata));
|
).addMetadata(deliveryKafkaMetadata));
|
||||||
|
|
||||||
System.out.println("[LOG] Confirmation de délivrance publiée dans Kafka pour : " + senderId);
|
logger.info("[MessageService] Confirmation de délivrance publiée dans Kafka pour : " + senderId);
|
||||||
} catch (Exception deliveryEx) {
|
} catch (Exception deliveryEx) {
|
||||||
System.out.println("[ERROR] Erreur publication confirmation délivrance : " + deliveryEx.getMessage());
|
logger.error("[MessageService] Erreur publication confirmation délivrance : " + deliveryEx.getMessage());
|
||||||
// Ne pas bloquer si la confirmation échoue
|
// Ne pas bloquer si la confirmation échoue
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@@ -202,7 +203,7 @@ public class MessageService {
|
|||||||
* @throws UserNotFoundException Si l'utilisateur n'existe pas
|
* @throws UserNotFoundException Si l'utilisateur n'existe pas
|
||||||
*/
|
*/
|
||||||
public List<Conversation> getUserConversations(UUID userId) {
|
public List<Conversation> getUserConversations(UUID userId) {
|
||||||
System.out.println("[LOG] Récupération des conversations pour l'utilisateur ID : " + userId);
|
logger.info("[MessageService] Récupération des conversations pour l'utilisateur ID : " + userId);
|
||||||
|
|
||||||
Users user = usersRepository.findById(userId);
|
Users user = usersRepository.findById(userId);
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
@@ -221,7 +222,7 @@ public class MessageService {
|
|||||||
* @return Liste paginée des messages
|
* @return Liste paginée des messages
|
||||||
*/
|
*/
|
||||||
public List<Message> getConversationMessages(UUID conversationId, int page, int size) {
|
public List<Message> getConversationMessages(UUID conversationId, int page, int size) {
|
||||||
System.out.println("[LOG] Récupération des messages pour la conversation ID : " + conversationId);
|
logger.info("[MessageService] Récupération des messages pour la conversation ID : " + conversationId);
|
||||||
return messageRepository.findByConversationId(conversationId, page, size);
|
return messageRepository.findByConversationId(conversationId, page, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -232,7 +233,7 @@ public class MessageService {
|
|||||||
* @return La conversation
|
* @return La conversation
|
||||||
*/
|
*/
|
||||||
public Conversation getConversation(UUID conversationId) {
|
public Conversation getConversation(UUID conversationId) {
|
||||||
System.out.println("[LOG] Récupération de la conversation ID : " + conversationId);
|
logger.info("[MessageService] Récupération de la conversation ID : " + conversationId);
|
||||||
return conversationRepository.findById(conversationId);
|
return conversationRepository.findById(conversationId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -245,7 +246,7 @@ public class MessageService {
|
|||||||
* @throws UserNotFoundException Si l'un des utilisateurs n'existe pas
|
* @throws UserNotFoundException Si l'un des utilisateurs n'existe pas
|
||||||
*/
|
*/
|
||||||
public Conversation getConversationBetweenUsers(UUID user1Id, UUID user2Id) {
|
public Conversation getConversationBetweenUsers(UUID user1Id, UUID user2Id) {
|
||||||
System.out.println("[LOG] Recherche de conversation entre " + user1Id + " et " + user2Id);
|
logger.info("[MessageService] Recherche de conversation entre " + user1Id + " et " + user2Id);
|
||||||
|
|
||||||
Users user1 = usersRepository.findById(user1Id);
|
Users user1 = usersRepository.findById(user1Id);
|
||||||
Users user2 = usersRepository.findById(user2Id);
|
Users user2 = usersRepository.findById(user2Id);
|
||||||
@@ -265,7 +266,7 @@ public class MessageService {
|
|||||||
*/
|
*/
|
||||||
@Transactional
|
@Transactional
|
||||||
public Message markMessageAsRead(UUID messageId) {
|
public Message markMessageAsRead(UUID messageId) {
|
||||||
System.out.println("[LOG] Marquage du message comme lu : " + messageId);
|
logger.info("[MessageService] Marquage du message comme lu : " + messageId);
|
||||||
|
|
||||||
Message message = messageRepository.findById(messageId);
|
Message message = messageRepository.findById(messageId);
|
||||||
if (message == null) {
|
if (message == null) {
|
||||||
@@ -309,12 +310,12 @@ public class MessageService {
|
|||||||
throwable -> java.util.concurrent.CompletableFuture.completedFuture(null)
|
throwable -> java.util.concurrent.CompletableFuture.completedFuture(null)
|
||||||
).addMetadata(readKafkaMetadata));
|
).addMetadata(readKafkaMetadata));
|
||||||
|
|
||||||
System.out.println("[LOG] Confirmation de lecture publiée dans Kafka pour : " + message.getSender().getId());
|
logger.info("[MessageService] Confirmation de lecture publiée dans Kafka pour : " + message.getSender().getId());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
System.out.println("[ERROR] Erreur publication confirmation lecture : " + e.getMessage());
|
logger.error("[MessageService] Erreur publication confirmation lecture : " + e.getMessage());
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
System.out.println("[ERROR] Erreur envoi confirmation lecture : " + e.getMessage());
|
logger.error("[MessageService] Erreur envoi confirmation lecture : " + e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
return message;
|
return message;
|
||||||
@@ -329,7 +330,7 @@ public class MessageService {
|
|||||||
*/
|
*/
|
||||||
@Transactional
|
@Transactional
|
||||||
public int markAllMessagesAsRead(UUID conversationId, UUID userId) {
|
public int markAllMessagesAsRead(UUID conversationId, UUID userId) {
|
||||||
System.out.println("[LOG] Marquage de tous les messages comme lus pour la conversation " + conversationId);
|
logger.info("[MessageService] Marquage de tous les messages comme lus pour la conversation " + conversationId);
|
||||||
|
|
||||||
Conversation conversation = conversationRepository.findById(conversationId);
|
Conversation conversation = conversationRepository.findById(conversationId);
|
||||||
if (conversation == null) {
|
if (conversation == null) {
|
||||||
@@ -358,7 +359,7 @@ public class MessageService {
|
|||||||
* @return Le nombre de messages non lus
|
* @return Le nombre de messages non lus
|
||||||
*/
|
*/
|
||||||
public long getTotalUnreadCount(UUID userId) {
|
public long getTotalUnreadCount(UUID userId) {
|
||||||
System.out.println("[LOG] Récupération du nombre total de messages non lus pour l'utilisateur " + userId);
|
logger.info("[MessageService] Récupération du nombre total de messages non lus pour l'utilisateur " + userId);
|
||||||
return conversationRepository.countTotalUnreadMessages(userId);
|
return conversationRepository.countTotalUnreadMessages(userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -370,7 +371,7 @@ public class MessageService {
|
|||||||
*/
|
*/
|
||||||
@Transactional
|
@Transactional
|
||||||
public boolean deleteMessage(UUID messageId) {
|
public boolean deleteMessage(UUID messageId) {
|
||||||
System.out.println("[LOG] Suppression du message ID : " + messageId);
|
logger.info("[MessageService] Suppression du message ID : " + messageId);
|
||||||
|
|
||||||
Message message = messageRepository.findById(messageId);
|
Message message = messageRepository.findById(messageId);
|
||||||
if (message == null) {
|
if (message == null) {
|
||||||
@@ -389,7 +390,7 @@ public class MessageService {
|
|||||||
*/
|
*/
|
||||||
@Transactional
|
@Transactional
|
||||||
public boolean deleteConversation(UUID conversationId) {
|
public boolean deleteConversation(UUID conversationId) {
|
||||||
System.out.println("[LOG] Suppression de la conversation ID : " + conversationId);
|
logger.info("[MessageService] Suppression de la conversation ID : " + conversationId);
|
||||||
|
|
||||||
// Supprimer d'abord tous les messages
|
// Supprimer d'abord tous les messages
|
||||||
messageRepository.deleteByConversationId(conversationId);
|
messageRepository.deleteByConversationId(conversationId);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.lions.dev.service;
|
package com.lions.dev.service;
|
||||||
|
|
||||||
|
import com.lions.dev.dto.events.NotificationEvent;
|
||||||
import com.lions.dev.entity.events.Events;
|
import com.lions.dev.entity.events.Events;
|
||||||
import com.lions.dev.entity.notification.Notification;
|
import com.lions.dev.entity.notification.Notification;
|
||||||
import com.lions.dev.entity.users.Users;
|
import com.lions.dev.entity.users.Users;
|
||||||
@@ -10,20 +11,25 @@ import com.lions.dev.repository.UsersRepository;
|
|||||||
import jakarta.enterprise.context.ApplicationScoped;
|
import jakarta.enterprise.context.ApplicationScoped;
|
||||||
import jakarta.inject.Inject;
|
import jakarta.inject.Inject;
|
||||||
import jakarta.transaction.Transactional;
|
import jakarta.transaction.Transactional;
|
||||||
|
import org.eclipse.microprofile.reactive.messaging.Channel;
|
||||||
|
import org.eclipse.microprofile.reactive.messaging.Emitter;
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Service de gestion des notifications.
|
* Service de gestion des notifications.
|
||||||
*
|
*
|
||||||
* Ce service contient la logique métier pour la création, récupération,
|
* Ce service contient la logique métier pour la création, récupération,
|
||||||
* mise à jour et suppression des notifications.
|
* mise à jour et suppression des notifications.
|
||||||
*
|
|
||||||
* Tous les logs nécessaires pour la traçabilité sont intégrés.
|
|
||||||
*/
|
*/
|
||||||
@ApplicationScoped
|
@ApplicationScoped
|
||||||
public class NotificationService {
|
public class NotificationService {
|
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(NotificationService.class);
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
NotificationRepository notificationRepository;
|
NotificationRepository notificationRepository;
|
||||||
|
|
||||||
@@ -33,6 +39,10 @@ public class NotificationService {
|
|||||||
@Inject
|
@Inject
|
||||||
EventsRepository eventsRepository;
|
EventsRepository eventsRepository;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
@Channel("notifications")
|
||||||
|
Emitter<NotificationEvent> notificationEmitter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Récupère toutes les notifications d'un utilisateur.
|
* Récupère toutes les notifications d'un utilisateur.
|
||||||
*
|
*
|
||||||
@@ -41,16 +51,16 @@ public class NotificationService {
|
|||||||
* @throws UserNotFoundException Si l'utilisateur n'existe pas
|
* @throws UserNotFoundException Si l'utilisateur n'existe pas
|
||||||
*/
|
*/
|
||||||
public List<Notification> getNotificationsByUserId(UUID userId) {
|
public List<Notification> getNotificationsByUserId(UUID userId) {
|
||||||
System.out.println("[LOG] Récupération des notifications pour l'utilisateur ID : " + userId);
|
logger.info("[NotificationService] Récupération des notifications pour l'utilisateur ID : " + userId);
|
||||||
|
|
||||||
Users user = usersRepository.findById(userId);
|
Users user = usersRepository.findById(userId);
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
System.out.println("[ERROR] Utilisateur non trouvé avec l'ID : " + userId);
|
logger.error("[NotificationService] Utilisateur non trouvé avec l'ID : " + userId);
|
||||||
throw new UserNotFoundException("Utilisateur non trouvé avec l'ID : " + userId);
|
throw new UserNotFoundException("Utilisateur non trouvé avec l'ID : " + userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Notification> notifications = notificationRepository.findByUserId(userId);
|
List<Notification> notifications = notificationRepository.findByUserId(userId);
|
||||||
System.out.println("[LOG] " + notifications.size() + " notification(s) récupérée(s) pour l'utilisateur ID : " + userId);
|
logger.info("[NotificationService] " + notifications.size() + " notification(s) récupérée(s) pour l'utilisateur ID : " + userId);
|
||||||
return notifications;
|
return notifications;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,11 +74,11 @@ public class NotificationService {
|
|||||||
* @throws UserNotFoundException Si l'utilisateur n'existe pas
|
* @throws UserNotFoundException Si l'utilisateur n'existe pas
|
||||||
*/
|
*/
|
||||||
public List<Notification> getNotificationsByUserIdWithPagination(UUID userId, int page, int size) {
|
public List<Notification> getNotificationsByUserIdWithPagination(UUID userId, int page, int size) {
|
||||||
System.out.println("[LOG] Récupération paginée des notifications pour l'utilisateur ID : " + userId);
|
logger.info("[NotificationService] Récupération paginée des notifications pour l'utilisateur ID : " + userId);
|
||||||
|
|
||||||
Users user = usersRepository.findById(userId);
|
Users user = usersRepository.findById(userId);
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
System.out.println("[ERROR] Utilisateur non trouvé avec l'ID : " + userId);
|
logger.error("[NotificationService] Utilisateur non trouvé avec l'ID : " + userId);
|
||||||
throw new UserNotFoundException("Utilisateur non trouvé avec l'ID : " + userId);
|
throw new UserNotFoundException("Utilisateur non trouvé avec l'ID : " + userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,11 +103,11 @@ public class NotificationService {
|
|||||||
String type,
|
String type,
|
||||||
UUID userId,
|
UUID userId,
|
||||||
UUID eventId) {
|
UUID eventId) {
|
||||||
System.out.println("[LOG] Création d'une notification : " + title + " pour l'utilisateur ID : " + userId);
|
logger.info("[NotificationService] Création d'une notification : " + title + " pour l'utilisateur ID : " + userId);
|
||||||
|
|
||||||
Users user = usersRepository.findById(userId);
|
Users user = usersRepository.findById(userId);
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
System.out.println("[ERROR] Utilisateur non trouvé avec l'ID : " + userId);
|
logger.error("[NotificationService] Utilisateur non trouvé avec l'ID : " + userId);
|
||||||
throw new UserNotFoundException("Utilisateur non trouvé avec l'ID : " + userId);
|
throw new UserNotFoundException("Utilisateur non trouvé avec l'ID : " + userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,15 +117,50 @@ public class NotificationService {
|
|||||||
Events event = eventsRepository.findById(eventId);
|
Events event = eventsRepository.findById(eventId);
|
||||||
if (event != null) {
|
if (event != null) {
|
||||||
notification.setEvent(event);
|
notification.setEvent(event);
|
||||||
System.out.println("[LOG] Notification associée à l'événement ID : " + eventId);
|
logger.info("[NotificationService] Notification associée à l'événement ID : " + eventId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
notificationRepository.persist(notification);
|
notificationRepository.persist(notification);
|
||||||
System.out.println("[LOG] Notification créée avec succès : " + notification.getId());
|
logger.info("[NotificationService] Notification créée avec succès : " + notification.getId());
|
||||||
|
|
||||||
|
// Publication temps réel via Kafka → WebSocket
|
||||||
|
publishToKafka(notification, user);
|
||||||
|
|
||||||
return notification;
|
return notification;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Publie une notification dans Kafka pour livraison temps réel via WebSocket.
|
||||||
|
*/
|
||||||
|
private void publishToKafka(Notification notification, Users user) {
|
||||||
|
try {
|
||||||
|
Map<String, Object> data = new HashMap<>();
|
||||||
|
data.put("notificationId", notification.getId().toString());
|
||||||
|
data.put("title", notification.getTitle());
|
||||||
|
data.put("message", notification.getMessage());
|
||||||
|
data.put("isRead", notification.isRead());
|
||||||
|
data.put("createdAt", notification.getCreatedAt() != null
|
||||||
|
? notification.getCreatedAt().toString() : null);
|
||||||
|
|
||||||
|
if (notification.getEvent() != null) {
|
||||||
|
data.put("eventId", notification.getEvent().getId().toString());
|
||||||
|
data.put("eventTitle", notification.getEvent().getTitle());
|
||||||
|
}
|
||||||
|
|
||||||
|
NotificationEvent event = new NotificationEvent(
|
||||||
|
user.getId().toString(),
|
||||||
|
notification.getType(),
|
||||||
|
data
|
||||||
|
);
|
||||||
|
|
||||||
|
notificationEmitter.send(event);
|
||||||
|
logger.debug("[NotificationService] Notification publiée dans Kafka pour temps réel : " + notification.getId());
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.warn("[NotificationService] Échec publication Kafka (non bloquant) : " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Marque une notification comme lue.
|
* Marque une notification comme lue.
|
||||||
*
|
*
|
||||||
@@ -124,17 +169,17 @@ public class NotificationService {
|
|||||||
*/
|
*/
|
||||||
@Transactional
|
@Transactional
|
||||||
public Notification markAsRead(UUID notificationId) {
|
public Notification markAsRead(UUID notificationId) {
|
||||||
System.out.println("[LOG] Marquage de la notification ID : " + notificationId + " comme lue");
|
logger.info("[NotificationService] Marquage de la notification ID : " + notificationId + " comme lue");
|
||||||
|
|
||||||
Notification notification = notificationRepository.findById(notificationId);
|
Notification notification = notificationRepository.findById(notificationId);
|
||||||
if (notification == null) {
|
if (notification == null) {
|
||||||
System.out.println("[ERROR] Notification non trouvée avec l'ID : " + notificationId);
|
logger.error("[NotificationService] Notification non trouvée avec l'ID : " + notificationId);
|
||||||
throw new IllegalArgumentException("Notification non trouvée avec l'ID : " + notificationId);
|
throw new IllegalArgumentException("Notification non trouvée avec l'ID : " + notificationId);
|
||||||
}
|
}
|
||||||
|
|
||||||
notification.markAsRead();
|
notification.markAsRead();
|
||||||
notificationRepository.persist(notification);
|
notificationRepository.persist(notification);
|
||||||
System.out.println("[LOG] Notification marquée comme lue avec succès");
|
logger.info("[NotificationService] Notification marquée comme lue avec succès");
|
||||||
return notification;
|
return notification;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,16 +192,16 @@ public class NotificationService {
|
|||||||
*/
|
*/
|
||||||
@Transactional
|
@Transactional
|
||||||
public int markAllAsRead(UUID userId) {
|
public int markAllAsRead(UUID userId) {
|
||||||
System.out.println("[LOG] Marquage de toutes les notifications comme lues pour l'utilisateur ID : " + userId);
|
logger.info("[NotificationService] Marquage de toutes les notifications comme lues pour l'utilisateur ID : " + userId);
|
||||||
|
|
||||||
Users user = usersRepository.findById(userId);
|
Users user = usersRepository.findById(userId);
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
System.out.println("[ERROR] Utilisateur non trouvé avec l'ID : " + userId);
|
logger.error("[NotificationService] Utilisateur non trouvé avec l'ID : " + userId);
|
||||||
throw new UserNotFoundException("Utilisateur non trouvé avec l'ID : " + userId);
|
throw new UserNotFoundException("Utilisateur non trouvé avec l'ID : " + userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
int updated = notificationRepository.markAllAsReadByUserId(userId);
|
int updated = notificationRepository.markAllAsReadByUserId(userId);
|
||||||
System.out.println("[LOG] " + updated + " notification(s) marquée(s) comme lue(s)");
|
logger.info("[NotificationService] " + updated + " notification(s) marquée(s) comme lue(s)");
|
||||||
return updated;
|
return updated;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,16 +213,16 @@ public class NotificationService {
|
|||||||
*/
|
*/
|
||||||
@Transactional
|
@Transactional
|
||||||
public boolean deleteNotification(UUID notificationId) {
|
public boolean deleteNotification(UUID notificationId) {
|
||||||
System.out.println("[LOG] Suppression de la notification ID : " + notificationId);
|
logger.info("[NotificationService] Suppression de la notification ID : " + notificationId);
|
||||||
|
|
||||||
Notification notification = notificationRepository.findById(notificationId);
|
Notification notification = notificationRepository.findById(notificationId);
|
||||||
if (notification == null) {
|
if (notification == null) {
|
||||||
System.out.println("[ERROR] Notification non trouvée avec l'ID : " + notificationId);
|
logger.error("[NotificationService] Notification non trouvée avec l'ID : " + notificationId);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
notificationRepository.delete(notification);
|
notificationRepository.delete(notification);
|
||||||
System.out.println("[LOG] Notification supprimée avec succès");
|
logger.info("[NotificationService] Notification supprimée avec succès");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,11 +233,11 @@ public class NotificationService {
|
|||||||
* @return La notification trouvée
|
* @return La notification trouvée
|
||||||
*/
|
*/
|
||||||
public Notification getNotificationById(UUID notificationId) {
|
public Notification getNotificationById(UUID notificationId) {
|
||||||
System.out.println("[LOG] Récupération de la notification ID : " + notificationId);
|
logger.info("[NotificationService] Récupération de la notification ID : " + notificationId);
|
||||||
|
|
||||||
Notification notification = notificationRepository.findById(notificationId);
|
Notification notification = notificationRepository.findById(notificationId);
|
||||||
if (notification == null) {
|
if (notification == null) {
|
||||||
System.out.println("[ERROR] Notification non trouvée avec l'ID : " + notificationId);
|
logger.error("[NotificationService] Notification non trouvée avec l'ID : " + notificationId);
|
||||||
throw new IllegalArgumentException("Notification non trouvée avec l'ID : " + notificationId);
|
throw new IllegalArgumentException("Notification non trouvée avec l'ID : " + notificationId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
133
src/main/java/com/lions/dev/service/PasswordResetService.java
Normal file
133
src/main/java/com/lions/dev/service/PasswordResetService.java
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
package com.lions.dev.service;
|
||||||
|
|
||||||
|
import com.lions.dev.entity.auth.PasswordResetToken;
|
||||||
|
import com.lions.dev.entity.users.Users;
|
||||||
|
import com.lions.dev.repository.PasswordResetTokenRepository;
|
||||||
|
import com.lions.dev.repository.UsersRepository;
|
||||||
|
import jakarta.enterprise.context.ApplicationScoped;
|
||||||
|
import jakarta.inject.Inject;
|
||||||
|
import jakarta.transaction.Transactional;
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service de gestion de la réinitialisation de mot de passe.
|
||||||
|
*
|
||||||
|
* Coordonne la création de tokens, l'envoi d'emails et la validation.
|
||||||
|
*/
|
||||||
|
@ApplicationScoped
|
||||||
|
public class PasswordResetService {
|
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(PasswordResetService.class);
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
PasswordResetTokenRepository tokenRepository;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
UsersRepository usersRepository;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
EmailService emailService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initie le processus de réinitialisation de mot de passe.
|
||||||
|
* Crée un token et envoie un email à l'utilisateur.
|
||||||
|
*
|
||||||
|
* @param email L'email de l'utilisateur
|
||||||
|
* @return true si l'email a été envoyé (ou simulé), false si l'utilisateur n'existe pas
|
||||||
|
*/
|
||||||
|
@Transactional
|
||||||
|
public boolean initiatePasswordReset(String email) {
|
||||||
|
logger.info("Initiation de réinitialisation de mot de passe pour: " + email);
|
||||||
|
|
||||||
|
// Rechercher l'utilisateur
|
||||||
|
Optional<Users> userOpt = usersRepository.findByEmail(email);
|
||||||
|
if (userOpt.isEmpty()) {
|
||||||
|
logger.info("Aucun utilisateur trouvé avec l'email: " + email);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Users user = userOpt.get();
|
||||||
|
|
||||||
|
// Invalider les tokens existants pour cet utilisateur
|
||||||
|
tokenRepository.invalidateAllForUser(user);
|
||||||
|
|
||||||
|
// Créer un nouveau token
|
||||||
|
PasswordResetToken token = new PasswordResetToken(user);
|
||||||
|
tokenRepository.persist(token);
|
||||||
|
|
||||||
|
logger.info("Token de réinitialisation créé pour: " + email + " (expire: " + token.getExpiresAt() + ")");
|
||||||
|
|
||||||
|
// Envoyer l'email
|
||||||
|
try {
|
||||||
|
emailService.sendPasswordResetEmail(user.getEmail(), user.getFirstName(), token.getToken());
|
||||||
|
return true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("Erreur lors de l'envoi de l'email de réinitialisation", e);
|
||||||
|
// On ne révèle pas l'erreur à l'utilisateur pour des raisons de sécurité
|
||||||
|
return true; // Retourne true quand même pour ne pas révéler si l'email existe
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Valide un token de réinitialisation.
|
||||||
|
*
|
||||||
|
* @param tokenValue La valeur du token
|
||||||
|
* @return Le token s'il est valide, empty sinon
|
||||||
|
*/
|
||||||
|
public Optional<PasswordResetToken> validateToken(String tokenValue) {
|
||||||
|
Optional<PasswordResetToken> tokenOpt = tokenRepository.findValidToken(tokenValue);
|
||||||
|
|
||||||
|
if (tokenOpt.isEmpty()) {
|
||||||
|
logger.warn("Token de réinitialisation invalide ou expiré: " + tokenValue.substring(0, 8) + "...");
|
||||||
|
}
|
||||||
|
|
||||||
|
return tokenOpt;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Réinitialise le mot de passe avec un token valide.
|
||||||
|
*
|
||||||
|
* @param tokenValue La valeur du token
|
||||||
|
* @param newPassword Le nouveau mot de passe
|
||||||
|
* @return true si le mot de passe a été réinitialisé, false sinon
|
||||||
|
*/
|
||||||
|
@Transactional
|
||||||
|
public boolean resetPassword(String tokenValue, String newPassword) {
|
||||||
|
Optional<PasswordResetToken> tokenOpt = tokenRepository.findValidToken(tokenValue);
|
||||||
|
|
||||||
|
if (tokenOpt.isEmpty()) {
|
||||||
|
logger.warn("Tentative de réinitialisation avec un token invalide");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
PasswordResetToken token = tokenOpt.get();
|
||||||
|
Users user = token.getUser();
|
||||||
|
|
||||||
|
// Mettre à jour le mot de passe
|
||||||
|
user.setPassword(newPassword);
|
||||||
|
usersRepository.persist(user);
|
||||||
|
|
||||||
|
// Marquer le token comme utilisé
|
||||||
|
token.markAsUsed();
|
||||||
|
tokenRepository.persist(token);
|
||||||
|
|
||||||
|
// Invalider tous les autres tokens pour cet utilisateur
|
||||||
|
tokenRepository.invalidateAllForUser(user);
|
||||||
|
|
||||||
|
logger.info("Mot de passe réinitialisé avec succès pour: " + user.getEmail());
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Nettoie les tokens expirés (à appeler via un scheduler).
|
||||||
|
*
|
||||||
|
* @return Le nombre de tokens supprimés
|
||||||
|
*/
|
||||||
|
@Transactional
|
||||||
|
public long cleanupExpiredTokens() {
|
||||||
|
return tokenRepository.deleteExpiredTokens();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -51,7 +51,7 @@ public class PresenceService {
|
|||||||
// Broadcast présence aux autres utilisateurs
|
// Broadcast présence aux autres utilisateurs
|
||||||
broadcastPresenceToAll(userId, true, user.getLastSeen());
|
broadcastPresenceToAll(userId, true, user.getLastSeen());
|
||||||
|
|
||||||
System.out.println("[PRESENCE] Utilisateur " + userId + " marqué online");
|
Log.debug("[PRESENCE] Utilisateur " + userId + " marqué online");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,7 +70,7 @@ public class PresenceService {
|
|||||||
// Broadcast présence aux autres utilisateurs
|
// Broadcast présence aux autres utilisateurs
|
||||||
broadcastPresenceToAll(userId, false, user.getLastSeen());
|
broadcastPresenceToAll(userId, false, user.getLastSeen());
|
||||||
|
|
||||||
System.out.println("[PRESENCE] Utilisateur " + userId + " marqué offline");
|
Log.debug("[PRESENCE] Utilisateur " + userId + " marqué offline");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,7 +87,7 @@ public class PresenceService {
|
|||||||
if (user != null) {
|
if (user != null) {
|
||||||
user.updatePresence();
|
user.updatePresence();
|
||||||
usersRepository.persist(user);
|
usersRepository.persist(user);
|
||||||
System.out.println("[PRESENCE] Heartbeat reçu pour utilisateur " + userId);
|
Log.debug("[PRESENCE] Heartbeat reçu pour utilisateur " + userId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package com.lions.dev.service;
|
package com.lions.dev.service;
|
||||||
|
|
||||||
import com.lions.dev.entity.friends.Friendship;
|
import com.lions.dev.entity.friends.Friendship;
|
||||||
import com.lions.dev.entity.friends.FriendshipStatus;
|
|
||||||
import com.lions.dev.entity.social.SocialPost;
|
import com.lions.dev.entity.social.SocialPost;
|
||||||
import com.lions.dev.entity.users.Users;
|
import com.lions.dev.entity.users.Users;
|
||||||
import com.lions.dev.exception.UserNotFoundException;
|
import com.lions.dev.exception.UserNotFoundException;
|
||||||
@@ -14,6 +13,7 @@ import org.eclipse.microprofile.reactive.messaging.Emitter;
|
|||||||
import jakarta.enterprise.context.ApplicationScoped;
|
import jakarta.enterprise.context.ApplicationScoped;
|
||||||
import jakarta.inject.Inject;
|
import jakarta.inject.Inject;
|
||||||
import jakarta.transaction.Transactional;
|
import jakarta.transaction.Transactional;
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -22,15 +22,15 @@ import java.util.stream.Collectors;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Service de gestion des posts sociaux.
|
* Service de gestion des posts sociaux.
|
||||||
*
|
*
|
||||||
* Ce service contient la logique métier pour la création, récupération,
|
* Ce service contient la logique métier pour la création, récupération,
|
||||||
* mise à jour et suppression des posts sociaux.
|
* mise à jour et suppression des posts sociaux.
|
||||||
*
|
|
||||||
* Tous les logs nécessaires pour la traçabilité sont intégrés.
|
|
||||||
*/
|
*/
|
||||||
@ApplicationScoped
|
@ApplicationScoped
|
||||||
public class SocialPostService {
|
public class SocialPostService {
|
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(SocialPostService.class);
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
SocialPostRepository socialPostRepository;
|
SocialPostRepository socialPostRepository;
|
||||||
|
|
||||||
@@ -55,7 +55,7 @@ public class SocialPostService {
|
|||||||
* @return Liste paginée des posts
|
* @return Liste paginée des posts
|
||||||
*/
|
*/
|
||||||
public List<SocialPost> getAllPosts(int page, int size) {
|
public List<SocialPost> getAllPosts(int page, int size) {
|
||||||
System.out.println("[LOG] Récupération de tous les posts (page: " + page + ", size: " + size + ")");
|
logger.info("[SocialPostService] Récupération de tous les posts (page: " + page + ", size: " + size + ")");
|
||||||
return socialPostRepository.findAllWithPagination(page, size);
|
return socialPostRepository.findAllWithPagination(page, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,11 +67,11 @@ public class SocialPostService {
|
|||||||
* @throws UserNotFoundException Si l'utilisateur n'existe pas
|
* @throws UserNotFoundException Si l'utilisateur n'existe pas
|
||||||
*/
|
*/
|
||||||
public List<SocialPost> getPostsByUserId(UUID userId) {
|
public List<SocialPost> getPostsByUserId(UUID userId) {
|
||||||
System.out.println("[LOG] Récupération des posts pour l'utilisateur ID : " + userId);
|
logger.info("[SocialPostService] Récupération des posts pour l'utilisateur ID : " + userId);
|
||||||
|
|
||||||
Users user = usersRepository.findById(userId);
|
Users user = usersRepository.findById(userId);
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
System.out.println("[ERROR] Utilisateur non trouvé avec l'ID : " + userId);
|
logger.error("[SocialPostService] Utilisateur non trouvé avec l'ID : " + userId);
|
||||||
throw new UserNotFoundException("Utilisateur non trouvé avec l'ID : " + userId);
|
throw new UserNotFoundException("Utilisateur non trouvé avec l'ID : " + userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,11 +89,11 @@ public class SocialPostService {
|
|||||||
*/
|
*/
|
||||||
@Transactional
|
@Transactional
|
||||||
public SocialPost createPost(String content, UUID userId, String imageUrl) {
|
public SocialPost createPost(String content, UUID userId, String imageUrl) {
|
||||||
System.out.println("[LOG] Création d'un post par l'utilisateur ID : " + userId);
|
logger.info("[SocialPostService] Création d'un post par l'utilisateur ID : " + userId);
|
||||||
|
|
||||||
Users user = usersRepository.findById(userId);
|
Users user = usersRepository.findById(userId);
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
System.out.println("[ERROR] Utilisateur non trouvé avec l'ID : " + userId);
|
logger.error("[SocialPostService] Utilisateur non trouvé avec l'ID : " + userId);
|
||||||
throw new UserNotFoundException("Utilisateur non trouvé avec l'ID : " + userId);
|
throw new UserNotFoundException("Utilisateur non trouvé avec l'ID : " + userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,7 +103,7 @@ public class SocialPostService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
socialPostRepository.persist(post);
|
socialPostRepository.persist(post);
|
||||||
System.out.println("[LOG] Post créé avec succès : " + post.getId());
|
logger.info("[SocialPostService] Post créé avec succès : " + post.getId());
|
||||||
|
|
||||||
// Créer des notifications pour tous les amis
|
// Créer des notifications pour tous les amis
|
||||||
try {
|
try {
|
||||||
@@ -128,9 +128,9 @@ public class SocialPostService {
|
|||||||
null
|
null
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
System.out.println("[LOG] Notifications créées pour " + friendships.size() + " ami(s)");
|
logger.info("[SocialPostService] Notifications créées pour " + friendships.size() + " ami(s)");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
System.out.println("[ERROR] Erreur lors de la création des notifications : " + e.getMessage());
|
logger.error("[SocialPostService] Erreur lors de la création des notifications : " + e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
return post;
|
return post;
|
||||||
@@ -143,11 +143,11 @@ public class SocialPostService {
|
|||||||
* @return Le post trouvé
|
* @return Le post trouvé
|
||||||
*/
|
*/
|
||||||
public SocialPost getPostById(UUID postId) {
|
public SocialPost getPostById(UUID postId) {
|
||||||
System.out.println("[LOG] Récupération du post ID : " + postId);
|
logger.info("[SocialPostService] Récupération du post ID : " + postId);
|
||||||
|
|
||||||
SocialPost post = socialPostRepository.findById(postId);
|
SocialPost post = socialPostRepository.findById(postId);
|
||||||
if (post == null) {
|
if (post == null) {
|
||||||
System.out.println("[ERROR] Post non trouvé avec l'ID : " + postId);
|
logger.error("[SocialPostService] Post non trouvé avec l'ID : " + postId);
|
||||||
throw new IllegalArgumentException("Post non trouvé avec l'ID : " + postId);
|
throw new IllegalArgumentException("Post non trouvé avec l'ID : " + postId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,11 +164,11 @@ public class SocialPostService {
|
|||||||
*/
|
*/
|
||||||
@Transactional
|
@Transactional
|
||||||
public SocialPost updatePost(UUID postId, String content, String imageUrl) {
|
public SocialPost updatePost(UUID postId, String content, String imageUrl) {
|
||||||
System.out.println("[LOG] Mise à jour du post ID : " + postId);
|
logger.info("[SocialPostService] Mise à jour du post ID : " + postId);
|
||||||
|
|
||||||
SocialPost post = socialPostRepository.findById(postId);
|
SocialPost post = socialPostRepository.findById(postId);
|
||||||
if (post == null) {
|
if (post == null) {
|
||||||
System.out.println("[ERROR] Post non trouvé avec l'ID : " + postId);
|
logger.error("[SocialPostService] Post non trouvé avec l'ID : " + postId);
|
||||||
throw new IllegalArgumentException("Post non trouvé avec l'ID : " + postId);
|
throw new IllegalArgumentException("Post non trouvé avec l'ID : " + postId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,7 +178,7 @@ public class SocialPostService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
socialPostRepository.persist(post);
|
socialPostRepository.persist(post);
|
||||||
System.out.println("[LOG] Post mis à jour avec succès");
|
logger.info("[SocialPostService] Post mis à jour avec succès");
|
||||||
return post;
|
return post;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -190,16 +190,16 @@ public class SocialPostService {
|
|||||||
*/
|
*/
|
||||||
@Transactional
|
@Transactional
|
||||||
public boolean deletePost(UUID postId) {
|
public boolean deletePost(UUID postId) {
|
||||||
System.out.println("[LOG] Suppression du post ID : " + postId);
|
logger.info("[SocialPostService] Suppression du post ID : " + postId);
|
||||||
|
|
||||||
SocialPost post = socialPostRepository.findById(postId);
|
SocialPost post = socialPostRepository.findById(postId);
|
||||||
if (post == null) {
|
if (post == null) {
|
||||||
System.out.println("[ERROR] Post non trouvé avec l'ID : " + postId);
|
logger.error("[SocialPostService] Post non trouvé avec l'ID : " + postId);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
socialPostRepository.delete(post);
|
socialPostRepository.delete(post);
|
||||||
System.out.println("[LOG] Post supprimé avec succès");
|
logger.info("[SocialPostService] Post supprimé avec succès");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -210,7 +210,7 @@ public class SocialPostService {
|
|||||||
* @return Liste des posts correspondant à la recherche
|
* @return Liste des posts correspondant à la recherche
|
||||||
*/
|
*/
|
||||||
public List<SocialPost> searchPosts(String query) {
|
public List<SocialPost> searchPosts(String query) {
|
||||||
System.out.println("[LOG] Recherche de posts avec la requête : " + query);
|
logger.info("[SocialPostService] Recherche de posts avec la requête : " + query);
|
||||||
return socialPostRepository.searchByContent(query);
|
return socialPostRepository.searchByContent(query);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -223,17 +223,35 @@ public class SocialPostService {
|
|||||||
*/
|
*/
|
||||||
@Transactional
|
@Transactional
|
||||||
public SocialPost likePost(UUID postId, UUID userId) {
|
public SocialPost likePost(UUID postId, UUID userId) {
|
||||||
System.out.println("[LOG] Like du post ID : " + postId + " par utilisateur : " + userId);
|
logger.info("[SocialPostService] Like du post ID : " + postId + " par utilisateur : " + userId);
|
||||||
|
|
||||||
SocialPost post = socialPostRepository.findById(postId);
|
SocialPost post = socialPostRepository.findById(postId);
|
||||||
if (post == null) {
|
if (post == null) {
|
||||||
System.out.println("[ERROR] Post non trouvé avec l'ID : " + postId);
|
logger.error("[SocialPostService] Post non trouvé avec l'ID : " + postId);
|
||||||
throw new IllegalArgumentException("Post non trouvé avec l'ID : " + postId);
|
throw new IllegalArgumentException("Post non trouvé avec l'ID : " + postId);
|
||||||
}
|
}
|
||||||
|
|
||||||
post.incrementLikes();
|
post.incrementLikes();
|
||||||
socialPostRepository.persist(post);
|
socialPostRepository.persist(post);
|
||||||
|
|
||||||
|
// Notification pour l'auteur du post (sauf auto-like)
|
||||||
|
try {
|
||||||
|
Users author = post.getUser();
|
||||||
|
if (author != null && !author.getId().equals(userId)) {
|
||||||
|
Users liker = usersRepository.findById(userId);
|
||||||
|
String likerName = liker != null ? liker.getFirstName() + " " + liker.getLastName() : "Quelqu'un";
|
||||||
|
notificationService.createNotification(
|
||||||
|
"Nouveau like",
|
||||||
|
likerName + " a aimé votre post",
|
||||||
|
"post",
|
||||||
|
author.getId(),
|
||||||
|
null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("[SocialPostService] Erreur création notification like : " + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
// TEMPS RÉEL: Publier dans Kafka (v2.0)
|
// TEMPS RÉEL: Publier dans Kafka (v2.0)
|
||||||
try {
|
try {
|
||||||
Map<String, Object> reactionData = new HashMap<>();
|
Map<String, Object> reactionData = new HashMap<>();
|
||||||
@@ -252,9 +270,9 @@ public class SocialPostService {
|
|||||||
);
|
);
|
||||||
|
|
||||||
reactionEmitter.send(event);
|
reactionEmitter.send(event);
|
||||||
System.out.println("[LOG] Réaction like publiée dans Kafka pour post: " + postId);
|
logger.info("[SocialPostService] Réaction like publiée dans Kafka pour post: " + postId);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
System.out.println("[ERROR] Erreur publication Kafka: " + e.getMessage());
|
logger.error("[SocialPostService] Erreur publication Kafka: " + e.getMessage());
|
||||||
// Ne pas bloquer le like si Kafka échoue
|
// Ne pas bloquer le like si Kafka échoue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -271,17 +289,38 @@ public class SocialPostService {
|
|||||||
*/
|
*/
|
||||||
@Transactional
|
@Transactional
|
||||||
public SocialPost addComment(UUID postId, UUID userId, String commentContent) {
|
public SocialPost addComment(UUID postId, UUID userId, String commentContent) {
|
||||||
System.out.println("[LOG] Ajout de commentaire au post ID : " + postId + " par utilisateur : " + userId);
|
logger.info("[SocialPostService] Ajout de commentaire au post ID : " + postId + " par utilisateur : " + userId);
|
||||||
|
|
||||||
SocialPost post = socialPostRepository.findById(postId);
|
SocialPost post = socialPostRepository.findById(postId);
|
||||||
if (post == null) {
|
if (post == null) {
|
||||||
System.out.println("[ERROR] Post non trouvé avec l'ID : " + postId);
|
logger.error("[SocialPostService] Post non trouvé avec l'ID : " + postId);
|
||||||
throw new IllegalArgumentException("Post non trouvé avec l'ID : " + postId);
|
throw new IllegalArgumentException("Post non trouvé avec l'ID : " + postId);
|
||||||
}
|
}
|
||||||
|
|
||||||
post.incrementComments();
|
post.incrementComments();
|
||||||
socialPostRepository.persist(post);
|
socialPostRepository.persist(post);
|
||||||
|
|
||||||
|
// Notification pour l'auteur du post (sauf auto-commentaire)
|
||||||
|
try {
|
||||||
|
Users author = post.getUser();
|
||||||
|
if (author != null && !author.getId().equals(userId)) {
|
||||||
|
Users commenter = usersRepository.findById(userId);
|
||||||
|
String commenterName = commenter != null ? commenter.getFirstName() + " " + commenter.getLastName() : "Quelqu'un";
|
||||||
|
String preview = commentContent != null && commentContent.length() > 60
|
||||||
|
? commentContent.substring(0, 60) + "..."
|
||||||
|
: (commentContent != null ? commentContent : "");
|
||||||
|
notificationService.createNotification(
|
||||||
|
"Nouveau commentaire",
|
||||||
|
commenterName + " a commenté votre post : " + preview,
|
||||||
|
"post",
|
||||||
|
author.getId(),
|
||||||
|
null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("[SocialPostService] Erreur création notification commentaire : " + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
// TEMPS RÉEL: Publier dans Kafka (v2.0)
|
// TEMPS RÉEL: Publier dans Kafka (v2.0)
|
||||||
try {
|
try {
|
||||||
Users commenter = usersRepository.findById(userId);
|
Users commenter = usersRepository.findById(userId);
|
||||||
@@ -304,9 +343,9 @@ public class SocialPostService {
|
|||||||
);
|
);
|
||||||
|
|
||||||
reactionEmitter.send(event);
|
reactionEmitter.send(event);
|
||||||
System.out.println("[LOG] Réaction comment publiée dans Kafka pour post: " + postId);
|
logger.info("[SocialPostService] Réaction comment publiée dans Kafka pour post: " + postId);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
System.out.println("[ERROR] Erreur publication Kafka: " + e.getMessage());
|
logger.error("[SocialPostService] Erreur publication Kafka: " + e.getMessage());
|
||||||
// Ne pas bloquer le commentaire si Kafka échoue
|
// Ne pas bloquer le commentaire si Kafka échoue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -322,11 +361,11 @@ public class SocialPostService {
|
|||||||
*/
|
*/
|
||||||
@Transactional
|
@Transactional
|
||||||
public SocialPost sharePost(UUID postId, UUID userId) {
|
public SocialPost sharePost(UUID postId, UUID userId) {
|
||||||
System.out.println("[LOG] Partage du post ID : " + postId + " par utilisateur : " + userId);
|
logger.info("[SocialPostService] Partage du post ID : " + postId + " par utilisateur : " + userId);
|
||||||
|
|
||||||
SocialPost post = socialPostRepository.findById(postId);
|
SocialPost post = socialPostRepository.findById(postId);
|
||||||
if (post == null) {
|
if (post == null) {
|
||||||
System.out.println("[ERROR] Post non trouvé avec l'ID : " + postId);
|
logger.error("[SocialPostService] Post non trouvé avec l'ID : " + postId);
|
||||||
throw new IllegalArgumentException("Post non trouvé avec l'ID : " + postId);
|
throw new IllegalArgumentException("Post non trouvé avec l'ID : " + postId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -348,9 +387,9 @@ public class SocialPostService {
|
|||||||
);
|
);
|
||||||
|
|
||||||
reactionEmitter.send(event);
|
reactionEmitter.send(event);
|
||||||
System.out.println("[LOG] Réaction share publiée dans Kafka pour post: " + postId);
|
logger.info("[SocialPostService] Réaction share publiée dans Kafka pour post: " + postId);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
System.out.println("[ERROR] Erreur publication Kafka: " + e.getMessage());
|
logger.error("[SocialPostService] Erreur publication Kafka: " + e.getMessage());
|
||||||
// Ne pas bloquer le partage si Kafka échoue
|
// Ne pas bloquer le partage si Kafka échoue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -367,11 +406,11 @@ public class SocialPostService {
|
|||||||
* @throws UserNotFoundException Si l'utilisateur n'existe pas
|
* @throws UserNotFoundException Si l'utilisateur n'existe pas
|
||||||
*/
|
*/
|
||||||
public List<SocialPost> getPostsByFriends(UUID userId, int page, int size) {
|
public List<SocialPost> getPostsByFriends(UUID userId, int page, int size) {
|
||||||
System.out.println("[LOG] Récupération des posts des amis pour l'utilisateur ID : " + userId);
|
logger.info("[SocialPostService] Récupération des posts des amis pour l'utilisateur ID : " + userId);
|
||||||
|
|
||||||
Users user = usersRepository.findById(userId);
|
Users user = usersRepository.findById(userId);
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
System.out.println("[ERROR] Utilisateur non trouvé avec l'ID : " + userId);
|
logger.error("[SocialPostService] Utilisateur non trouvé avec l'ID : " + userId);
|
||||||
throw new UserNotFoundException("Utilisateur non trouvé avec l'ID : " + userId);
|
throw new UserNotFoundException("Utilisateur non trouvé avec l'ID : " + userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -389,7 +428,7 @@ public class SocialPostService {
|
|||||||
.distinct()
|
.distinct()
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
System.out.println("[LOG] " + friendIds.size() + " ami(s) trouvé(s) pour l'utilisateur ID : " + userId);
|
logger.info("[SocialPostService] " + friendIds.size() + " ami(s) trouvé(s) pour l'utilisateur ID : " + userId);
|
||||||
|
|
||||||
// Récupérer les posts de l'utilisateur et de ses amis
|
// Récupérer les posts de l'utilisateur et de ses amis
|
||||||
return socialPostRepository.findPostsByFriends(userId, friendIds, page, size);
|
return socialPostRepository.findPostsByFriends(userId, friendIds, page, size);
|
||||||
|
|||||||
@@ -1,15 +1,24 @@
|
|||||||
package com.lions.dev.service;
|
package com.lions.dev.service;
|
||||||
|
|
||||||
|
import com.lions.dev.dto.events.NotificationEvent;
|
||||||
|
import com.lions.dev.entity.friends.Friendship;
|
||||||
|
import com.lions.dev.entity.friends.FriendshipStatus;
|
||||||
import com.lions.dev.entity.story.MediaType;
|
import com.lions.dev.entity.story.MediaType;
|
||||||
import com.lions.dev.entity.story.Story;
|
import com.lions.dev.entity.story.Story;
|
||||||
import com.lions.dev.entity.users.Users;
|
import com.lions.dev.entity.users.Users;
|
||||||
import com.lions.dev.exception.UserNotFoundException;
|
import com.lions.dev.exception.UserNotFoundException;
|
||||||
|
import com.lions.dev.repository.FriendshipRepository;
|
||||||
import com.lions.dev.repository.StoryRepository;
|
import com.lions.dev.repository.StoryRepository;
|
||||||
import com.lions.dev.repository.UsersRepository;
|
import com.lions.dev.repository.UsersRepository;
|
||||||
import jakarta.enterprise.context.ApplicationScoped;
|
import jakarta.enterprise.context.ApplicationScoped;
|
||||||
import jakarta.inject.Inject;
|
import jakarta.inject.Inject;
|
||||||
import jakarta.transaction.Transactional;
|
import jakarta.transaction.Transactional;
|
||||||
|
import org.eclipse.microprofile.reactive.messaging.Channel;
|
||||||
|
import org.eclipse.microprofile.reactive.messaging.Emitter;
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -17,18 +26,25 @@ import java.util.UUID;
|
|||||||
*
|
*
|
||||||
* Ce service contient la logique métier pour la création, récupération,
|
* Ce service contient la logique métier pour la création, récupération,
|
||||||
* mise à jour et suppression des stories.
|
* mise à jour et suppression des stories.
|
||||||
*
|
|
||||||
* Tous les logs nécessaires pour la traçabilité sont intégrés.
|
|
||||||
*/
|
*/
|
||||||
@ApplicationScoped
|
@ApplicationScoped
|
||||||
public class StoryService {
|
public class StoryService {
|
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(StoryService.class);
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
StoryRepository storyRepository;
|
StoryRepository storyRepository;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
UsersRepository usersRepository;
|
UsersRepository usersRepository;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
FriendshipRepository friendshipRepository;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
@Channel("notifications")
|
||||||
|
Emitter<NotificationEvent> notificationEmitter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Récupère toutes les stories actives (non expirées).
|
* Récupère toutes les stories actives (non expirées).
|
||||||
*
|
*
|
||||||
@@ -46,7 +62,7 @@ public class StoryService {
|
|||||||
* @return Liste paginée des stories actives
|
* @return Liste paginée des stories actives
|
||||||
*/
|
*/
|
||||||
public List<Story> getAllActiveStories(int page, int size) {
|
public List<Story> getAllActiveStories(int page, int size) {
|
||||||
System.out.println("[LOG] Récupération de toutes les stories actives (page: " + page + ", size: " + size + ")");
|
logger.info("[StoryService] Récupération de toutes les stories actives (page: " + page + ", size: " + size + ")");
|
||||||
return storyRepository.findAllActive(page, size);
|
return storyRepository.findAllActive(page, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,11 +87,11 @@ public class StoryService {
|
|||||||
* @throws UserNotFoundException Si l'utilisateur n'existe pas
|
* @throws UserNotFoundException Si l'utilisateur n'existe pas
|
||||||
*/
|
*/
|
||||||
public List<Story> getActiveStoriesByUserId(UUID userId, int page, int size) {
|
public List<Story> getActiveStoriesByUserId(UUID userId, int page, int size) {
|
||||||
System.out.println("[LOG] Récupération des stories actives pour l'utilisateur ID : " + userId + " (page: " + page + ", size: " + size + ")");
|
logger.info("[StoryService] Récupération des stories actives pour l'utilisateur ID : " + userId + " (page: " + page + ", size: " + size + ")");
|
||||||
|
|
||||||
Users user = usersRepository.findById(userId);
|
Users user = usersRepository.findById(userId);
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
System.out.println("[ERROR] Utilisateur non trouvé avec l'ID : " + userId);
|
logger.error("[StoryService] Utilisateur non trouvé avec l'ID : " + userId);
|
||||||
throw new UserNotFoundException("Utilisateur non trouvé avec l'ID : " + userId);
|
throw new UserNotFoundException("Utilisateur non trouvé avec l'ID : " + userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,23 +112,23 @@ public class StoryService {
|
|||||||
*/
|
*/
|
||||||
@Transactional
|
@Transactional
|
||||||
public Story createStory(UUID userId, MediaType mediaType, String mediaUrl, String thumbnailUrl, Integer durationSeconds) {
|
public Story createStory(UUID userId, MediaType mediaType, String mediaUrl, String thumbnailUrl, Integer durationSeconds) {
|
||||||
System.out.println("[LOG] Création d'une story par l'utilisateur ID : " + userId);
|
logger.info("[StoryService] Création d'une story par l'utilisateur ID : " + userId);
|
||||||
|
|
||||||
// Validation de l'utilisateur
|
// Validation de l'utilisateur
|
||||||
Users user = usersRepository.findById(userId);
|
Users user = usersRepository.findById(userId);
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
System.out.println("[ERROR] Utilisateur non trouvé avec l'ID : " + userId);
|
logger.error("[StoryService] Utilisateur non trouvé avec l'ID : " + userId);
|
||||||
throw new UserNotFoundException("Utilisateur non trouvé avec l'ID : " + userId);
|
throw new UserNotFoundException("Utilisateur non trouvé avec l'ID : " + userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validation des paramètres
|
// Validation des paramètres
|
||||||
if (mediaUrl == null || mediaUrl.trim().isEmpty()) {
|
if (mediaUrl == null || mediaUrl.trim().isEmpty()) {
|
||||||
System.out.println("[ERROR] L'URL du média est obligatoire");
|
logger.error("[StoryService] L'URL du média est obligatoire");
|
||||||
throw new IllegalArgumentException("L'URL du média est obligatoire");
|
throw new IllegalArgumentException("L'URL du média est obligatoire");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mediaType == null) {
|
if (mediaType == null) {
|
||||||
System.out.println("[ERROR] Le type de média est obligatoire");
|
logger.error("[StoryService] Le type de média est obligatoire");
|
||||||
throw new IllegalArgumentException("Le type de média est obligatoire");
|
throw new IllegalArgumentException("Le type de média est obligatoire");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,10 +144,52 @@ public class StoryService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
storyRepository.persist(story);
|
storyRepository.persist(story);
|
||||||
System.out.println("[LOG] Story créée avec succès : " + story.getId());
|
logger.info("[StoryService] Story créée avec succès : " + story.getId());
|
||||||
|
|
||||||
|
// Notifier les amis en temps réel via Kafka
|
||||||
|
notifyFriendsOfNewStory(user, story);
|
||||||
|
|
||||||
return story;
|
return story;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notifie les amis d'un utilisateur qu'une nouvelle story a été publiée.
|
||||||
|
*/
|
||||||
|
private void notifyFriendsOfNewStory(Users creator, Story story) {
|
||||||
|
try {
|
||||||
|
List<Friendship> friendships = friendshipRepository.findAcceptedFriendships(creator.getId());
|
||||||
|
String creatorName = creator.getFirstName() + " " + creator.getLastName();
|
||||||
|
|
||||||
|
for (Friendship friendship : friendships) {
|
||||||
|
Users friend = friendship.getUser().getId().equals(creator.getId())
|
||||||
|
? friendship.getFriend()
|
||||||
|
: friendship.getUser();
|
||||||
|
|
||||||
|
if (friend == null || friend.getId() == null) continue;
|
||||||
|
|
||||||
|
Map<String, Object> data = new HashMap<>();
|
||||||
|
data.put("storyId", story.getId().toString());
|
||||||
|
data.put("creatorId", creator.getId().toString());
|
||||||
|
data.put("creatorName", creatorName);
|
||||||
|
data.put("creatorProfileImage", creator.getProfileImageUrl());
|
||||||
|
data.put("mediaType", story.getMediaType().toString());
|
||||||
|
data.put("thumbnailUrl", story.getThumbnailUrl());
|
||||||
|
|
||||||
|
NotificationEvent event = new NotificationEvent(
|
||||||
|
friend.getId().toString(),
|
||||||
|
"story_created",
|
||||||
|
data
|
||||||
|
);
|
||||||
|
|
||||||
|
notificationEmitter.send(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debug("[StoryService] Notification story envoyée à " + friendships.size() + " amis");
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.warn("[StoryService] Échec notification Kafka story (non bloquant) : " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Récupère une story par son ID.
|
* Récupère une story par son ID.
|
||||||
*
|
*
|
||||||
@@ -140,11 +198,11 @@ public class StoryService {
|
|||||||
* @throws IllegalArgumentException Si la story n'existe pas
|
* @throws IllegalArgumentException Si la story n'existe pas
|
||||||
*/
|
*/
|
||||||
public Story getStoryById(UUID storyId) {
|
public Story getStoryById(UUID storyId) {
|
||||||
System.out.println("[LOG] Récupération de la story ID : " + storyId);
|
logger.info("[StoryService] Récupération de la story ID : " + storyId);
|
||||||
|
|
||||||
Story story = storyRepository.findById(storyId);
|
Story story = storyRepository.findById(storyId);
|
||||||
if (story == null) {
|
if (story == null) {
|
||||||
System.out.println("[ERROR] Story non trouvée avec l'ID : " + storyId);
|
logger.error("[StoryService] Story non trouvée avec l'ID : " + storyId);
|
||||||
throw new IllegalArgumentException("Story non trouvée avec l'ID : " + storyId);
|
throw new IllegalArgumentException("Story non trouvée avec l'ID : " + storyId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -162,19 +220,19 @@ public class StoryService {
|
|||||||
*/
|
*/
|
||||||
@Transactional
|
@Transactional
|
||||||
public Story markStoryAsViewed(UUID storyId, UUID viewerId) {
|
public Story markStoryAsViewed(UUID storyId, UUID viewerId) {
|
||||||
System.out.println("[LOG] Marquage de la story ID : " + storyId + " comme vue par l'utilisateur ID : " + viewerId);
|
logger.info("[StoryService] Marquage de la story ID : " + storyId + " comme vue par l'utilisateur ID : " + viewerId);
|
||||||
|
|
||||||
// Validation de l'utilisateur
|
// Validation de l'utilisateur
|
||||||
Users viewer = usersRepository.findById(viewerId);
|
Users viewer = usersRepository.findById(viewerId);
|
||||||
if (viewer == null) {
|
if (viewer == null) {
|
||||||
System.out.println("[ERROR] Utilisateur non trouvé avec l'ID : " + viewerId);
|
logger.error("[StoryService] Utilisateur non trouvé avec l'ID : " + viewerId);
|
||||||
throw new UserNotFoundException("Utilisateur non trouvé avec l'ID : " + viewerId);
|
throw new UserNotFoundException("Utilisateur non trouvé avec l'ID : " + viewerId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validation de la story
|
// Validation de la story
|
||||||
Story story = storyRepository.findById(storyId);
|
Story story = storyRepository.findById(storyId);
|
||||||
if (story == null) {
|
if (story == null) {
|
||||||
System.out.println("[ERROR] Story non trouvée avec l'ID : " + storyId);
|
logger.error("[StoryService] Story non trouvée avec l'ID : " + storyId);
|
||||||
throw new IllegalArgumentException("Story non trouvée avec l'ID : " + storyId);
|
throw new IllegalArgumentException("Story non trouvée avec l'ID : " + storyId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,14 +240,47 @@ public class StoryService {
|
|||||||
boolean isNewView = story.markAsViewed(viewerId);
|
boolean isNewView = story.markAsViewed(viewerId);
|
||||||
if (isNewView) {
|
if (isNewView) {
|
||||||
storyRepository.persist(story);
|
storyRepository.persist(story);
|
||||||
System.out.println("[LOG] Story marquée comme vue (nouvelle vue)");
|
logger.info("[StoryService] Story marquée comme vue (nouvelle vue)");
|
||||||
|
|
||||||
|
// Notifier le créateur en temps réel
|
||||||
|
notifyCreatorOfStoryView(story, viewer);
|
||||||
} else {
|
} else {
|
||||||
System.out.println("[LOG] Story déjà vue par cet utilisateur");
|
logger.info("[StoryService] Story déjà vue par cet utilisateur");
|
||||||
}
|
}
|
||||||
|
|
||||||
return story;
|
return story;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notifie le créateur d'une story qu'elle a été vue.
|
||||||
|
*/
|
||||||
|
private void notifyCreatorOfStoryView(Story story, Users viewer) {
|
||||||
|
try {
|
||||||
|
Users creator = story.getUser();
|
||||||
|
if (creator == null || creator.getId().equals(viewer.getId())) return;
|
||||||
|
|
||||||
|
String viewerName = viewer.getFirstName() + " " + viewer.getLastName();
|
||||||
|
|
||||||
|
Map<String, Object> data = new HashMap<>();
|
||||||
|
data.put("storyId", story.getId().toString());
|
||||||
|
data.put("viewerId", viewer.getId().toString());
|
||||||
|
data.put("viewerName", viewerName);
|
||||||
|
data.put("viewerProfileImage", viewer.getProfileImageUrl());
|
||||||
|
data.put("viewsCount", story.getViewsCount());
|
||||||
|
|
||||||
|
NotificationEvent event = new NotificationEvent(
|
||||||
|
creator.getId().toString(),
|
||||||
|
"story_viewed",
|
||||||
|
data
|
||||||
|
);
|
||||||
|
|
||||||
|
notificationEmitter.send(event);
|
||||||
|
logger.debug("[StoryService] Notification vue story envoyée au créateur : " + creator.getId());
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.warn("[StoryService] Échec notification vue story (non bloquant) : " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Supprime une story.
|
* Supprime une story.
|
||||||
*
|
*
|
||||||
@@ -198,16 +289,16 @@ public class StoryService {
|
|||||||
*/
|
*/
|
||||||
@Transactional
|
@Transactional
|
||||||
public boolean deleteStory(UUID storyId) {
|
public boolean deleteStory(UUID storyId) {
|
||||||
System.out.println("[LOG] Suppression de la story ID : " + storyId);
|
logger.info("[StoryService] Suppression de la story ID : " + storyId);
|
||||||
|
|
||||||
Story story = storyRepository.findById(storyId);
|
Story story = storyRepository.findById(storyId);
|
||||||
if (story == null) {
|
if (story == null) {
|
||||||
System.out.println("[ERROR] Story non trouvée avec l'ID : " + storyId);
|
logger.error("[StoryService] Story non trouvée avec l'ID : " + storyId);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
storyRepository.delete(story);
|
storyRepository.delete(story);
|
||||||
System.out.println("[LOG] Story supprimée avec succès");
|
logger.info("[StoryService] Story supprimée avec succès");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -219,7 +310,7 @@ public class StoryService {
|
|||||||
*/
|
*/
|
||||||
@Transactional
|
@Transactional
|
||||||
public int deactivateExpiredStories() {
|
public int deactivateExpiredStories() {
|
||||||
System.out.println("[LOG] Désactivation des stories expirées");
|
logger.info("[StoryService] Désactivation des stories expirées");
|
||||||
return storyRepository.deactivateExpiredStories();
|
return storyRepository.deactivateExpiredStories();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -230,7 +321,7 @@ public class StoryService {
|
|||||||
* @return Liste des stories les plus vues
|
* @return Liste des stories les plus vues
|
||||||
*/
|
*/
|
||||||
public List<Story> getMostViewedStories(int limit) {
|
public List<Story> getMostViewedStories(int limit) {
|
||||||
System.out.println("[LOG] Récupération des " + limit + " stories les plus vues");
|
logger.info("[StoryService] Récupération des " + limit + " stories les plus vues");
|
||||||
return storyRepository.findMostViewedStories(limit);
|
return storyRepository.findMostViewedStories(limit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import com.lions.dev.repository.UsersRepository;
|
|||||||
import jakarta.enterprise.context.ApplicationScoped;
|
import jakarta.enterprise.context.ApplicationScoped;
|
||||||
import jakarta.inject.Inject;
|
import jakarta.inject.Inject;
|
||||||
import jakarta.transaction.Transactional;
|
import jakarta.transaction.Transactional;
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
@@ -19,6 +20,8 @@ import java.util.UUID;
|
|||||||
@ApplicationScoped
|
@ApplicationScoped
|
||||||
public class UsersService {
|
public class UsersService {
|
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(UsersService.class);
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
UsersRepository usersRepository;
|
UsersRepository usersRepository;
|
||||||
|
|
||||||
@@ -66,7 +69,7 @@ public class UsersService {
|
|||||||
);
|
);
|
||||||
|
|
||||||
usersRepository.persist(user);
|
usersRepository.persist(user);
|
||||||
System.out.println("[LOG] Utilisateur créé : " + user.getEmail());
|
logger.info("Utilisateur créé : " + user.getEmail());
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,7 +85,7 @@ public class UsersService {
|
|||||||
public Users updateUser(UUID id, UserCreateRequestDTO userCreateRequestDTO) {
|
public Users updateUser(UUID id, UserCreateRequestDTO userCreateRequestDTO) {
|
||||||
Users existingUser = usersRepository.findById(id);
|
Users existingUser = usersRepository.findById(id);
|
||||||
if (existingUser == null) {
|
if (existingUser == null) {
|
||||||
System.out.println("[ERROR] Utilisateur non trouvé avec l'ID : " + id);
|
logger.error("Utilisateur non trouvé avec l'ID : " + id);
|
||||||
throw new UserNotFoundException("Utilisateur non trouvé avec l'ID : " + id);
|
throw new UserNotFoundException("Utilisateur non trouvé avec l'ID : " + id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,7 +120,7 @@ public class UsersService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
usersRepository.persist(existingUser);
|
usersRepository.persist(existingUser);
|
||||||
System.out.println("[LOG] Utilisateur mis à jour avec succès : " + existingUser.getEmail());
|
logger.info("Utilisateur mis à jour avec succès : " + existingUser.getEmail());
|
||||||
return existingUser;
|
return existingUser;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
@@ -132,7 +135,7 @@ public class UsersService {
|
|||||||
public Users updateUserProfileImage(UUID id, String profileImageUrl) {
|
public Users updateUserProfileImage(UUID id, String profileImageUrl) {
|
||||||
Users existingUser = usersRepository.findById(id);
|
Users existingUser = usersRepository.findById(id);
|
||||||
if (existingUser == null) {
|
if (existingUser == null) {
|
||||||
System.out.println("[ERROR] Utilisateur non trouvé avec l'ID : " + id);
|
logger.error("Utilisateur non trouvé avec l'ID : " + id);
|
||||||
throw new UserNotFoundException("Utilisateur non trouvé avec l'ID : " + id);
|
throw new UserNotFoundException("Utilisateur non trouvé avec l'ID : " + id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,7 +143,7 @@ public class UsersService {
|
|||||||
existingUser.setProfileImageUrl(profileImageUrl);
|
existingUser.setProfileImageUrl(profileImageUrl);
|
||||||
|
|
||||||
usersRepository.persist(existingUser);
|
usersRepository.persist(existingUser);
|
||||||
System.out.println("[LOG] L'image de profile de l\'Utilisateur mis à jour avec succès : " + existingUser.getEmail());
|
logger.info("Image de profil de l'utilisateur mise à jour avec succès : " + existingUser.getEmail());
|
||||||
return existingUser;
|
return existingUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,10 +169,10 @@ public class UsersService {
|
|||||||
public Users authenticateUser(String email, String password) {
|
public Users authenticateUser(String email, String password) {
|
||||||
Optional<Users> userOptional = usersRepository.findByEmail(email);
|
Optional<Users> userOptional = usersRepository.findByEmail(email);
|
||||||
if (userOptional.isEmpty() || !userOptional.get().verifyPassword(password)) { // v2.0
|
if (userOptional.isEmpty() || !userOptional.get().verifyPassword(password)) { // v2.0
|
||||||
System.out.println("[ERROR] Échec de l'authentification pour l'email : " + email);
|
logger.warn("Échec de l'authentification pour l'email : " + email);
|
||||||
throw new UserNotFoundException("Utilisateur ou mot de passe incorrect.");
|
throw new UserNotFoundException("Utilisateur ou mot de passe incorrect.");
|
||||||
}
|
}
|
||||||
System.out.println("[LOG] Utilisateur authentifié : " + email);
|
logger.info("Utilisateur authentifié : " + email);
|
||||||
return userOptional.get();
|
return userOptional.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -192,10 +195,10 @@ public class UsersService {
|
|||||||
public Users getUserById(UUID id) {
|
public Users getUserById(UUID id) {
|
||||||
Users user = usersRepository.findById(id);
|
Users user = usersRepository.findById(id);
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
System.out.println("[ERROR] Utilisateur non trouvé avec l'ID : " + id);
|
logger.error("Utilisateur non trouvé avec l'ID : " + id);
|
||||||
throw new UserNotFoundException("Utilisateur non trouvé avec l'ID : " + id);
|
throw new UserNotFoundException("Utilisateur non trouvé avec l'ID : " + id);
|
||||||
}
|
}
|
||||||
System.out.println("[LOG] Utilisateur trouvé avec l'ID : " + id);
|
logger.debug("Utilisateur trouvé avec l'ID : " + id);
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -210,13 +213,13 @@ public class UsersService {
|
|||||||
public void resetPassword(UUID id, String newPassword) {
|
public void resetPassword(UUID id, String newPassword) {
|
||||||
Users user = usersRepository.findById(id);
|
Users user = usersRepository.findById(id);
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
System.out.println("[ERROR] Utilisateur non trouvé avec l'ID : " + id);
|
logger.error("Utilisateur non trouvé avec l'ID : " + id);
|
||||||
throw new UserNotFoundException("Utilisateur non trouvé.");
|
throw new UserNotFoundException("Utilisateur non trouvé.");
|
||||||
}
|
}
|
||||||
|
|
||||||
user.setPassword(newPassword); // v2.0 - Hachage automatique
|
user.setPassword(newPassword); // v2.0 - Hachage automatique
|
||||||
usersRepository.persist(user);
|
usersRepository.persist(user);
|
||||||
System.out.println("[LOG] Mot de passe réinitialisé pour l'utilisateur : " + user.getEmail());
|
logger.info("Mot de passe réinitialisé pour l'utilisateur : " + user.getEmail());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -228,9 +231,9 @@ public class UsersService {
|
|||||||
public boolean deleteUser(UUID id) {
|
public boolean deleteUser(UUID id) {
|
||||||
boolean deleted = usersRepository.deleteById(id);
|
boolean deleted = usersRepository.deleteById(id);
|
||||||
if (deleted) {
|
if (deleted) {
|
||||||
System.out.println("[LOG] Utilisateur supprimé avec succès : " + id);
|
logger.info("Utilisateur supprimé avec succès : " + id);
|
||||||
} else {
|
} else {
|
||||||
System.out.println("[ERROR] Échec de la suppression de l'utilisateur avec l'ID : " + id);
|
logger.error("Échec de la suppression de l'utilisateur avec l'ID : " + id);
|
||||||
}
|
}
|
||||||
return deleted;
|
return deleted;
|
||||||
}
|
}
|
||||||
@@ -245,10 +248,10 @@ public class UsersService {
|
|||||||
public Users getUserByEmail(String email) {
|
public Users getUserByEmail(String email) {
|
||||||
Optional<Users> userOptional = usersRepository.findByEmail(email);
|
Optional<Users> userOptional = usersRepository.findByEmail(email);
|
||||||
if (userOptional.isEmpty()) {
|
if (userOptional.isEmpty()) {
|
||||||
System.out.println("[ERROR] Utilisateur non trouvé avec l'email : " + email);
|
logger.error("Utilisateur non trouvé avec l'email : " + email);
|
||||||
throw new UserNotFoundException("Utilisateur non trouvé avec l'email : " + email);
|
throw new UserNotFoundException("Utilisateur non trouvé avec l'email : " + email);
|
||||||
}
|
}
|
||||||
System.out.println("[LOG] Utilisateur trouvé avec l'email : " + email);
|
logger.debug("Utilisateur trouvé avec l'email : " + email);
|
||||||
return userOptional.get();
|
return userOptional.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -280,7 +283,28 @@ public class UsersService {
|
|||||||
}
|
}
|
||||||
user.setRole(newRole);
|
user.setRole(newRole);
|
||||||
usersRepository.persist(user);
|
usersRepository.persist(user);
|
||||||
System.out.println("[LOG] Rôle attribué à " + user.getEmail() + " : " + newRole);
|
logger.info("Rôle attribué à " + user.getEmail() + " : " + newRole);
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Force ou suspend le compte d'un utilisateur (réservé au super administrateur).
|
||||||
|
* Utilisé pour les managers : isActive = false = suspendu (abonnement expiré / sanction).
|
||||||
|
*
|
||||||
|
* @param userId L'ID de l'utilisateur à modifier.
|
||||||
|
* @param active true = forcer l'activation, false = suspendre.
|
||||||
|
* @return L'utilisateur mis à jour.
|
||||||
|
* @throws UserNotFoundException Si l'utilisateur n'existe pas.
|
||||||
|
*/
|
||||||
|
@Transactional
|
||||||
|
public Users setUserActive(UUID userId, boolean active) {
|
||||||
|
Users user = usersRepository.findById(userId);
|
||||||
|
if (user == null) {
|
||||||
|
throw new UserNotFoundException("Utilisateur non trouvé avec l'ID : " + userId);
|
||||||
|
}
|
||||||
|
user.setActive(active);
|
||||||
|
usersRepository.persist(user);
|
||||||
|
logger.info("Statut actif modifié pour " + user.getEmail() + " : " + active);
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,6 +48,8 @@ public class WavePaymentService {
|
|||||||
EstablishmentPaymentRepository paymentRepository;
|
EstablishmentPaymentRepository paymentRepository;
|
||||||
@Inject
|
@Inject
|
||||||
UsersRepository usersRepository;
|
UsersRepository usersRepository;
|
||||||
|
@Inject
|
||||||
|
EmailService emailService;
|
||||||
|
|
||||||
@ConfigProperty(name = "wave.api.url", defaultValue = "https://api.wave.com")
|
@ConfigProperty(name = "wave.api.url", defaultValue = "https://api.wave.com")
|
||||||
String waveApiUrl;
|
String waveApiUrl;
|
||||||
@@ -184,8 +186,32 @@ public class WavePaymentService {
|
|||||||
manager.setActive(true);
|
manager.setActive(true);
|
||||||
usersRepository.persist(manager);
|
usersRepository.persist(manager);
|
||||||
LOG.info("Webhook Wave: établissement et manager activés pour " + establishmentId);
|
LOG.info("Webhook Wave: établissement et manager activés pour " + establishmentId);
|
||||||
|
try {
|
||||||
|
emailService.sendPaymentConfirmationEmail(
|
||||||
|
manager.getEmail(),
|
||||||
|
manager.getFirstName(),
|
||||||
|
establishment.getName(),
|
||||||
|
sub.getAmountXof() != null ? sub.getAmountXof() : 0,
|
||||||
|
sub.getPlan() != null ? sub.getPlan() : "MONTHLY"
|
||||||
|
);
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOG.warn("Envoi email confirmation paiement échoué: " + e.getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if ("payment.refunded".equals(eventType)) {
|
||||||
|
sub.setStatus(EstablishmentSubscription.STATUS_CANCELLED);
|
||||||
|
subscriptionRepository.persist(sub);
|
||||||
|
if (establishment != null) {
|
||||||
|
establishment.setIsActive(false);
|
||||||
|
establishmentRepository.persist(establishment);
|
||||||
|
Users manager = establishment.getManager();
|
||||||
|
if (manager != null) {
|
||||||
|
manager.setActive(false);
|
||||||
|
usersRepository.persist(manager);
|
||||||
|
}
|
||||||
|
LOG.info("Webhook Wave: remboursement traité, établissement désactivé pour " + establishmentId);
|
||||||
|
}
|
||||||
} else if ("payment.cancelled".equals(eventType) || "payment.expired".equals(eventType)
|
} else if ("payment.cancelled".equals(eventType) || "payment.expired".equals(eventType)
|
||||||
|| "payment.failed".equals(eventType)) {
|
|| "payment.failed".equals(eventType)) {
|
||||||
sub.setStatus(EstablishmentSubscription.STATUS_CANCELLED);
|
sub.setStatus(EstablishmentSubscription.STATUS_CANCELLED);
|
||||||
@@ -199,6 +225,17 @@ public class WavePaymentService {
|
|||||||
manager.setActive(false);
|
manager.setActive(false);
|
||||||
usersRepository.persist(manager);
|
usersRepository.persist(manager);
|
||||||
LOG.info("Webhook Wave: établissement et manager suspendus pour " + establishmentId);
|
LOG.info("Webhook Wave: établissement et manager suspendus pour " + establishmentId);
|
||||||
|
if ("payment.failed".equals(eventType)) {
|
||||||
|
try {
|
||||||
|
emailService.sendPaymentFailureEmail(
|
||||||
|
manager.getEmail(),
|
||||||
|
manager.getFirstName(),
|
||||||
|
establishment.getName()
|
||||||
|
);
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOG.warn("Envoi email échec paiement échoué: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -207,8 +244,11 @@ public class WavePaymentService {
|
|||||||
paymentRepository.findByWaveSessionId(sessionId).ifPresent(payment -> {
|
paymentRepository.findByWaveSessionId(sessionId).ifPresent(payment -> {
|
||||||
if ("payment.completed".equals(eventType)) {
|
if ("payment.completed".equals(eventType)) {
|
||||||
payment.setStatus(EstablishmentPayment.STATUS_COMPLETED);
|
payment.setStatus(EstablishmentPayment.STATUS_COMPLETED);
|
||||||
} else if ("payment.cancelled".equals(eventType) || "payment.expired".equals(eventType)) {
|
} else if ("payment.cancelled".equals(eventType) || "payment.expired".equals(eventType)
|
||||||
|
|| "payment.refunded".equals(eventType)) {
|
||||||
payment.setStatus(EstablishmentPayment.STATUS_CANCELLED);
|
payment.setStatus(EstablishmentPayment.STATUS_CANCELLED);
|
||||||
|
} else if ("payment.failed".equals(eventType)) {
|
||||||
|
payment.setStatus(EstablishmentPayment.STATUS_FAILED);
|
||||||
}
|
}
|
||||||
paymentRepository.persist(payment);
|
paymentRepository.persist(payment);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ public class InputConverter {
|
|||||||
try {
|
try {
|
||||||
return Integer.parseInt(value);
|
return Integer.parseInt(value);
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
System.out.println("[ERROR] Impossible de convertir la valeur en entier : " + value);
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -33,7 +32,6 @@ public class InputConverter {
|
|||||||
} else if ("false".equalsIgnoreCase(value)) {
|
} else if ("false".equalsIgnoreCase(value)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
System.out.println("[ERROR] Valeur non valide pour un booléen : " + value);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,7 +42,6 @@ public class SecureStorage {
|
|||||||
* @param data Les données à effacer.
|
* @param data Les données à effacer.
|
||||||
*/
|
*/
|
||||||
public void clearData(String data) {
|
public void clearData(String data) {
|
||||||
System.out.println("[LOG] Les données ont été effacées de manière sécurisée.");
|
|
||||||
// En Java, il n'y a pas de suppression directe des données en mémoire. On peut ici
|
// En Java, il n'y a pas de suppression directe des données en mémoire. On peut ici
|
||||||
// gérer la suppression logique ou l'effacement de données dans un fichier sécurisé.
|
// gérer la suppression logique ou l'effacement de données dans un fichier sécurisé.
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,46 +0,0 @@
|
|||||||
package com.lions.dev.util;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Rôles utilisateur de l'application AfterWork.
|
|
||||||
* Hiérarchie : SUPER_ADMIN > ADMIN > MANAGER > USER.
|
|
||||||
*/
|
|
||||||
public final class UserRole {
|
|
||||||
|
|
||||||
private UserRole() {}
|
|
||||||
|
|
||||||
/** Utilisateur standard (participation aux événements, profil, amis). */
|
|
||||||
public static final String USER = "USER";
|
|
||||||
|
|
||||||
/** Responsable d'établissement (gestion de son établissement). */
|
|
||||||
public static final String MANAGER = "MANAGER";
|
|
||||||
|
|
||||||
/** Administrateur (gestion des établissements, modération). */
|
|
||||||
public static final String ADMIN = "ADMIN";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Super administrateur : tous les droits (gestion des utilisateurs, attribution des rôles,
|
|
||||||
* gestion des établissements, accès aux paiements Wave, etc.).
|
|
||||||
*/
|
|
||||||
public static final String SUPER_ADMIN = "SUPER_ADMIN";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Vérifie si le rôle a les droits super admin (ou est super admin).
|
|
||||||
*/
|
|
||||||
public static boolean isSuperAdmin(String role) {
|
|
||||||
return SUPER_ADMIN.equals(role);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Vérifie si le rôle peut gérer les utilisateurs (attribution de rôles, etc.).
|
|
||||||
*/
|
|
||||||
public static boolean canManageUsers(String role) {
|
|
||||||
return SUPER_ADMIN.equals(role) || ADMIN.equals(role);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Vérifie si le rôle peut gérer les établissements (vérification, modération).
|
|
||||||
*/
|
|
||||||
public static boolean canManageEstablishments(String role) {
|
|
||||||
return SUPER_ADMIN.equals(role) || ADMIN.equals(role);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -34,9 +34,14 @@ quarkus.hibernate-orm.schema-generation.scripts.action=drop-and-create
|
|||||||
# ====================================================================
|
# ====================================================================
|
||||||
# Kafka (développement local)
|
# Kafka (développement local)
|
||||||
# ====================================================================
|
# ====================================================================
|
||||||
# En dev, défaut localhost:9092. Définir KAFKA_BOOTSTRAP_SERVERS si Kafka tourne ailleurs.
|
# En dev, même connecteur que la base (smallrye-kafka). Démarrer Kafka en local
|
||||||
|
# (ex. docker-compose) ou définir KAFKA_BOOTSTRAP_SERVERS si Kafka est ailleurs.
|
||||||
|
afterwork.kafka.enabled=${KAFKA_ENABLED:true}
|
||||||
kafka.bootstrap.servers=${KAFKA_BOOTSTRAP_SERVERS:localhost:9092}
|
kafka.bootstrap.servers=${KAFKA_BOOTSTRAP_SERVERS:localhost:9092}
|
||||||
|
|
||||||
|
# SmallRye Reactive Messaging - Utiliser smallrye-kafka (pas d'extension in-memory en runtime).
|
||||||
|
# Les canaux sont définis dans application.properties.
|
||||||
|
|
||||||
# ====================================================================
|
# ====================================================================
|
||||||
# Logging
|
# Logging
|
||||||
# ====================================================================
|
# ====================================================================
|
||||||
|
|||||||
@@ -33,6 +33,26 @@ afterwork.super-admin.api-key=${SUPER_ADMIN_API_KEY:}
|
|||||||
# ====================================================================
|
# ====================================================================
|
||||||
wave.api.url=${WAVE_API_URL:https://api.wave.com}
|
wave.api.url=${WAVE_API_URL:https://api.wave.com}
|
||||||
wave.api.key=${WAVE_API_KEY:}
|
wave.api.key=${WAVE_API_KEY:}
|
||||||
|
# Secret pour vérifier la signature des webhooks (X-Wave-Signature). Si vide, la vérification est désactivée.
|
||||||
|
wave.webhook.secret=${WAVE_WEBHOOK_SECRET:}
|
||||||
|
|
||||||
|
# ====================================================================
|
||||||
|
# Configuration Email (Quarkus Mailer)
|
||||||
|
# ====================================================================
|
||||||
|
# En production, configurer via variables d'environnement
|
||||||
|
quarkus.mailer.from=${MAILER_FROM:AfterWork <noreply@lions.dev>}
|
||||||
|
quarkus.mailer.host=${MAILER_HOST:smtp.gmail.com}
|
||||||
|
quarkus.mailer.port=${MAILER_PORT:587}
|
||||||
|
quarkus.mailer.start-tls=REQUIRED
|
||||||
|
quarkus.mailer.username=${MAILER_USERNAME:}
|
||||||
|
quarkus.mailer.password=${MAILER_PASSWORD:}
|
||||||
|
# Mode mock pour les tests (pas d'envoi réel)
|
||||||
|
quarkus.mailer.mock=${MAILER_MOCK:true}
|
||||||
|
|
||||||
|
# Configuration application pour les emails
|
||||||
|
afterwork.app.name=AfterWork
|
||||||
|
afterwork.app.url=${APP_URL:https://afterwork.lions.dev}
|
||||||
|
afterwork.email.from=${MAILER_FROM:noreply@lions.dev}
|
||||||
|
|
||||||
# ====================================================================
|
# ====================================================================
|
||||||
# HTTP (commun à tous les environnements)
|
# HTTP (commun à tous les environnements)
|
||||||
@@ -60,7 +80,8 @@ quarkus.hibernate-orm.log.sql=false
|
|||||||
# Upload de fichiers (commun à tous les environnements)
|
# Upload de fichiers (commun à tous les environnements)
|
||||||
# ====================================================================
|
# ====================================================================
|
||||||
quarkus.http.body.uploads-directory=/tmp/uploads
|
quarkus.http.body.uploads-directory=/tmp/uploads
|
||||||
quarkus.http.limits.max-body-size=10M
|
# Limite augmentée pour éviter "Broken pipe" sur gros fichiers (ex. photos > 10M)
|
||||||
|
quarkus.http.limits.max-body-size=25M
|
||||||
|
|
||||||
# ====================================================================
|
# ====================================================================
|
||||||
# WebSockets Next (commun à tous les environnements)
|
# WebSockets Next (commun à tous les environnements)
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
-- V16: Création de la table password_reset_tokens pour la réinitialisation de mot de passe
|
||||||
|
-- AfterWork Platform - Lions Dev
|
||||||
|
|
||||||
|
-- Table des tokens de réinitialisation de mot de passe
|
||||||
|
CREATE TABLE IF NOT EXISTS password_reset_tokens (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
token VARCHAR(64) NOT NULL UNIQUE,
|
||||||
|
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||||
|
expires_at TIMESTAMP NOT NULL,
|
||||||
|
used BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Index pour la recherche rapide par token
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_password_reset_tokens_token ON password_reset_tokens(token);
|
||||||
|
|
||||||
|
-- Index pour la recherche par utilisateur
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_password_reset_tokens_user_id ON password_reset_tokens(user_id);
|
||||||
|
|
||||||
|
-- Index pour le nettoyage des tokens expirés
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_password_reset_tokens_expires_at ON password_reset_tokens(expires_at);
|
||||||
|
|
||||||
|
COMMENT ON TABLE password_reset_tokens IS 'Tokens de réinitialisation de mot de passe (expire après 1h)';
|
||||||
|
COMMENT ON COLUMN password_reset_tokens.token IS 'Token unique de 64 caractères';
|
||||||
|
COMMENT ON COLUMN password_reset_tokens.user_id IS 'Référence vers l''utilisateur';
|
||||||
|
COMMENT ON COLUMN password_reset_tokens.expires_at IS 'Date d''expiration du token (1h après création)';
|
||||||
|
COMMENT ON COLUMN password_reset_tokens.used IS 'Indique si le token a été utilisé';
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
-- Migration V17: Création de la table friendships (demandes d'amitié)
|
||||||
|
-- Description: Table des relations d'amitié entre utilisateurs (PENDING, ACCEPTED, REJECTED)
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS friendships (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
user_id UUID NOT NULL,
|
||||||
|
friend_id UUID NOT NULL,
|
||||||
|
status VARCHAR(20) NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT fk_friendships_user
|
||||||
|
FOREIGN KEY (user_id)
|
||||||
|
REFERENCES users(id)
|
||||||
|
ON DELETE CASCADE,
|
||||||
|
CONSTRAINT fk_friendships_friend
|
||||||
|
FOREIGN KEY (friend_id)
|
||||||
|
REFERENCES users(id)
|
||||||
|
ON DELETE CASCADE,
|
||||||
|
CONSTRAINT chk_friendships_user_friend_diff
|
||||||
|
CHECK (user_id != friend_id),
|
||||||
|
CONSTRAINT uq_friendships_user_friend
|
||||||
|
UNIQUE (user_id, friend_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_friendships_user_id ON friendships(user_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_friendships_friend_id ON friendships(friend_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_friendships_status ON friendships(status);
|
||||||
|
|
||||||
|
-- Trigger pour mettre à jour updated_at (aligné avec BaseEntity, nomenclature comme V8/V10/V11/V15)
|
||||||
|
CREATE OR REPLACE FUNCTION update_friendships_updated_at()
|
||||||
|
RETURNS TRIGGER AS $$
|
||||||
|
BEGIN
|
||||||
|
NEW.updated_at = CURRENT_TIMESTAMP;
|
||||||
|
RETURN NEW;
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
|
CREATE TRIGGER trigger_update_friendships_updated_at
|
||||||
|
BEFORE UPDATE ON friendships
|
||||||
|
FOR EACH ROW
|
||||||
|
EXECUTE FUNCTION update_friendships_updated_at();
|
||||||
103
src/test/java/com/lions/dev/config/ScheduledJobsTest.java
Normal file
103
src/test/java/com/lions/dev/config/ScheduledJobsTest.java
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
package com.lions.dev.config;
|
||||||
|
|
||||||
|
import com.lions.dev.repository.EstablishmentRepository;
|
||||||
|
import com.lions.dev.repository.EstablishmentSubscriptionRepository;
|
||||||
|
import com.lions.dev.repository.EventsRepository;
|
||||||
|
import com.lions.dev.repository.PasswordResetTokenRepository;
|
||||||
|
import com.lions.dev.repository.StoryRepository;
|
||||||
|
import com.lions.dev.service.EmailService;
|
||||||
|
import com.lions.dev.service.NotificationService;
|
||||||
|
import io.quarkus.test.InjectMock;
|
||||||
|
import io.quarkus.test.junit.QuarkusTest;
|
||||||
|
import jakarta.inject.Inject;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.DisplayName;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.mockito.ArgumentMatchers.*;
|
||||||
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests des jobs planifiés (ScheduledJobs).
|
||||||
|
* Les dépendances sont mockées pour éviter DB/Kafka ; on vérifie que les méthodes ne lèvent pas et appellent les repos.
|
||||||
|
*/
|
||||||
|
@QuarkusTest
|
||||||
|
class ScheduledJobsTest {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
ScheduledJobs scheduledJobs;
|
||||||
|
|
||||||
|
@InjectMock
|
||||||
|
StoryRepository storyRepository;
|
||||||
|
|
||||||
|
@InjectMock
|
||||||
|
PasswordResetTokenRepository passwordResetTokenRepository;
|
||||||
|
|
||||||
|
@InjectMock
|
||||||
|
EstablishmentSubscriptionRepository subscriptionRepository;
|
||||||
|
|
||||||
|
@InjectMock
|
||||||
|
EstablishmentRepository establishmentRepository;
|
||||||
|
|
||||||
|
@InjectMock
|
||||||
|
EventsRepository eventsRepository;
|
||||||
|
|
||||||
|
@InjectMock
|
||||||
|
NotificationService notificationService;
|
||||||
|
|
||||||
|
@InjectMock
|
||||||
|
EmailService emailService;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("deactivateExpiredStories appelle le repository et ne lance pas")
|
||||||
|
void deactivateExpiredStories_callsRepository() {
|
||||||
|
when(storyRepository.deactivateExpiredStories()).thenReturn(0);
|
||||||
|
|
||||||
|
scheduledJobs.deactivateExpiredStories();
|
||||||
|
|
||||||
|
verify(storyRepository).deactivateExpiredStories();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("deleteExpiredPasswordResetTokens appelle le repository et ne lance pas")
|
||||||
|
void deleteExpiredPasswordResetTokens_callsRepository() {
|
||||||
|
when(passwordResetTokenRepository.deleteExpiredTokens()).thenReturn(0L);
|
||||||
|
|
||||||
|
scheduledJobs.deleteExpiredPasswordResetTokens();
|
||||||
|
|
||||||
|
verify(passwordResetTokenRepository).deleteExpiredTokens();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("expireSubscriptionsAndDisableEstablishments sans abonnements expirés ne lance pas")
|
||||||
|
void expireSubscriptionsAndDisableEstablishments_emptyList_doesNotThrow() {
|
||||||
|
when(subscriptionRepository.findExpiredActiveSubscriptions()).thenReturn(Collections.emptyList());
|
||||||
|
|
||||||
|
scheduledJobs.expireSubscriptionsAndDisableEstablishments();
|
||||||
|
|
||||||
|
verify(subscriptionRepository).findExpiredActiveSubscriptions();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("sendEventReminders sans événements dans les fenêtres ne lance pas")
|
||||||
|
void sendEventReminders_emptyWindows_doesNotThrow() {
|
||||||
|
when(eventsRepository.findEventsStartingBetween(any(), any())).thenReturn(List.of());
|
||||||
|
|
||||||
|
scheduledJobs.sendEventReminders();
|
||||||
|
|
||||||
|
verify(eventsRepository, atLeast(1)).findEventsStartingBetween(any(), any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("sendSubscriptionExpirationWarningEmails sans abonnements J-3 ne lance pas")
|
||||||
|
void sendSubscriptionExpirationWarningEmails_emptyList_doesNotThrow() {
|
||||||
|
when(subscriptionRepository.findActiveSubscriptionsExpiringBetween(any(), any()))
|
||||||
|
.thenReturn(Collections.emptyList());
|
||||||
|
|
||||||
|
scheduledJobs.sendSubscriptionExpirationWarningEmails();
|
||||||
|
|
||||||
|
verify(subscriptionRepository).findActiveSubscriptionsExpiringBetween(any(), any());
|
||||||
|
}
|
||||||
|
}
|
||||||
69
src/test/java/com/lions/dev/service/EmailServiceTest.java
Normal file
69
src/test/java/com/lions/dev/service/EmailServiceTest.java
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
package com.lions.dev.service;
|
||||||
|
|
||||||
|
import io.quarkus.test.junit.QuarkusTest;
|
||||||
|
import jakarta.inject.Inject;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.DisplayName;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests du service email (mode mock : pas d'envoi réel).
|
||||||
|
* Vérifie que les méthodes n'échouent pas et que le contenu est cohérent.
|
||||||
|
*/
|
||||||
|
@QuarkusTest
|
||||||
|
class EmailServiceTest {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
EmailService emailService;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("sendPasswordResetEmail ne lance pas d'exception")
|
||||||
|
void sendPasswordResetEmail_doesNotThrow() {
|
||||||
|
emailService.sendPasswordResetEmail("test@example.com", "Jean", "token-123");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("sendWelcomeEmail ne lance pas d'exception")
|
||||||
|
void sendWelcomeEmail_doesNotThrow() {
|
||||||
|
emailService.sendWelcomeEmail("welcome@example.com", "Marie");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("sendPaymentConfirmationEmail ne lance pas d'exception")
|
||||||
|
void sendPaymentConfirmationEmail_doesNotThrow() {
|
||||||
|
emailService.sendPaymentConfirmationEmail(
|
||||||
|
"manager@example.com", "Paul", "Le Bar du coin", 15_000, "MONTHLY");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("sendEventReminderEmail ne lance pas d'exception")
|
||||||
|
void sendEventReminderEmail_doesNotThrow() {
|
||||||
|
emailService.sendEventReminderEmail(
|
||||||
|
"user@example.com", "Sophie", "Afterwork Jeudi",
|
||||||
|
LocalDateTime.now().plusHours(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("sendSubscriptionExpirationWarningEmail ne lance pas d'exception")
|
||||||
|
void sendSubscriptionExpirationWarningEmail_doesNotThrow() {
|
||||||
|
emailService.sendSubscriptionExpirationWarningEmail(
|
||||||
|
"manager@example.com", "Luc", "Mon Établissement",
|
||||||
|
LocalDateTime.now().plusDays(3));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("sendBookingConfirmationEmail ne lance pas d'exception")
|
||||||
|
void sendBookingConfirmationEmail_doesNotThrow() {
|
||||||
|
emailService.sendBookingConfirmationEmail(
|
||||||
|
"client@example.com", "Emma", "Le Restaurant",
|
||||||
|
LocalDateTime.now().plusDays(1), 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("sendPaymentFailureEmail ne lance pas d'exception")
|
||||||
|
void sendPaymentFailureEmail_doesNotThrow() {
|
||||||
|
emailService.sendPaymentFailureEmail(
|
||||||
|
"manager@example.com", "Marc", "Établissement XYZ");
|
||||||
|
}
|
||||||
|
}
|
||||||
138
src/test/java/com/lions/dev/service/NotificationServiceTest.java
Normal file
138
src/test/java/com/lions/dev/service/NotificationServiceTest.java
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
package com.lions.dev.service;
|
||||||
|
|
||||||
|
import com.lions.dev.entity.notification.Notification;
|
||||||
|
import com.lions.dev.entity.users.Users;
|
||||||
|
import com.lions.dev.exception.UserNotFoundException;
|
||||||
|
import com.lions.dev.repository.EventsRepository;
|
||||||
|
import com.lions.dev.repository.NotificationRepository;
|
||||||
|
import com.lions.dev.repository.UsersRepository;
|
||||||
|
import io.quarkus.test.InjectMock;
|
||||||
|
import io.quarkus.test.junit.QuarkusTest;
|
||||||
|
import jakarta.inject.Inject;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.DisplayName;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
import static org.mockito.ArgumentMatchers.*;
|
||||||
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
|
@QuarkusTest
|
||||||
|
class NotificationServiceTest {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
NotificationService notificationService;
|
||||||
|
|
||||||
|
@InjectMock
|
||||||
|
NotificationRepository notificationRepository;
|
||||||
|
|
||||||
|
@InjectMock
|
||||||
|
UsersRepository usersRepository;
|
||||||
|
|
||||||
|
@InjectMock
|
||||||
|
EventsRepository eventsRepository;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("getNotificationsByUserId avec utilisateur existant retourne la liste")
|
||||||
|
void getNotificationsByUserId_userExists_returnsList() {
|
||||||
|
UUID userId = UUID.randomUUID();
|
||||||
|
Users user = new Users();
|
||||||
|
user.setId(userId);
|
||||||
|
Notification notif = new Notification("Titre", "Message", "event", user);
|
||||||
|
|
||||||
|
when(usersRepository.findById(userId)).thenReturn(user);
|
||||||
|
when(notificationRepository.findByUserId(userId)).thenReturn(List.of(notif));
|
||||||
|
|
||||||
|
List<Notification> result = notificationService.getNotificationsByUserId(userId);
|
||||||
|
|
||||||
|
assertNotNull(result);
|
||||||
|
assertEquals(1, result.size());
|
||||||
|
assertEquals("Titre", result.get(0).getTitle());
|
||||||
|
verify(usersRepository).findById(userId);
|
||||||
|
verify(notificationRepository).findByUserId(userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("getNotificationsByUserId avec utilisateur inconnu lance UserNotFoundException")
|
||||||
|
void getNotificationsByUserId_userNotFound_throws() {
|
||||||
|
UUID userId = UUID.randomUUID();
|
||||||
|
when(usersRepository.findById(userId)).thenReturn(null);
|
||||||
|
|
||||||
|
assertThrows(UserNotFoundException.class, () ->
|
||||||
|
notificationService.getNotificationsByUserId(userId));
|
||||||
|
verify(notificationRepository, never()).findByUserId(any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("createNotification avec données valides persiste et retourne la notification")
|
||||||
|
void createNotification_validData_persistsAndReturns() {
|
||||||
|
UUID userId = UUID.randomUUID();
|
||||||
|
UUID eventId = UUID.randomUUID();
|
||||||
|
Users user = new Users();
|
||||||
|
user.setId(userId);
|
||||||
|
|
||||||
|
when(usersRepository.findById(userId)).thenReturn(user);
|
||||||
|
|
||||||
|
Notification created = notificationService.createNotification(
|
||||||
|
"Rappel", "L'événement commence bientôt", "reminder", userId, eventId);
|
||||||
|
|
||||||
|
assertNotNull(created);
|
||||||
|
assertEquals("Rappel", created.getTitle());
|
||||||
|
assertEquals("reminder", created.getType());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("createNotification avec userId inconnu lance UserNotFoundException")
|
||||||
|
void createNotification_unknownUser_throws() {
|
||||||
|
UUID userId = UUID.randomUUID();
|
||||||
|
when(usersRepository.findById(userId)).thenReturn(null);
|
||||||
|
|
||||||
|
assertThrows(UserNotFoundException.class, () ->
|
||||||
|
notificationService.createNotification("T", "M", "event", userId, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("countUnreadNotifications avec utilisateur existant retourne le compte")
|
||||||
|
void countUnreadNotifications_userExists_returnsCount() {
|
||||||
|
UUID userId = UUID.randomUUID();
|
||||||
|
Users user = new Users();
|
||||||
|
user.setId(userId);
|
||||||
|
when(usersRepository.findById(userId)).thenReturn(user);
|
||||||
|
when(notificationRepository.countUnreadByUserId(userId)).thenReturn(3L);
|
||||||
|
|
||||||
|
long count = notificationService.countUnreadNotifications(userId);
|
||||||
|
|
||||||
|
assertEquals(3L, count);
|
||||||
|
verify(notificationRepository).countUnreadByUserId(userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("countUnreadNotifications avec utilisateur inconnu lance UserNotFoundException")
|
||||||
|
void countUnreadNotifications_userNotFound_throws() {
|
||||||
|
UUID userId = UUID.randomUUID();
|
||||||
|
when(usersRepository.findById(userId)).thenReturn(null);
|
||||||
|
|
||||||
|
assertThrows(UserNotFoundException.class, () ->
|
||||||
|
notificationService.countUnreadNotifications(userId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("getNotificationsByUserIdWithPagination avec utilisateur existant retourne la page")
|
||||||
|
void getNotificationsByUserIdWithPagination_userExists_returnsPage() {
|
||||||
|
UUID userId = UUID.randomUUID();
|
||||||
|
Users user = new Users();
|
||||||
|
user.setId(userId);
|
||||||
|
when(usersRepository.findById(userId)).thenReturn(user);
|
||||||
|
when(notificationRepository.findByUserIdWithPagination(userId, 0, 10))
|
||||||
|
.thenReturn(Collections.emptyList());
|
||||||
|
|
||||||
|
List<Notification> result = notificationService.getNotificationsByUserIdWithPagination(userId, 0, 10);
|
||||||
|
|
||||||
|
assertNotNull(result);
|
||||||
|
assertTrue(result.isEmpty());
|
||||||
|
verify(notificationRepository).findByUserIdWithPagination(userId, 0, 10);
|
||||||
|
}
|
||||||
|
}
|
||||||
183
src/test/java/com/lions/dev/service/WavePaymentServiceTest.java
Normal file
183
src/test/java/com/lions/dev/service/WavePaymentServiceTest.java
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
package com.lions.dev.service;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.lions.dev.dto.response.establishment.InitiateSubscriptionResponseDTO;
|
||||||
|
import com.lions.dev.entity.establishment.Establishment;
|
||||||
|
import com.lions.dev.entity.establishment.EstablishmentSubscription;
|
||||||
|
import com.lions.dev.entity.users.Users;
|
||||||
|
import com.lions.dev.repository.EstablishmentPaymentRepository;
|
||||||
|
import com.lions.dev.repository.EstablishmentRepository;
|
||||||
|
import com.lions.dev.repository.EstablishmentSubscriptionRepository;
|
||||||
|
import com.lions.dev.repository.UsersRepository;
|
||||||
|
import io.quarkus.test.InjectMock;
|
||||||
|
import io.quarkus.test.junit.QuarkusTest;
|
||||||
|
import jakarta.inject.Inject;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.DisplayName;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
import static org.mockito.ArgumentMatchers.*;
|
||||||
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
|
@QuarkusTest
|
||||||
|
class WavePaymentServiceTest {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
WavePaymentService wavePaymentService;
|
||||||
|
|
||||||
|
@InjectMock
|
||||||
|
EstablishmentRepository establishmentRepository;
|
||||||
|
|
||||||
|
@InjectMock
|
||||||
|
EstablishmentSubscriptionRepository subscriptionRepository;
|
||||||
|
|
||||||
|
@InjectMock
|
||||||
|
EstablishmentPaymentRepository paymentRepository;
|
||||||
|
|
||||||
|
@InjectMock
|
||||||
|
UsersRepository usersRepository;
|
||||||
|
|
||||||
|
@InjectMock
|
||||||
|
EmailService emailService;
|
||||||
|
|
||||||
|
private static final ObjectMapper objectMapper = new ObjectMapper();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("initiatePayment avec établissement inconnu lance IllegalArgumentException")
|
||||||
|
void initiatePayment_unknownEstablishment_throws() {
|
||||||
|
UUID establishmentId = UUID.randomUUID();
|
||||||
|
when(establishmentRepository.findById(establishmentId)).thenReturn(null);
|
||||||
|
|
||||||
|
assertThrows(IllegalArgumentException.class, () ->
|
||||||
|
wavePaymentService.initiatePayment(establishmentId, "MONTHLY", "+221771234567"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("initiatePayment avec établissement valide retourne un DTO avec paymentUrl")
|
||||||
|
void initiatePayment_validEstablishment_returnsDto() {
|
||||||
|
UUID establishmentId = UUID.randomUUID();
|
||||||
|
Establishment establishment = new Establishment();
|
||||||
|
establishment.setId(establishmentId);
|
||||||
|
establishment.setName("Le Bar Test");
|
||||||
|
|
||||||
|
when(establishmentRepository.findById(establishmentId)).thenReturn(establishment);
|
||||||
|
|
||||||
|
InitiateSubscriptionResponseDTO result = wavePaymentService.initiatePayment(
|
||||||
|
establishmentId, "MONTHLY", "+221771234567");
|
||||||
|
|
||||||
|
assertNotNull(result);
|
||||||
|
assertNotNull(result.getPaymentUrl());
|
||||||
|
assertTrue(result.getPaymentUrl().contains("checkout") || result.getPaymentUrl().contains("test"));
|
||||||
|
assertEquals("MONTHLY", result.getPlan());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("hasActiveSubscription retourne false quand aucun abonnement actif")
|
||||||
|
void hasActiveSubscription_noActive_returnsFalse() {
|
||||||
|
UUID establishmentId = UUID.randomUUID();
|
||||||
|
when(subscriptionRepository.findActiveByEstablishmentId(establishmentId)).thenReturn(Optional.empty());
|
||||||
|
|
||||||
|
boolean result = wavePaymentService.hasActiveSubscription(establishmentId);
|
||||||
|
|
||||||
|
assertFalse(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("hasActiveSubscription retourne true quand abonnement actif existe")
|
||||||
|
void hasActiveSubscription_hasActive_returnsTrue() {
|
||||||
|
UUID establishmentId = UUID.randomUUID();
|
||||||
|
EstablishmentSubscription sub = new EstablishmentSubscription();
|
||||||
|
sub.setEstablishmentId(establishmentId);
|
||||||
|
sub.setStatus(EstablishmentSubscription.STATUS_ACTIVE);
|
||||||
|
when(subscriptionRepository.findActiveByEstablishmentId(establishmentId)).thenReturn(Optional.of(sub));
|
||||||
|
|
||||||
|
boolean result = wavePaymentService.hasActiveSubscription(establishmentId);
|
||||||
|
|
||||||
|
assertTrue(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("handleWebhook payment.completed active l'établissement et envoie l'email de confirmation")
|
||||||
|
void handleWebhook_paymentCompleted_activatesAndSendsEmail() throws Exception {
|
||||||
|
String sessionId = "wave-session-123";
|
||||||
|
UUID establishmentId = UUID.randomUUID();
|
||||||
|
Establishment establishment = new Establishment();
|
||||||
|
establishment.setId(establishmentId);
|
||||||
|
establishment.setName("Bar Test");
|
||||||
|
establishment.setIsActive(false);
|
||||||
|
Users manager = new Users();
|
||||||
|
manager.setId(UUID.randomUUID());
|
||||||
|
manager.setEmail("manager@test.com");
|
||||||
|
manager.setFirstName("Jean");
|
||||||
|
manager.setActive(false);
|
||||||
|
establishment.setManager(manager);
|
||||||
|
|
||||||
|
EstablishmentSubscription sub = new EstablishmentSubscription();
|
||||||
|
sub.setId(UUID.randomUUID());
|
||||||
|
sub.setEstablishmentId(establishmentId);
|
||||||
|
sub.setPlan(EstablishmentSubscription.PLAN_MONTHLY);
|
||||||
|
sub.setStatus(EstablishmentSubscription.STATUS_PENDING);
|
||||||
|
sub.setAmountXof(15_000);
|
||||||
|
|
||||||
|
when(subscriptionRepository.findByWaveSessionId(sessionId)).thenReturn(Optional.of(sub));
|
||||||
|
when(establishmentRepository.findById(establishmentId)).thenReturn(establishment);
|
||||||
|
when(usersRepository.findById(manager.getId())).thenReturn(manager);
|
||||||
|
when(paymentRepository.findByWaveSessionId(sessionId)).thenReturn(Optional.of(new com.lions.dev.entity.establishment.EstablishmentPayment()));
|
||||||
|
|
||||||
|
String payload = """
|
||||||
|
{"type":"payment.completed","data":{"id":"%s"}}
|
||||||
|
""".formatted(sessionId);
|
||||||
|
var node = objectMapper.readTree(payload);
|
||||||
|
|
||||||
|
wavePaymentService.handleWebhook(node);
|
||||||
|
|
||||||
|
verify(emailService).sendPaymentConfirmationEmail(
|
||||||
|
eq("manager@test.com"),
|
||||||
|
eq("Jean"),
|
||||||
|
eq("Bar Test"),
|
||||||
|
eq(15_000),
|
||||||
|
eq("MONTHLY")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("handleWebhook payment.failed envoie l'email d'échec")
|
||||||
|
void handleWebhook_paymentFailed_sendsFailureEmail() throws Exception {
|
||||||
|
String sessionId = "wave-session-fail";
|
||||||
|
UUID establishmentId = UUID.randomUUID();
|
||||||
|
Establishment establishment = new Establishment();
|
||||||
|
establishment.setId(establishmentId);
|
||||||
|
establishment.setName("Bar Fail");
|
||||||
|
Users manager = new Users();
|
||||||
|
manager.setId(UUID.randomUUID());
|
||||||
|
manager.setEmail("manager@fail.com");
|
||||||
|
manager.setFirstName("Paul");
|
||||||
|
establishment.setManager(manager);
|
||||||
|
|
||||||
|
EstablishmentSubscription sub = new EstablishmentSubscription();
|
||||||
|
sub.setId(UUID.randomUUID());
|
||||||
|
sub.setEstablishmentId(establishmentId);
|
||||||
|
sub.setStatus(EstablishmentSubscription.STATUS_PENDING);
|
||||||
|
|
||||||
|
when(subscriptionRepository.findByWaveSessionId(sessionId)).thenReturn(Optional.of(sub));
|
||||||
|
when(establishmentRepository.findById(establishmentId)).thenReturn(establishment);
|
||||||
|
when(usersRepository.findById(manager.getId())).thenReturn(manager);
|
||||||
|
when(paymentRepository.findByWaveSessionId(sessionId)).thenReturn(Optional.of(new com.lions.dev.entity.establishment.EstablishmentPayment()));
|
||||||
|
|
||||||
|
String payload = """
|
||||||
|
{"type":"payment.failed","data":{"id":"%s"}}
|
||||||
|
""".formatted(sessionId);
|
||||||
|
var node = objectMapper.readTree(payload);
|
||||||
|
|
||||||
|
wavePaymentService.handleWebhook(node);
|
||||||
|
|
||||||
|
verify(emailService).sendPaymentFailureEmail(
|
||||||
|
eq("manager@fail.com"),
|
||||||
|
eq("Paul"),
|
||||||
|
eq("Bar Fail")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
9
src/test/resources/application.properties
Normal file
9
src/test/resources/application.properties
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
# Profil test : bootstrap Kafka résolvable (localhost) + consumers désactivés
|
||||||
|
%test.kafka.bootstrap.servers=localhost:9092
|
||||||
|
%test.mp.messaging.incoming.kafka-notifications.enabled=false
|
||||||
|
%test.mp.messaging.incoming.kafka-chat.enabled=false
|
||||||
|
%test.mp.messaging.incoming.kafka-reactions.enabled=false
|
||||||
|
%test.mp.messaging.incoming.kafka-presence.enabled=false
|
||||||
|
|
||||||
|
# Scheduler désactivé en test pour éviter exécution des jobs pendant les tests
|
||||||
|
%test.quarkus.scheduler.enabled=false
|
||||||
Reference in New Issue
Block a user