feat(backend): Ajout complet des fonctionnalités Chat, Social, Story et Notifications
Implémentation complète de toutes les fonctionnalités backend : ## Nouvelles Fonctionnalités ### Chat (Messagerie Instantanée) - Entities : Conversation, Message - DTOs : ConversationResponseDTO, MessageResponseDTO, SendMessageRequestDTO - Resources : MessageResource (endpoints REST) - Services : MessageService (logique métier) - Repositories : ConversationRepository, MessageRepository - WebSocket : ChatWebSocket (temps réel) ### Social (Publications Sociales) - Entities : SocialPost, SocialComment, SocialLike - DTOs : SocialPostResponseDTO, CreateSocialPostRequestDTO - Resources : SocialPostResource - Services : SocialPostService - Repositories : SocialPostRepository ### Story (Stories temporaires) - Entities : Story, StoryView - DTOs : StoryResponseDTO, CreateStoryRequestDTO - Resources : StoryResource - Services : StoryService - Repositories : StoryRepository ### Notifications (Temps Réel) - Entities : Notification - DTOs : NotificationResponseDTO - Resources : NotificationResource - Services : NotificationService, PresenceService - Repositories : NotificationRepository - WebSocket : NotificationWebSocket (temps réel) ## Améliorations ### Users & Friendship - Mise à jour UserResponseDTO avec nouveaux champs - Amélioration FriendshipResource avec séparation demandes envoyées/reçues - FriendSuggestionResponseDTO pour suggestions d'amis - Optimisations dans UsersService et FriendshipService ### Events - Améliorations EventsResource et EventService - Optimisations EventsRepository ### Configuration - Mise à jour application.properties - Configuration docker-compose.yml - Dockerfile pour développement ## Fichiers Modifiés - .dockerignore, .gitignore - README.md - docker-compose.yml - Configuration Maven wrapper
This commit is contained in:
@@ -13,11 +13,15 @@ import com.lions.dev.exception.FriendshipNotFoundException;
|
||||
import com.lions.dev.exception.UserNotFoundException;
|
||||
import com.lions.dev.repository.FriendshipRepository;
|
||||
import com.lions.dev.repository.UsersRepository;
|
||||
import com.lions.dev.websocket.NotificationWebSocket;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.transaction.Transactional;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import org.jboss.logging.Logger;
|
||||
@@ -33,6 +37,8 @@ public class FriendshipService {
|
||||
FriendshipRepository friendshipRepository; // Injecte le repository des amitiés
|
||||
@Inject
|
||||
UsersRepository usersRepository; // Injecte le repository des utilisateurs
|
||||
@Inject
|
||||
NotificationService notificationService; // Injecte le service de notifications
|
||||
|
||||
private static final Logger logger = Logger.getLogger(FriendshipService.class);
|
||||
|
||||
@@ -56,6 +62,12 @@ public class FriendshipService {
|
||||
throw new UserNotFoundException("Utilisateur avec l'ID " + notFoundId + " introuvable.");
|
||||
}
|
||||
|
||||
// VALIDATION: Empêcher l'utilisateur de s'ajouter lui-même comme ami
|
||||
if (user.getId().equals(friend.getId())) {
|
||||
logger.error("[ERROR] Tentative d'ajout de soi-même comme ami bloquée pour l'utilisateur : " + user.getId());
|
||||
throw new IllegalArgumentException("Vous ne pouvez pas vous ajouter vous-même comme ami.");
|
||||
}
|
||||
|
||||
// Vérifier s'il existe déjà une relation d'amitié
|
||||
Friendship existingFriendship = friendshipRepository.findByUsers(user, friend).orElse(null);
|
||||
if (existingFriendship != null) {
|
||||
@@ -67,6 +79,26 @@ public class FriendshipService {
|
||||
Friendship friendship = new Friendship(user, friend, FriendshipStatus.PENDING);
|
||||
friendshipRepository.persist(friendship);
|
||||
|
||||
// TEMPS RÉEL: Notifier le destinataire via WebSocket
|
||||
try {
|
||||
Map<String, Object> notificationData = new HashMap<>();
|
||||
notificationData.put("requestId", friendship.getId().toString());
|
||||
notificationData.put("senderId", user.getId().toString());
|
||||
notificationData.put("senderName", user.getPrenoms() + " " + user.getNom());
|
||||
notificationData.put("senderProfileImage", user.getProfileImageUrl() != null ? user.getProfileImageUrl() : "");
|
||||
|
||||
NotificationWebSocket.sendNotificationToUser(
|
||||
friend.getId(),
|
||||
"friend_request_received",
|
||||
notificationData
|
||||
);
|
||||
|
||||
logger.info("[LOG] Notification WebSocket envoyée au destinataire : " + friend.getId());
|
||||
} catch (Exception e) {
|
||||
logger.error("[ERROR] Erreur lors de l'envoi de la notification WebSocket : " + e.getMessage(), e);
|
||||
// Ne pas bloquer la demande d'amitié si le WebSocket échoue
|
||||
}
|
||||
|
||||
logger.info("[LOG] Demande d'amitié envoyée avec succès.");
|
||||
return new FriendshipCreateOneResponseDTO(friendship);
|
||||
}
|
||||
@@ -107,6 +139,60 @@ public class FriendshipService {
|
||||
// Log de succès
|
||||
logger.info(String.format("[LOG] Demande d'amitié acceptée avec succès pour l'ID: %s", friendshipId)); // Correctement formaté
|
||||
|
||||
// TEMPS RÉEL: Notifier l'émetteur de la demande via WebSocket
|
||||
try {
|
||||
Users user = friendship.getUser();
|
||||
Users friend = friendship.getFriend();
|
||||
String friendName = friend.getPrenoms() + " " + friend.getNom();
|
||||
|
||||
Map<String, Object> notificationData = new HashMap<>();
|
||||
notificationData.put("acceptedBy", friendName);
|
||||
notificationData.put("friendshipId", friendshipId.toString());
|
||||
notificationData.put("accepterId", friend.getId().toString());
|
||||
notificationData.put("accepterProfileImage", friend.getProfileImageUrl() != null ? friend.getProfileImageUrl() : "");
|
||||
|
||||
NotificationWebSocket.sendNotificationToUser(
|
||||
user.getId(),
|
||||
"friend_request_accepted",
|
||||
notificationData
|
||||
);
|
||||
|
||||
logger.info("[LOG] Notification WebSocket d'acceptation envoyée à : " + user.getId());
|
||||
} catch (Exception e) {
|
||||
logger.error("[ERROR] Erreur lors de l'envoi de la notification WebSocket d'acceptation : " + e.getMessage(), e);
|
||||
// Ne pas bloquer l'acceptation si le WebSocket échoue
|
||||
}
|
||||
|
||||
// Créer des notifications pour les deux utilisateurs
|
||||
try {
|
||||
Users user = friendship.getUser();
|
||||
Users friend = friendship.getFriend();
|
||||
String userName = user.getPrenoms() + " " + user.getNom();
|
||||
String friendName = friend.getPrenoms() + " " + friend.getNom();
|
||||
|
||||
// Notification pour l'utilisateur qui a envoyé la demande
|
||||
notificationService.createNotification(
|
||||
"Demande d'amitié acceptée",
|
||||
friendName + " a accepté votre demande d'amitié",
|
||||
"friend",
|
||||
user.getId(),
|
||||
null
|
||||
);
|
||||
|
||||
// Notification pour l'utilisateur qui a accepté la demande
|
||||
notificationService.createNotification(
|
||||
"Nouveaux amis",
|
||||
"Vous êtes maintenant ami(e) avec " + userName,
|
||||
"friend",
|
||||
friend.getId(),
|
||||
null
|
||||
);
|
||||
|
||||
logger.info("[LOG] Notifications d'amitié créées pour les deux utilisateurs");
|
||||
} catch (Exception e) {
|
||||
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);
|
||||
}
|
||||
@@ -126,6 +212,26 @@ public class FriendshipService {
|
||||
friendship.setStatus(FriendshipStatus.REJECTED);
|
||||
friendshipRepository.persist(friendship);
|
||||
|
||||
// TEMPS RÉEL: Notifier l'émetteur de la demande via WebSocket (optionnel selon UX)
|
||||
try {
|
||||
Users user = friendship.getUser();
|
||||
|
||||
Map<String, Object> notificationData = new HashMap<>();
|
||||
notificationData.put("friendshipId", friendshipId.toString());
|
||||
notificationData.put("rejectedAt", System.currentTimeMillis());
|
||||
|
||||
NotificationWebSocket.sendNotificationToUser(
|
||||
user.getId(),
|
||||
"friend_request_rejected",
|
||||
notificationData
|
||||
);
|
||||
|
||||
logger.info("[LOG] Notification WebSocket de rejet envoyée à : " + user.getId());
|
||||
} catch (Exception e) {
|
||||
logger.error("[ERROR] Erreur lors de l'envoi de la notification WebSocket de rejet : " + e.getMessage(), e);
|
||||
// Ne pas bloquer le rejet si le WebSocket échoue
|
||||
}
|
||||
|
||||
logger.info("[LOG] Demande d'amitié rejetée.");
|
||||
}
|
||||
|
||||
@@ -297,4 +403,126 @@ public class FriendshipService {
|
||||
|
||||
return friendships.stream().map(FriendshipReadStatusResponseDTO::new).toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupérer les suggestions d'amis pour un utilisateur.
|
||||
*
|
||||
* L'algorithme suggère des utilisateurs basés sur :
|
||||
* 1. Amis d'amis (utilisateurs qui ont des amis en commun)
|
||||
* 2. Utilisateurs récents (qui ne sont ni amis ni ont de demandes en attente)
|
||||
*
|
||||
* @param userId ID de l'utilisateur.
|
||||
* @param limit Nombre maximum de suggestions à retourner.
|
||||
* @return Liste des suggestions d'amis.
|
||||
*/
|
||||
public List<com.lions.dev.dto.response.users.FriendSuggestionResponseDTO> getFriendSuggestions(UUID userId, int limit) {
|
||||
logger.info("[LOG] Récupération des suggestions d'amis pour l'utilisateur : " + userId);
|
||||
|
||||
Users user = usersRepository.findById(userId);
|
||||
if (user == null) {
|
||||
logger.error("[ERROR] Utilisateur non trouvé.");
|
||||
throw new UserNotFoundException("Utilisateur introuvable.");
|
||||
}
|
||||
|
||||
// Récupérer tous les amis actuels de l'utilisateur (ACCEPTED)
|
||||
// Utiliser une taille de page élevée pour récupérer tous les résultats
|
||||
List<Friendship> currentFriendships = friendshipRepository.findByUserAndStatus(user, FriendshipStatus.ACCEPTED, 0, 10000);
|
||||
Set<UUID> currentFriendIds = new HashSet<>();
|
||||
for (Friendship friendship : currentFriendships) {
|
||||
// Ajouter les IDs des amis (que l'utilisateur ait envoyé ou reçu la demande)
|
||||
if (friendship.getUser().getId().equals(userId)) {
|
||||
currentFriendIds.add(friendship.getFriend().getId());
|
||||
} else {
|
||||
currentFriendIds.add(friendship.getUser().getId());
|
||||
}
|
||||
}
|
||||
|
||||
// Récupérer toutes les demandes en attente pour exclure ces utilisateurs
|
||||
List<Friendship> pendingFriendships = friendshipRepository.findByUserAndStatus(user, FriendshipStatus.PENDING, 0, 10000);
|
||||
Set<UUID> pendingUserIds = new HashSet<>();
|
||||
for (Friendship friendship : pendingFriendships) {
|
||||
if (friendship.getUser().getId().equals(userId)) {
|
||||
pendingUserIds.add(friendship.getFriend().getId());
|
||||
} else {
|
||||
pendingUserIds.add(friendship.getUser().getId());
|
||||
}
|
||||
}
|
||||
|
||||
// Map pour compter les amis en commun
|
||||
Map<UUID, Integer> mutualFriendsCount = new HashMap<>();
|
||||
|
||||
// Pour chaque ami, trouver ses amis (amis d'amis)
|
||||
for (UUID friendId : currentFriendIds) {
|
||||
Users friend = usersRepository.findById(friendId);
|
||||
if (friend == null) continue;
|
||||
|
||||
List<Friendship> friendsOfFriend = friendshipRepository.findByUserAndStatus(friend, FriendshipStatus.ACCEPTED, 0, 10000);
|
||||
|
||||
for (Friendship fof : friendsOfFriend) {
|
||||
UUID potentialFriendId;
|
||||
if (fof.getUser().getId().equals(friendId)) {
|
||||
potentialFriendId = fof.getFriend().getId();
|
||||
} else {
|
||||
potentialFriendId = fof.getUser().getId();
|
||||
}
|
||||
|
||||
// Exclure l'utilisateur lui-même, ses amis actuels et les demandes en attente
|
||||
if (!potentialFriendId.equals(userId) &&
|
||||
!currentFriendIds.contains(potentialFriendId) &&
|
||||
!pendingUserIds.contains(potentialFriendId)) {
|
||||
mutualFriendsCount.put(potentialFriendId, mutualFriendsCount.getOrDefault(potentialFriendId, 0) + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Trier par nombre d'amis en commun (décroissant)
|
||||
List<Map.Entry<UUID, Integer>> sortedSuggestions = mutualFriendsCount.entrySet()
|
||||
.stream()
|
||||
.sorted(Map.Entry.<UUID, Integer>comparingByValue().reversed())
|
||||
.limit(limit)
|
||||
.toList();
|
||||
|
||||
// Créer les DTOs
|
||||
List<com.lions.dev.dto.response.users.FriendSuggestionResponseDTO> suggestions = new ArrayList<>();
|
||||
for (Map.Entry<UUID, Integer> entry : sortedSuggestions) {
|
||||
Users suggestedUser = usersRepository.findById(entry.getKey());
|
||||
if (suggestedUser != null) {
|
||||
String reason = entry.getValue() > 1
|
||||
? entry.getValue() + " amis en commun"
|
||||
: "1 ami en commun";
|
||||
suggestions.add(new com.lions.dev.dto.response.users.FriendSuggestionResponseDTO(
|
||||
suggestedUser,
|
||||
entry.getValue(),
|
||||
reason
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// Si pas assez de suggestions, ajouter des utilisateurs récents
|
||||
if (suggestions.size() < limit) {
|
||||
int remaining = limit - suggestions.size();
|
||||
Set<UUID> excludedIds = new HashSet<>(currentFriendIds);
|
||||
excludedIds.addAll(pendingUserIds);
|
||||
excludedIds.add(userId);
|
||||
excludedIds.addAll(mutualFriendsCount.keySet());
|
||||
|
||||
// Récupérer des utilisateurs récents qui ne sont pas dans les exclusions
|
||||
List<Users> recentUsers = usersRepository.findAll()
|
||||
.stream()
|
||||
.filter(u -> !excludedIds.contains(u.getId()))
|
||||
.limit(remaining)
|
||||
.toList();
|
||||
|
||||
for (Users recentUser : recentUsers) {
|
||||
suggestions.add(new com.lions.dev.dto.response.users.FriendSuggestionResponseDTO(
|
||||
recentUser,
|
||||
0,
|
||||
"Nouvel utilisateur"
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
logger.info("[LOG] " + suggestions.size() + " suggestions d'amis générées.");
|
||||
return suggestions;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user