Refactoring

This commit is contained in:
dahoud
2026-01-31 16:54:46 +00:00
parent ce89face73
commit 9dc9ca591c
85 changed files with 2643 additions and 381 deletions

View 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());
}
}
}
}

View File

@@ -13,6 +13,5 @@ public abstract class Exceptions extends Exception {
*/
public Exceptions(String message) {
super(message);
System.out.println("[ERROR] Exception déclenchée : " + message);
}
}

View File

@@ -15,7 +15,6 @@ public class Failures {
*/
public Failures(String failureMessage) {
this.failureMessage = failureMessage;
System.out.println("[FAILURE] Échec détecté : " + failureMessage);
}
/**

View File

@@ -16,7 +16,6 @@ public class BadRequestException extends WebApplicationException {
*/
public BadRequestException(String message) {
super(message, Response.Status.BAD_REQUEST);
System.out.println("[ERROR] Requête invalide : " + message);
}
}

View File

@@ -16,6 +16,5 @@ public class NotFoundException extends WebApplicationException {
*/
public NotFoundException(String message) {
super(message, Response.Status.NOT_FOUND);
System.out.println("[ERROR] Ressource non trouvée : " + message);
}
}

View File

@@ -13,6 +13,5 @@ public class ServerException extends RuntimeException {
*/
public ServerException(String message) {
super(message);
System.out.println("[ERROR] Erreur serveur : " + message);
}
}

View File

@@ -16,6 +16,5 @@ public class UnauthorizedException extends WebApplicationException {
*/
public UnauthorizedException(String message) {
super(message, Response.Status.UNAUTHORIZED);
System.out.println("[ERROR] Accès non autorisé : " + message);
}
}

View File

@@ -2,6 +2,7 @@ package com.lions.dev.dto.request.establishment;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@@ -21,5 +22,6 @@ public class InitiateSubscriptionRequestDTO {
/** 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")
@Size(max = 25, message = "Le numéro de téléphone ne peut pas dépasser 25 caractères")
private String clientPhone;
}

View File

@@ -6,6 +6,7 @@ import lombok.Setter;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import java.time.LocalDateTime;
import org.jboss.logging.Logger;
/**
* DTO pour la création d'un événement.
@@ -61,7 +62,6 @@ public class EventCreateRequestDTO {
private String location;
public EventCreateRequestDTO() {
System.out.println("[LOG] DTO de requête de création d'événement initialisé.");
}
/**

View File

@@ -2,6 +2,7 @@ package com.lions.dev.dto.request.events;
import jakarta.validation.constraints.NotNull;
import java.util.UUID;
import org.jboss.logging.Logger;
/**
* DTO pour la suppression d'un événement.
@@ -15,6 +16,5 @@ public class EventDeleteRequestDTO {
private UUID eventId; // ID de l'événement à supprimer
public EventDeleteRequestDTO() {
System.out.println("[LOG] DTO de requête de suppression d'événement initialisé.");
}
}

View File

@@ -2,6 +2,7 @@ package com.lions.dev.dto.request.events;
import jakarta.validation.constraints.NotNull;
import java.util.UUID;
import org.jboss.logging.Logger;
/**
* 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
public EventReadOneByIdRequestDTO() {
System.out.println("[LOG] DTO de requête de lecture d'événement initialisé.");
}
}

View File

@@ -6,6 +6,7 @@ import jakarta.validation.constraints.Size;
import java.util.UUID;
import lombok.Getter;
import lombok.Setter;
import org.jboss.logging.Logger;
/**
* DTO pour la création d'un post social.
@@ -29,7 +30,6 @@ public class SocialPostCreateRequestDTO {
private String imageUrl; // URL de l'image (optionnel)
public SocialPostCreateRequestDTO() {
System.out.println("[LOG] DTO de requête de création de post social initialisé.");
}
}

View File

@@ -7,6 +7,7 @@ import jakarta.validation.constraints.Size;
import java.util.UUID;
import lombok.Getter;
import lombok.Setter;
import org.jboss.logging.Logger;
/**
* 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)
public StoryCreateRequestDTO() {
System.out.println("[LOG] DTO de requête de création de story initialisé.");
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}

View File

@@ -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();
}
}

View File

@@ -27,8 +27,24 @@ public class FriendshipCreateOneResponseDTO {
/**
* 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 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) {
this.id = friendship.getId();

View File

@@ -3,6 +3,7 @@ package com.lions.dev.dto.response.users;
import com.lions.dev.entity.users.Users;
import java.util.UUID;
import lombok.Getter;
import org.jboss.logging.Logger;
/**
* DTO pour renvoyer les informations d'un utilisateur.
@@ -68,6 +69,5 @@ public class UserCreateResponseDTO {
this.nom = this.lastName;
this.prenoms = this.firstName;
System.out.println("[LOG] DTO créé pour l'utilisateur : " + this.email);
}
}

View File

@@ -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 java.util.UUID;

View File

@@ -37,7 +37,6 @@ public abstract class BaseEntity {
protected void onCreate() {
this.createdAt = 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
protected void onUpdate() {
this.updatedAt = LocalDateTime.now();
System.out.println("[LOG] Entité mise à jour avec ID : " + this.id + " à " + this.updatedAt);
}
}

View File

@@ -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;
}
}

View File

@@ -62,7 +62,6 @@ public class Conversation extends BaseEntity {
this.user1 = user1;
this.user2 = user2;
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++;
}
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) {
if (user != null && user1 != null && user.getId().equals(user1.getId())) {
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())) {
unreadCountUser2 = 0;
System.out.println("[LOG] Messages marqués comme lus pour user2 dans la conversation " + this.getId());
}
}

View File

@@ -57,7 +57,6 @@ public class Message extends BaseEntity {
this.messageType = "text";
this.isRead = 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() {
if (!this.isRead) {
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() {
if (!this.isDelivered) {
this.isDelivered = true;
System.out.println("[LOG] Message " + this.getId() + " marqué comme délivré");
}
}

View File

@@ -53,15 +53,7 @@ public class Comment extends BaseEntity {
this.user = user;
this.event = event;
this.text = text;
this.commentDate =
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);
this.commentDate = LocalDateTime.now();
}
/**
@@ -73,14 +65,7 @@ public class Comment extends BaseEntity {
* @param newText Le nouveau texte du commentaire.
*/
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.commentDate = LocalDateTime.now(); // Mise à jour de la date de modification
this.commentDate = LocalDateTime.now();
}
}

View File

@@ -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();
}

View File

@@ -45,6 +45,10 @@ public class EstablishmentAmenity {
@JoinColumn(name = "establishment_id", insertable = false, updatable = false)
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.
*
@@ -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();
}
}

View File

@@ -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();
}
}

View File

@@ -117,7 +117,6 @@ public class Events extends BaseEntity {
*/
public void addParticipant(Users 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) {
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() {
int count = participants.size();
System.out.println("[LOG] Nombre de participants à l'événement : " + this.title + " - " + count);
return count;
}
@@ -146,7 +143,6 @@ public class Events extends BaseEntity {
*/
public void setClosed(boolean closed) {
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.
*/
public List<Comment> getComments() {
System.out.println("[LOG] Récupération des commentaires pour l'événement : " + this.title);
return comments;
}

View File

@@ -38,12 +38,5 @@ public class Friendship extends BaseEntity {
*/
public void setStatus(FriendshipStatus 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);
}
}

View File

@@ -60,7 +60,6 @@ public class Notification extends BaseEntity {
this.type = type;
this.user = user;
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() {
if (!this.isRead) {
this.isRead = true;
System.out.println("[LOG] Notification marquée comme lue : " + this.title);
}
}

View File

@@ -52,7 +52,6 @@ public class SocialPost extends BaseEntity {
this.likesCount = 0;
this.commentsCount = 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() {
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() {
if (this.likesCount > 0) {
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() {
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() {
this.sharesCount++;
System.out.println("[LOG] Partage ajouté au post ID : " + this.getId() + " (total: " + this.sharesCount + ")");
}
}

View File

@@ -70,7 +70,6 @@ public class Story extends BaseEntity {
this.expiresAt = LocalDateTime.now().plusHours(24); // Expire après 24h
this.isActive = true;
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) {
if (viewerIds.add(viewerId)) {
this.viewsCount++;
System.out.println("[LOG] Story ID : " + this.getId() + " vue par l'utilisateur ID : " + viewerId + " (total: " + this.viewsCount + ")");
return true;
}
return false;
@@ -102,7 +100,6 @@ public class Story extends BaseEntity {
*/
public void deactivate() {
this.isActive = false;
System.out.println("[LOG] Story ID : " + this.getId() + " désactivée");
}
/**

View File

@@ -84,7 +84,6 @@ public class Users extends BaseEntity {
*/
public void setPassword(String password) {
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) {
BCrypt.Result result = BCrypt.verifyer().verify(password.toCharArray(), this.passwordHash);
boolean isValid = result.verified;
System.out.println("[LOG] Vérification du mot de passe pour l'utilisateur : " + this.email + " - Résultat : " + isValid);
return isValid;
return result.verified;
}
/**
@@ -134,9 +131,7 @@ public class Users extends BaseEntity {
* @return true si l'utilisateur est un administrateur, false sinon.
*/
public boolean isAdmin() {
boolean isAdmin = "ADMIN".equalsIgnoreCase(this.role);
System.out.println("[LOG] Vérification du rôle ADMIN pour l'utilisateur : " + this.email + " - Résultat : " + isAdmin);
return isAdmin;
return "ADMIN".equalsIgnoreCase(this.role);
}
@ManyToMany(fetch = FetchType.LAZY)
@@ -154,7 +149,6 @@ public class Users extends BaseEntity {
*/
public void addFavoriteEvent(Events 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) {
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.
*/
public Set<Events> getFavoriteEvents() {
System.out.println("[LOG] Récupération des événements favoris pour l'utilisateur : " + this.email);
return favoriteEvents;
}
@@ -214,7 +206,6 @@ public class Users extends BaseEntity {
} else {
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() {
this.isOnline = true;
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() {
this.isOnline = false;
this.lastSeen = java.time.LocalDateTime.now();
System.out.println("[LOG] Utilisateur marqué hors ligne : " + this.email);
}
}

View File

@@ -17,6 +17,5 @@ public class EventNotFoundException extends WebApplicationException {
*/
public EventNotFoundException(UUID eventId) {
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());
}
}

View File

@@ -16,6 +16,5 @@ public class UserNotFoundException extends WebApplicationException {
*/
public UserNotFoundException(String message) {
super(message, Response.Status.NOT_FOUND);
System.out.println("[ERROR] Utilisateur non trouvé : " + message);
}
}

View File

@@ -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;
}
}

View File

@@ -5,6 +5,7 @@ import com.lions.dev.entity.users.Users;
import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase;
import io.quarkus.panache.common.Sort;
import jakarta.enterprise.context.ApplicationScoped;
import org.jboss.logging.Logger;
import java.util.List;
import java.util.Optional;
@@ -27,7 +28,6 @@ public class ConversationRepository implements PanacheRepositoryBase<Conversatio
* @return La conversation si elle existe, null sinon
*/
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
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) {
conversation = new Conversation(user1, user2);
persist(conversation);
System.out.println("[LOG] Nouvelle conversation créée avec l'ID : " + conversation.getId());
} else {
System.out.println("[LOG] Conversation existante trouvée avec l'ID : " + conversation.getId());
}
return conversation;
@@ -62,7 +60,6 @@ public class ConversationRepository implements PanacheRepositoryBase<Conversatio
* @return Liste des conversations triées par date du dernier message
*/
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)
.list();
}
@@ -74,7 +71,6 @@ public class ConversationRepository implements PanacheRepositoryBase<Conversatio
* @return Liste des conversations avec des messages non lus
*/
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(
"(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;
}
@@ -112,7 +107,6 @@ public class ConversationRepository implements PanacheRepositoryBase<Conversatio
* @return true si la conversation a été supprimée, false sinon
*/
public boolean deleteConversation(UUID conversationId) {
System.out.println("[LOG] Suppression de la conversation ID : " + conversationId);
return deleteById(conversationId);
}
}

View File

@@ -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;
}
}

View File

@@ -4,6 +4,7 @@ import com.lions.dev.entity.establishment.EstablishmentSubscription;
import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase;
import jakarta.enterprise.context.ApplicationScoped;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
@@ -23,4 +24,16 @@ public class EstablishmentSubscriptionRepository implements PanacheRepositoryBas
return find("establishmentId = ?1 and status = ?2", establishmentId, EstablishmentSubscription.STATUS_ACTIVE)
.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);
}
}

View File

@@ -100,4 +100,12 @@ public class EventsRepository implements PanacheRepositoryBase<Events, UUID> {
.setParameter("loc", pattern)
.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);
}
}

View File

@@ -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());
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();
}
}

View File

@@ -6,6 +6,7 @@ import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase;
import io.quarkus.panache.common.Page;
import io.quarkus.panache.common.Sort;
import jakarta.enterprise.context.ApplicationScoped;
import org.jboss.logging.Logger;
import java.util.List;
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)
*/
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)
.page(Page.of(page, size))
.list();
@@ -41,7 +41,6 @@ public class MessageRepository implements PanacheRepositoryBase<Message, UUID> {
* @return Liste de tous les messages triés par date
*/
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();
}
@@ -64,7 +63,6 @@ public class MessageRepository implements PanacheRepositoryBase<Message, UUID> {
* @return Le nombre de messages mis à jour
*/
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);
}
@@ -86,7 +84,6 @@ public class MessageRepository implements PanacheRepositoryBase<Message, UUID> {
* @return Le nombre de messages supprimés
*/
public long deleteByConversationId(UUID conversationId) {
System.out.println("[LOG] Suppression de tous les messages pour la conversation " + conversationId);
return delete("conversation.id = ?1", conversationId);
}
}

View File

@@ -7,6 +7,7 @@ import io.quarkus.panache.common.Sort;
import jakarta.enterprise.context.ApplicationScoped;
import java.util.List;
import java.util.UUID;
import org.jboss.logging.Logger;
/**
* 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
*/
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();
System.out.println("[LOG] " + notifications.size() + " notification(s) trouvée(s) pour l'utilisateur ID : " + userId);
return notifications;
}
@@ -40,9 +39,7 @@ public class NotificationRepository implements PanacheRepositoryBase<Notificatio
* @return Liste des notifications non lues de l'utilisateur
*/
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();
System.out.println("[LOG] " + notifications.size() + " notification(s) non lue(s) trouvée(s) pour l'utilisateur ID : " + userId);
return notifications;
}
@@ -55,11 +52,9 @@ public class NotificationRepository implements PanacheRepositoryBase<Notificatio
* @return Liste paginée des notifications
*/
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)
.page(Page.of(page, size))
.list();
System.out.println("[LOG] " + notifications.size() + " notification(s) récupérée(s) pour la page " + page);
return notifications;
}
@@ -71,7 +66,6 @@ public class NotificationRepository implements PanacheRepositoryBase<Notificatio
*/
public long countUnreadByUserId(UUID 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;
}
@@ -82,9 +76,7 @@ public class NotificationRepository implements PanacheRepositoryBase<Notificatio
* @return Le nombre de notifications mises à jour
*/
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);
System.out.println("[LOG] " + updated + " notification(s) marquée(s) comme lue(s)");
return updated;
}
@@ -95,9 +87,7 @@ public class NotificationRepository implements PanacheRepositoryBase<Notificatio
* @return Le nombre de notifications supprimées
*/
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);
System.out.println("[LOG] " + deleted + " notification(s) supprimée(s)");
return deleted;
}
}

View File

@@ -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;
}
}

View File

@@ -7,6 +7,7 @@ import io.quarkus.panache.common.Sort;
import jakarta.enterprise.context.ApplicationScoped;
import java.util.List;
import java.util.UUID;
import org.jboss.logging.Logger;
/**
* Repository pour l'entité SocialPost.
@@ -27,11 +28,9 @@ public class SocialPostRepository implements PanacheRepositoryBase<SocialPost, U
* @return Liste paginée des posts
*/
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))
.page(Page.of(page, size))
.list();
System.out.println("[LOG] " + posts.size() + " post(s) récupéré(s)");
return posts;
}
@@ -42,9 +41,7 @@ public class SocialPostRepository implements PanacheRepositoryBase<SocialPost, U
* @return Liste des posts de l'utilisateur
*/
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();
System.out.println("[LOG] " + posts.size() + " post(s) trouvé(s) pour l'utilisateur ID : " + userId);
return posts;
}
@@ -55,10 +52,8 @@ public class SocialPostRepository implements PanacheRepositoryBase<SocialPost, U
* @return Liste des posts correspondant à la recherche
*/
public List<SocialPost> searchByContent(String query) {
System.out.println("[LOG] Recherche de posts avec la requête : " + query);
String searchPattern = "%" + query.toLowerCase() + "%";
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;
}
@@ -69,11 +64,9 @@ public class SocialPostRepository implements PanacheRepositoryBase<SocialPost, U
* @return Liste des posts les plus populaires
*/
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))
.page(0, limit)
.list();
System.out.println("[LOG] " + posts.size() + " post(s) populaire(s) récupéré(s)");
return posts;
}
@@ -87,10 +80,8 @@ public class SocialPostRepository implements PanacheRepositoryBase<SocialPost, U
* @return Liste paginée des posts des amis
*/
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()) {
System.out.println("[LOG] Aucun ami trouvé pour l'utilisateur ID : " + userId);
return List.of();
}
@@ -102,7 +93,6 @@ public class SocialPostRepository implements PanacheRepositoryBase<SocialPost, U
.page(Page.of(page, size))
.list();
System.out.println("[LOG] " + posts.size() + " post(s) récupéré(s) des amis");
return posts;
}
}

View File

@@ -7,6 +7,7 @@ import jakarta.enterprise.context.ApplicationScoped;
import java.time.LocalDateTime;
import java.util.List;
import java.util.UUID;
import org.jboss.logging.Logger;
/**
* Repository pour l'entité Story.
@@ -36,13 +37,11 @@ public class StoryRepository implements PanacheRepositoryBase<Story, UUID> {
* @return Liste paginée des stories actives
*/
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",
Sort.by("createdAt", Sort.Direction.Descending),
LocalDateTime.now())
.page(page, size)
.list();
System.out.println("[LOG] " + stories.size() + " story(ies) active(s) récupérée(s)");
return stories;
}
@@ -65,14 +64,12 @@ public class StoryRepository implements PanacheRepositoryBase<Story, UUID> {
* @return Liste paginée des stories actives de l'utilisateur
*/
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",
Sort.by("createdAt", Sort.Direction.Descending),
userId,
LocalDateTime.now())
.page(page, size)
.list();
System.out.println("[LOG] " + stories.size() + " story(ies) active(s) trouvée(s) pour l'utilisateur ID : " + userId);
return stories;
}
@@ -83,9 +80,7 @@ public class StoryRepository implements PanacheRepositoryBase<Story, UUID> {
* @return Liste de toutes les stories de l'utilisateur
*/
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();
System.out.println("[LOG] " + stories.size() + " story(ies) trouvée(s) pour l'utilisateur ID : " + userId);
return stories;
}
@@ -96,9 +91,7 @@ public class StoryRepository implements PanacheRepositoryBase<Story, UUID> {
* @return Liste des stories expirées
*/
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();
System.out.println("[LOG] " + stories.size() + " story(ies) expirée(s) trouvée(s)");
return stories;
}
@@ -109,9 +102,7 @@ public class StoryRepository implements PanacheRepositoryBase<Story, UUID> {
* @return Le nombre de stories désactivées
*/
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());
System.out.println("[LOG] " + updated + " story(ies) désactivée(s)");
return updated;
}
@@ -122,13 +113,11 @@ public class StoryRepository implements PanacheRepositoryBase<Story, UUID> {
* @return Liste des stories les plus vues
*/
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",
Sort.by("viewsCount", Sort.Direction.Descending),
LocalDateTime.now())
.page(0, limit)
.list();
System.out.println("[LOG] " + stories.size() + " story(ies) populaire(s) récupérée(s)");
return stories;
}
}

View File

@@ -8,6 +8,7 @@ import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import java.util.Optional;
import java.util.UUID;
import org.jboss.logging.Logger;
/**
* 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.
*/
public Optional<Users> findByEmail(String email) {
System.out.println("[LOG] Recherche de l'utilisateur avec l'email : " + email);
Users user = find("email", email).firstResult();
if (user != null) {
System.out.println("[LOG] Utilisateur trouvé : " + user.getEmail());
} else {
System.out.println("[LOG] Aucun utilisateur trouvé avec l'email : " + email);
}
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.
*/
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();
boolean exists = count > 0;
System.out.println("[LOG] Utilisateur existe : " + exists);
return exists;
}
@@ -93,13 +89,10 @@ public class UsersRepository implements PanacheRepositoryBase<Users, UUID> {
* @return true si l'utilisateur a été supprimé, sinon false.
*/
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
boolean deleted = deletedCount > 0; // Convertir en boolean
if (deleted) {
System.out.println("[LOG] Utilisateur avec l'ID " + id + " supprimé avec succès.");
} else {
System.out.println("[LOG] Aucune suppression, utilisateur avec l'ID " + id + " introuvable.");
}
return deleted;
}

View File

@@ -44,6 +44,12 @@ public class EstablishmentRatingResource {
@PathParam("establishmentId") String establishmentId,
@QueryParam("userId") String userIdStr,
@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);
try {
@@ -82,6 +88,12 @@ public class EstablishmentRatingResource {
@PathParam("establishmentId") String establishmentId,
@QueryParam("userId") String userIdStr,
@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);
try {

View File

@@ -2,9 +2,13 @@ package com.lions.dev.resource;
import com.lions.dev.dto.request.establishment.EstablishmentCreateRequestDTO;
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.entity.establishment.Establishment;
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.UsersRepository;
import com.lions.dev.service.EstablishmentService;
@@ -43,6 +47,12 @@ public class EstablishmentResource {
@Inject
EstablishmentRepository establishmentRepository;
@Inject
BusinessHoursRepository businessHoursRepository;
@Inject
EstablishmentAmenityRepository establishmentAmenityRepository;
private static final Logger LOG = Logger.getLogger(EstablishmentResource.class);
// *********** 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 ***********
@GET

View File

@@ -1,7 +1,6 @@
package com.lions.dev.resource;
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.EventReadManyByIdRequestDTO;
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.EventUpdateResponseDTO;
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.events.Events;
import com.lions.dev.entity.users.Users;

View File

@@ -18,6 +18,7 @@ import jakarta.inject.Inject;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
@Path("/media")
@@ -81,11 +82,16 @@ public class FileUploadResource {
// Note: En production, vous devriez utiliser une URL publique (CDN, S3, etc.)
String fileUrl = buildFileUrl(finalFileName, uriInfo);
String thumbnailUrl = null;
// Pour les vidéos, on pourrait générer un thumbnail ici
if ("video".equalsIgnoreCase(mediaType)) {
// TODO: Générer un thumbnail pour les vidéos
thumbnailUrl = fileUrl; // Placeholder
Optional<java.nio.file.Path> thumbPath = fileService.generateVideoThumbnail(savedFilePath);
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

View File

@@ -36,6 +36,7 @@ public class NotificationResource {
/**
* 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
* @return Liste des notifications de l'utilisateur

View File

@@ -2,6 +2,7 @@ package com.lions.dev.resource;
import com.lions.dev.dto.PasswordResetRequest;
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.UserCreateRequestDTO;
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.entity.users.Users;
import com.lions.dev.exception.UserNotFoundException;
import com.lions.dev.service.PasswordResetService;
import com.lions.dev.service.UsersService;
import jakarta.inject.Inject;
import jakarta.transaction.Transactional;
@@ -42,6 +44,9 @@ public class UsersResource {
@Inject
UsersService userService;
@Inject
PasswordResetService passwordResetService;
@ConfigProperty(name = "afterwork.super-admin.api-key", defaultValue = "")
Optional<String> superAdminApiKey;
@@ -300,18 +305,8 @@ public class UsersResource {
LOG.info("Demande de reinitialisation de mot de passe pour l'email : " + request.getEmail());
try {
// Rechercher l'utilisateur par email
Users user = userService.findByEmail(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());
}
// Le service gère tout : création du token et envoi de l'email
passwordResetService.initiatePasswordReset(request.getEmail());
// Toujours retourner 200 pour ne pas reveler si l'email existe
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).
* 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();
}
}
}

View File

@@ -7,10 +7,17 @@ import jakarta.inject.Inject;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import org.eclipse.microprofile.config.inject.ConfigProperty;
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.).
* Si wave.webhook.secret est configuré, la requête doit contenir le header X-Wave-Signature (HMAC-SHA256 du body).
*/
@Path("/webhooks/wave")
@Consumes(MediaType.APPLICATION_JSON)
@@ -18,15 +25,32 @@ import org.jboss.logging.Logger;
public class WaveWebhookResource {
private static final Logger LOG = Logger.getLogger(WaveWebhookResource.class);
private static final String SIGNATURE_HEADER = "X-Wave-Signature";
@Inject
WavePaymentService wavePaymentService;
@ConfigProperty(name = "wave.webhook.secret", defaultValue = "")
Optional<String> webhookSecret;
private final ObjectMapper objectMapper = new ObjectMapper();
@POST
public Response handleWebhook(String payload) {
public Response handleWebhook(
@HeaderParam(SIGNATURE_HEADER) String signature,
String payload) {
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);
wavePaymentService.handleWebhook(node);
return Response.ok().build();
@@ -35,4 +59,28 @@ public class WaveWebhookResource {
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;
}
}

View File

@@ -1,5 +1,6 @@
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.response.booking.ReservationResponseDTO;
import com.lions.dev.entity.booking.Booking;
@@ -11,22 +12,35 @@ import com.lions.dev.repository.UsersRepository;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
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.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;
@ApplicationScoped
public class BookingService {
private static final Logger LOG = Logger.getLogger(BookingService.class);
@Inject
BookingRepository bookingRepository;
@Inject
EstablishmentRepository establishmentRepository;
@Inject
UsersRepository usersRepository;
@Inject
EmailService emailService;
@Inject
@Channel("notifications")
Emitter<NotificationEvent> notificationEmitter;
private static final DateTimeFormatter ISO = DateTimeFormatter.ISO_DATE_TIME;
@@ -58,18 +72,121 @@ public class BookingService {
booking.setStatus("PENDING");
booking.setSpecialRequests(dto.getNotes());
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);
}
/**
* 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
public ReservationResponseDTO cancelReservation(UUID id) {
Booking booking = bookingRepository.findById(id);
if (booking == null) return null;
booking.setStatus("CANCELLED");
bookingRepository.persist(booking);
// Notifier l'utilisateur et le manager
notifyBookingCancellation(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
public void deleteReservation(UUID id) {
bookingRepository.deleteById(id);

View 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);
}
}
}

View File

@@ -31,6 +31,9 @@ public class EstablishmentRatingService {
@Inject
UsersRepository usersRepository;
@Inject
com.lions.dev.service.NotificationService notificationService;
private static final Logger LOG = Logger.getLogger(EstablishmentRatingService.class);
/**
@@ -74,6 +77,24 @@ public class EstablishmentRatingService {
// Mettre à jour les statistiques de l'établissement
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());
return rating;
}

View File

@@ -9,6 +9,8 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import org.jboss.logging.Logger;
/**
@@ -64,5 +66,63 @@ public class FileService {
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;
}
}

View File

@@ -90,6 +90,21 @@ public class FriendshipService {
Friendship friendship = new Friendship(user, friend, FriendshipStatus.PENDING);
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)
try {
Map<String, Object> notificationData = new HashMap<>();
@@ -113,7 +128,8 @@ public class FriendshipService {
}
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());
}
// Retourner la réponse avec les informations de la relation d'amitié
return new FriendshipCreateOneResponseDTO(friendship);
// Retourner la réponse avec les IDs déjà chargés (évite LazyInitializationException)
Users user = friendship.getUser();
Users friendUser = friendship.getFriend();
return new FriendshipCreateOneResponseDTO(friendship, user.getId(), friendUser.getId());
}
/**

View File

@@ -14,6 +14,7 @@ import io.smallrye.reactive.messaging.kafka.api.OutgoingKafkaRecordMetadata;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.transaction.Transactional;
import org.jboss.logging.Logger;
import java.util.HashMap;
import java.util.List;
@@ -25,12 +26,12 @@ import java.util.UUID;
*
* 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.
*
* Tous les logs nécessaires pour la traçabilité sont intégrés.
*/
@ApplicationScoped
public class MessageService {
private static final Logger logger = Logger.getLogger(MessageService.class);
@Inject
MessageRepository messageRepository;
@@ -65,7 +66,7 @@ public class MessageService {
String content,
String messageType,
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
Users sender = usersRepository.findById(senderId);
@@ -91,7 +92,7 @@ public class MessageService {
// Persister le 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
conversation.updateLastMessage(message);
@@ -112,9 +113,9 @@ public class MessageService {
recipientId,
null
);
System.out.println("[LOG] Notification créée pour le destinataire");
logger.info("[MessageService] Notification créée pour le destinataire");
} 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)
@@ -149,12 +150,12 @@ public class MessageService {
event,
() -> java.util.concurrent.CompletableFuture.completedFuture(null), // ack
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
}
).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)
try {
@@ -181,9 +182,9 @@ public class MessageService {
throwable -> java.util.concurrent.CompletableFuture.completedFuture(null)
).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) {
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
}
} catch (Exception e) {
@@ -202,7 +203,7 @@ public class MessageService {
* @throws UserNotFoundException Si l'utilisateur n'existe pas
*/
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);
if (user == null) {
@@ -221,7 +222,7 @@ public class MessageService {
* @return Liste paginée des messages
*/
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);
}
@@ -232,7 +233,7 @@ public class MessageService {
* @return La conversation
*/
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);
}
@@ -245,7 +246,7 @@ public class MessageService {
* @throws UserNotFoundException Si l'un des utilisateurs n'existe pas
*/
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 user2 = usersRepository.findById(user2Id);
@@ -265,7 +266,7 @@ public class MessageService {
*/
@Transactional
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);
if (message == null) {
@@ -309,12 +310,12 @@ public class MessageService {
throwable -> java.util.concurrent.CompletableFuture.completedFuture(null)
).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) {
System.out.println("[ERROR] Erreur publication confirmation lecture : " + e.getMessage());
logger.error("[MessageService] Erreur publication confirmation lecture : " + e.getMessage());
}
} catch (Exception e) {
System.out.println("[ERROR] Erreur envoi confirmation lecture : " + e.getMessage());
logger.error("[MessageService] Erreur envoi confirmation lecture : " + e.getMessage());
}
return message;
@@ -329,7 +330,7 @@ public class MessageService {
*/
@Transactional
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);
if (conversation == null) {
@@ -358,7 +359,7 @@ public class MessageService {
* @return Le nombre de messages non lus
*/
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);
}
@@ -370,7 +371,7 @@ public class MessageService {
*/
@Transactional
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);
if (message == null) {
@@ -389,7 +390,7 @@ public class MessageService {
*/
@Transactional
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
messageRepository.deleteByConversationId(conversationId);

View File

@@ -1,5 +1,6 @@
package com.lions.dev.service;
import com.lions.dev.dto.events.NotificationEvent;
import com.lions.dev.entity.events.Events;
import com.lions.dev.entity.notification.Notification;
import com.lions.dev.entity.users.Users;
@@ -10,20 +11,25 @@ import com.lions.dev.repository.UsersRepository;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
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.Map;
import java.util.UUID;
/**
* Service de gestion des notifications.
*
*
* Ce service contient la logique métier pour la création, récupération,
* mise à jour et suppression des notifications.
*
* Tous les logs nécessaires pour la traçabilité sont intégrés.
*/
@ApplicationScoped
public class NotificationService {
private static final Logger logger = Logger.getLogger(NotificationService.class);
@Inject
NotificationRepository notificationRepository;
@@ -33,6 +39,10 @@ public class NotificationService {
@Inject
EventsRepository eventsRepository;
@Inject
@Channel("notifications")
Emitter<NotificationEvent> notificationEmitter;
/**
* Récupère toutes les notifications d'un utilisateur.
*
@@ -41,16 +51,16 @@ public class NotificationService {
* @throws UserNotFoundException Si l'utilisateur n'existe pas
*/
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);
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);
}
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;
}
@@ -64,11 +74,11 @@ public class NotificationService {
* @throws UserNotFoundException Si l'utilisateur n'existe pas
*/
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);
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);
}
@@ -93,11 +103,11 @@ public class NotificationService {
String type,
UUID userId,
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);
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);
}
@@ -107,15 +117,50 @@ public class NotificationService {
Events event = eventsRepository.findById(eventId);
if (event != null) {
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);
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;
}
/**
* 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.
*
@@ -124,17 +169,17 @@ public class NotificationService {
*/
@Transactional
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);
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);
}
notification.markAsRead();
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;
}
@@ -147,16 +192,16 @@ public class NotificationService {
*/
@Transactional
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);
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);
}
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;
}
@@ -168,16 +213,16 @@ public class NotificationService {
*/
@Transactional
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);
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;
}
notificationRepository.delete(notification);
System.out.println("[LOG] Notification supprimée avec succès");
logger.info("[NotificationService] Notification supprimée avec succès");
return true;
}
@@ -188,11 +233,11 @@ public class NotificationService {
* @return La notification trouvée
*/
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);
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);
}

View 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();
}
}

View File

@@ -51,7 +51,7 @@ public class PresenceService {
// Broadcast présence aux autres utilisateurs
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
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) {
user.updatePresence();
usersRepository.persist(user);
System.out.println("[PRESENCE] Heartbeat reçu pour utilisateur " + userId);
Log.debug("[PRESENCE] Heartbeat reçu pour utilisateur " + userId);
}
}

View File

@@ -1,7 +1,6 @@
package com.lions.dev.service;
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.users.Users;
import com.lions.dev.exception.UserNotFoundException;
@@ -14,6 +13,7 @@ import org.eclipse.microprofile.reactive.messaging.Emitter;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.transaction.Transactional;
import org.jboss.logging.Logger;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -22,15 +22,15 @@ import java.util.stream.Collectors;
/**
* Service de gestion des posts sociaux.
*
*
* Ce service contient la logique métier pour la création, récupération,
* mise à jour et suppression des posts sociaux.
*
* Tous les logs nécessaires pour la traçabilité sont intégrés.
*/
@ApplicationScoped
public class SocialPostService {
private static final Logger logger = Logger.getLogger(SocialPostService.class);
@Inject
SocialPostRepository socialPostRepository;
@@ -55,7 +55,7 @@ public class SocialPostService {
* @return Liste paginée des posts
*/
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);
}
@@ -67,11 +67,11 @@ public class SocialPostService {
* @throws UserNotFoundException Si l'utilisateur n'existe pas
*/
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);
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);
}
@@ -89,11 +89,11 @@ public class SocialPostService {
*/
@Transactional
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);
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);
}
@@ -103,7 +103,7 @@ public class SocialPostService {
}
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
try {
@@ -128,9 +128,9 @@ public class SocialPostService {
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) {
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;
@@ -143,11 +143,11 @@ public class SocialPostService {
* @return Le post trouvé
*/
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);
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);
}
@@ -164,11 +164,11 @@ public class SocialPostService {
*/
@Transactional
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);
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);
}
@@ -178,7 +178,7 @@ public class SocialPostService {
}
socialPostRepository.persist(post);
System.out.println("[LOG] Post mis à jour avec succès");
logger.info("[SocialPostService] Post mis à jour avec succès");
return post;
}
@@ -190,16 +190,16 @@ public class SocialPostService {
*/
@Transactional
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);
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;
}
socialPostRepository.delete(post);
System.out.println("[LOG] Post supprimé avec succès");
logger.info("[SocialPostService] Post supprimé avec succès");
return true;
}
@@ -210,7 +210,7 @@ public class SocialPostService {
* @return Liste des posts correspondant à la recherche
*/
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);
}
@@ -223,17 +223,35 @@ public class SocialPostService {
*/
@Transactional
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);
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);
}
post.incrementLikes();
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)
try {
Map<String, Object> reactionData = new HashMap<>();
@@ -252,9 +270,9 @@ public class SocialPostService {
);
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) {
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
}
@@ -271,17 +289,38 @@ public class SocialPostService {
*/
@Transactional
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);
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);
}
post.incrementComments();
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)
try {
Users commenter = usersRepository.findById(userId);
@@ -304,9 +343,9 @@ public class SocialPostService {
);
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) {
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
}
@@ -322,11 +361,11 @@ public class SocialPostService {
*/
@Transactional
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);
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);
}
@@ -348,9 +387,9 @@ public class SocialPostService {
);
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) {
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
}
@@ -367,11 +406,11 @@ public class SocialPostService {
* @throws UserNotFoundException Si l'utilisateur n'existe pas
*/
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);
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);
}
@@ -389,7 +428,7 @@ public class SocialPostService {
.distinct()
.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
return socialPostRepository.findPostsByFriends(userId, friendIds, page, size);

View File

@@ -1,15 +1,24 @@
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.Story;
import com.lions.dev.entity.users.Users;
import com.lions.dev.exception.UserNotFoundException;
import com.lions.dev.repository.FriendshipRepository;
import com.lions.dev.repository.StoryRepository;
import com.lions.dev.repository.UsersRepository;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
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.Map;
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,
* mise à jour et suppression des stories.
*
* Tous les logs nécessaires pour la traçabilité sont intégrés.
*/
@ApplicationScoped
public class StoryService {
private static final Logger logger = Logger.getLogger(StoryService.class);
@Inject
StoryRepository storyRepository;
@Inject
UsersRepository usersRepository;
@Inject
FriendshipRepository friendshipRepository;
@Inject
@Channel("notifications")
Emitter<NotificationEvent> notificationEmitter;
/**
* Récupère toutes les stories actives (non expirées).
*
@@ -46,7 +62,7 @@ public class StoryService {
* @return Liste paginée des stories actives
*/
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);
}
@@ -71,11 +87,11 @@ public class StoryService {
* @throws UserNotFoundException Si l'utilisateur n'existe pas
*/
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);
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);
}
@@ -96,23 +112,23 @@ public class StoryService {
*/
@Transactional
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
Users user = usersRepository.findById(userId);
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);
}
// Validation des paramètres
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");
}
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");
}
@@ -128,10 +144,52 @@ public class StoryService {
}
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;
}
/**
* 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.
*
@@ -140,11 +198,11 @@ public class StoryService {
* @throws IllegalArgumentException Si la story n'existe pas
*/
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);
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);
}
@@ -162,19 +220,19 @@ public class StoryService {
*/
@Transactional
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
Users viewer = usersRepository.findById(viewerId);
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);
}
// Validation de la story
Story story = storyRepository.findById(storyId);
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);
}
@@ -182,14 +240,47 @@ public class StoryService {
boolean isNewView = story.markAsViewed(viewerId);
if (isNewView) {
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 {
System.out.println("[LOG] Story déjà vue par cet utilisateur");
logger.info("[StoryService] Story déjà vue par cet utilisateur");
}
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.
*
@@ -198,16 +289,16 @@ public class StoryService {
*/
@Transactional
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);
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;
}
storyRepository.delete(story);
System.out.println("[LOG] Story supprimée avec succès");
logger.info("[StoryService] Story supprimée avec succès");
return true;
}
@@ -219,7 +310,7 @@ public class StoryService {
*/
@Transactional
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();
}
@@ -230,7 +321,7 @@ public class StoryService {
* @return Liste des stories les plus vues
*/
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);
}
}

View File

@@ -7,6 +7,7 @@ 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.List;
import java.util.Optional;
@@ -19,6 +20,8 @@ import java.util.UUID;
@ApplicationScoped
public class UsersService {
private static final Logger logger = Logger.getLogger(UsersService.class);
@Inject
UsersRepository usersRepository;
@@ -66,7 +69,7 @@ public class UsersService {
);
usersRepository.persist(user);
System.out.println("[LOG] Utilisateur créé : " + user.getEmail());
logger.info("Utilisateur créé : " + user.getEmail());
return user;
}
@@ -82,7 +85,7 @@ public class UsersService {
public Users updateUser(UUID id, UserCreateRequestDTO userCreateRequestDTO) {
Users existingUser = usersRepository.findById(id);
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);
}
@@ -117,7 +120,7 @@ public class UsersService {
}
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;
}
/**
@@ -132,7 +135,7 @@ public class UsersService {
public Users updateUserProfileImage(UUID id, String profileImageUrl) {
Users existingUser = usersRepository.findById(id);
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);
}
@@ -140,7 +143,7 @@ public class UsersService {
existingUser.setProfileImageUrl(profileImageUrl);
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;
}
@@ -166,10 +169,10 @@ public class UsersService {
public Users authenticateUser(String email, String password) {
Optional<Users> userOptional = usersRepository.findByEmail(email);
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.");
}
System.out.println("[LOG] Utilisateur authentifié : " + email);
logger.info("Utilisateur authentifié : " + email);
return userOptional.get();
}
@@ -192,10 +195,10 @@ public class UsersService {
public Users getUserById(UUID id) {
Users user = usersRepository.findById(id);
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);
}
System.out.println("[LOG] Utilisateur trouvé avec l'ID : " + id);
logger.debug("Utilisateur trouvé avec l'ID : " + id);
return user;
}
@@ -210,13 +213,13 @@ public class UsersService {
public void resetPassword(UUID id, String newPassword) {
Users user = usersRepository.findById(id);
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é.");
}
user.setPassword(newPassword); // v2.0 - Hachage automatique
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) {
boolean deleted = usersRepository.deleteById(id);
if (deleted) {
System.out.println("[LOG] Utilisateur supprimé avec succès : " + id);
logger.info("Utilisateur supprimé avec succès : " + id);
} 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;
}
@@ -245,10 +248,10 @@ public class UsersService {
public Users getUserByEmail(String email) {
Optional<Users> userOptional = usersRepository.findByEmail(email);
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);
}
System.out.println("[LOG] Utilisateur trouvé avec l'email : " + email);
logger.debug("Utilisateur trouvé avec l'email : " + email);
return userOptional.get();
}
@@ -280,7 +283,28 @@ public class UsersService {
}
user.setRole(newRole);
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;
}
}

View File

@@ -48,6 +48,8 @@ public class WavePaymentService {
EstablishmentPaymentRepository paymentRepository;
@Inject
UsersRepository usersRepository;
@Inject
EmailService emailService;
@ConfigProperty(name = "wave.api.url", defaultValue = "https://api.wave.com")
String waveApiUrl;
@@ -184,8 +186,32 @@ public class WavePaymentService {
manager.setActive(true);
usersRepository.persist(manager);
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)
|| "payment.failed".equals(eventType)) {
sub.setStatus(EstablishmentSubscription.STATUS_CANCELLED);
@@ -199,6 +225,17 @@ public class WavePaymentService {
manager.setActive(false);
usersRepository.persist(manager);
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 -> {
if ("payment.completed".equals(eventType)) {
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);
} else if ("payment.failed".equals(eventType)) {
payment.setStatus(EstablishmentPayment.STATUS_FAILED);
}
paymentRepository.persist(payment);
});

View File

@@ -16,7 +16,6 @@ public class InputConverter {
try {
return Integer.parseInt(value);
} catch (NumberFormatException e) {
System.out.println("[ERROR] Impossible de convertir la valeur en entier : " + value);
return null;
}
}
@@ -33,7 +32,6 @@ public class InputConverter {
} else if ("false".equalsIgnoreCase(value)) {
return false;
}
System.out.println("[ERROR] Valeur non valide pour un booléen : " + value);
return false;
}
}

View File

@@ -42,7 +42,6 @@ public class SecureStorage {
* @param data Les données à effacer.
*/
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
// gérer la suppression logique ou l'effacement de données dans un fichier sécurisé.
}

View File

@@ -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);
}
}