feat(frontend): Séparation des demandes d'amitié envoyées et reçues
- Ajout de deux endpoints distincts dans Urls: getSentFriendRequestsWithUserId et getReceivedFriendRequestsWithUserId - Ajout de méthodes dans FriendsRepository et FriendsRepositoryImpl pour récupérer séparément les demandes envoyées et reçues - Ajout de la méthode cancelFriendRequest pour annuler une demande envoyée - Modification de FriendsProvider pour gérer deux listes distinctes: sentRequests et receivedRequests - Mise à jour de FriendsScreen pour afficher deux sections: - Demandes reçues: avec boutons Accepter/Rejeter - Demandes envoyées: avec bouton Annuler uniquement - Correction du mapping JSON dans FriendRequest.fromJson (userNom/userPrenoms correctement mappés) - Amélioration de FriendRequestCard pour gérer les deux types de demandes - Correction de la validation d'URL d'image dans FriendDetailScreen - Support du champ uuid dans UserModel.fromJson pour compatibilité backend
This commit is contained in:
@@ -1,45 +1,223 @@
|
||||
import '../../core/constants/env_config.dart';
|
||||
import '../../domain/entities/user.dart';
|
||||
|
||||
/// Modèle représentant l'utilisateur dans l'application AfterWork.
|
||||
/// Ce modèle est utilisé pour la conversion JSON et l'interaction avec l'API.
|
||||
/// Modèle de données pour les utilisateurs (Data Transfer Object).
|
||||
///
|
||||
/// Cette classe est responsable de la sérialisation/désérialisation
|
||||
/// avec l'API backend et convertit vers/depuis l'entité de domaine [User].
|
||||
///
|
||||
/// **Usage:**
|
||||
/// ```dart
|
||||
/// // Depuis JSON
|
||||
/// final user = UserModel.fromJson(jsonData);
|
||||
///
|
||||
/// // Vers JSON
|
||||
/// final json = user.toJson();
|
||||
///
|
||||
/// // Vers entité de domaine
|
||||
/// final entity = user.toEntity();
|
||||
/// ```
|
||||
class UserModel extends User {
|
||||
UserModel({
|
||||
required String userId,
|
||||
required String userLastName,
|
||||
required String userFirstName,
|
||||
required String email,
|
||||
required String motDePasse,
|
||||
required String profileImageUrl,
|
||||
}) : super(
|
||||
userId: userId,
|
||||
userLastName: userLastName,
|
||||
userFirstName: userFirstName,
|
||||
email: email,
|
||||
motDePasse: motDePasse,
|
||||
profileImageUrl: profileImageUrl,
|
||||
);
|
||||
/// Crée une nouvelle instance de [UserModel].
|
||||
///
|
||||
/// [userId] L'identifiant unique de l'utilisateur
|
||||
/// [userLastName] Le nom de famille de l'utilisateur
|
||||
/// [userFirstName] Le prénom de l'utilisateur
|
||||
/// [email] L'adresse email de l'utilisateur
|
||||
/// [motDePasse] Le mot de passe (hashé côté serveur)
|
||||
/// [profileImageUrl] L'URL de l'image de profil
|
||||
/// [eventsCount] Le nombre d'événements créés (optionnel)
|
||||
/// [friendsCount] Le nombre d'amis (optionnel)
|
||||
/// [postsCount] Le nombre de posts (optionnel)
|
||||
/// [visitedPlacesCount] Le nombre de lieux visités (optionnel)
|
||||
const UserModel({
|
||||
required super.userId,
|
||||
required super.userLastName,
|
||||
required super.userFirstName,
|
||||
required super.email,
|
||||
required super.motDePasse,
|
||||
required super.profileImageUrl,
|
||||
super.eventsCount,
|
||||
super.friendsCount,
|
||||
super.postsCount,
|
||||
super.visitedPlacesCount,
|
||||
});
|
||||
|
||||
/// Factory pour créer un `UserModel` à partir d'un JSON reçu depuis l'API.
|
||||
/// Crée un [UserModel] à partir d'un JSON reçu depuis l'API.
|
||||
///
|
||||
/// [json] Les données JSON à parser
|
||||
///
|
||||
/// Returns un [UserModel] avec les données parsées
|
||||
///
|
||||
/// **Note:** Les valeurs par défaut sont utilisées si des champs sont manquants.
|
||||
///
|
||||
/// **Exemple:**
|
||||
/// ```dart
|
||||
/// final json = {
|
||||
/// 'userId': '123',
|
||||
/// 'nom': 'Doe',
|
||||
/// 'prenoms': 'John',
|
||||
/// 'email': 'john@example.com',
|
||||
/// };
|
||||
/// final user = UserModel.fromJson(json);
|
||||
/// ```
|
||||
factory UserModel.fromJson(Map<String, dynamic> json) {
|
||||
return UserModel(
|
||||
userId: json['userId'] ?? '',
|
||||
userLastName: json['nom'] ?? 'Inconnu',
|
||||
userFirstName: json['prenoms'] ?? 'Inconnu',
|
||||
email: json['email'] ?? 'inconnu@example.com',
|
||||
motDePasse: json['motDePasse'] ?? '',
|
||||
profileImageUrl: json['profileImageUrl'] ?? '',
|
||||
);
|
||||
try {
|
||||
// Le backend peut renvoyer 'uuid' ou 'userId', on accepte les deux
|
||||
final userId = _parseString(json, 'userId', '') != ''
|
||||
? _parseString(json, 'userId', '')
|
||||
: _parseString(json, 'uuid', '');
|
||||
|
||||
return UserModel(
|
||||
userId: userId.isNotEmpty ? userId : (json['uuid']?.toString() ?? json['userId']?.toString() ?? ''),
|
||||
userLastName: _parseString(json, 'nom', 'Inconnu'),
|
||||
userFirstName: _parseString(json, 'prenoms', 'Inconnu'),
|
||||
email: _parseString(json, 'email', ''),
|
||||
motDePasse: _parseString(json, 'motDePasse', ''),
|
||||
profileImageUrl: _parseString(json, 'profileImageUrl', ''),
|
||||
eventsCount: _parseInt(json, 'eventsCount') ?? 0,
|
||||
friendsCount: _parseInt(json, 'friendsCount') ?? 0,
|
||||
postsCount: _parseInt(json, 'postsCount') ?? 0,
|
||||
visitedPlacesCount: _parseInt(json, 'visitedPlacesCount') ?? 0,
|
||||
);
|
||||
} catch (e) {
|
||||
if (EnvConfig.enableDetailedLogs) {
|
||||
print('[UserModel] Erreur lors du parsing JSON: $e');
|
||||
}
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
/// Convertit le `UserModel` en JSON pour l'envoi vers l'API.
|
||||
/// Parse une valeur string depuis le JSON avec valeur par défaut.
|
||||
static String _parseString(
|
||||
Map<String, dynamic> json,
|
||||
String key,
|
||||
String defaultValue,
|
||||
) {
|
||||
final value = json[key];
|
||||
if (value == null) return defaultValue;
|
||||
return value.toString();
|
||||
}
|
||||
|
||||
/// Parse une valeur int depuis le JSON (optionnel).
|
||||
static int? _parseInt(Map<String, dynamic> json, String key) {
|
||||
final value = json[key];
|
||||
if (value == null) return null;
|
||||
if (value is int) return value;
|
||||
if (value is String) {
|
||||
return int.tryParse(value);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Convertit ce [UserModel] en JSON pour l'envoi vers l'API.
|
||||
///
|
||||
/// Returns une [Map] contenant les données de l'utilisateur
|
||||
///
|
||||
/// **Note:** Le mot de passe est envoyé en clair (hashé côté serveur).
|
||||
///
|
||||
/// **Exemple:**
|
||||
/// ```dart
|
||||
/// final user = UserModel(...);
|
||||
/// final json = user.toJson();
|
||||
/// // Envoyer json à l'API
|
||||
/// ```
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'id': userId,
|
||||
final json = <String, dynamic>{
|
||||
if (userId.isNotEmpty) 'id': userId,
|
||||
'nom': userLastName,
|
||||
'prenoms': userFirstName,
|
||||
'email': email,
|
||||
'motDePasse': motDePasse, // Mot de passe en clair (comme demandé temporairement)
|
||||
'profileImageUrl':profileImageUrl,
|
||||
if (motDePasse.isNotEmpty) 'motDePasse': motDePasse,
|
||||
if (profileImageUrl.isNotEmpty) 'profileImageUrl': profileImageUrl,
|
||||
};
|
||||
|
||||
// Ajouter les compteurs optionnels s'ils sont présents
|
||||
if (eventsCount != null) {
|
||||
json['eventsCount'] = eventsCount;
|
||||
}
|
||||
if (friendsCount != null) {
|
||||
json['friendsCount'] = friendsCount;
|
||||
}
|
||||
if (postsCount != null) {
|
||||
json['postsCount'] = postsCount;
|
||||
}
|
||||
if (visitedPlacesCount != null) {
|
||||
json['visitedPlacesCount'] = visitedPlacesCount;
|
||||
}
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
/// Convertit ce modèle vers une entité de domaine [User].
|
||||
///
|
||||
/// Returns une instance de [User] avec les mêmes données
|
||||
///
|
||||
/// **Exemple:**
|
||||
/// ```dart
|
||||
/// final model = UserModel.fromJson(json);
|
||||
/// final entity = model.toEntity();
|
||||
/// ```
|
||||
User toEntity() {
|
||||
return User(
|
||||
userId: userId,
|
||||
userLastName: userLastName,
|
||||
userFirstName: userFirstName,
|
||||
email: email,
|
||||
motDePasse: motDePasse,
|
||||
profileImageUrl: profileImageUrl,
|
||||
eventsCount: eventsCount,
|
||||
friendsCount: friendsCount,
|
||||
postsCount: postsCount,
|
||||
visitedPlacesCount: visitedPlacesCount,
|
||||
);
|
||||
}
|
||||
|
||||
/// Crée une copie de ce [UserModel] avec des valeurs modifiées.
|
||||
///
|
||||
/// Tous les paramètres sont optionnels. Seuls les paramètres fournis
|
||||
/// seront modifiés dans la nouvelle instance.
|
||||
///
|
||||
/// **Exemple:**
|
||||
/// ```dart
|
||||
/// final updated = user.copyWith(
|
||||
/// userFirstName: 'Jane',
|
||||
/// profileImageUrl: 'https://example.com/new-image.jpg',
|
||||
/// );
|
||||
/// ```
|
||||
UserModel copyWith({
|
||||
String? userId,
|
||||
String? userLastName,
|
||||
String? userFirstName,
|
||||
String? email,
|
||||
String? motDePasse,
|
||||
String? profileImageUrl,
|
||||
int? eventsCount,
|
||||
int? friendsCount,
|
||||
int? postsCount,
|
||||
int? visitedPlacesCount,
|
||||
}) {
|
||||
return UserModel(
|
||||
userId: userId ?? this.userId,
|
||||
userLastName: userLastName ?? this.userLastName,
|
||||
userFirstName: userFirstName ?? this.userFirstName,
|
||||
email: email ?? this.email,
|
||||
motDePasse: motDePasse ?? this.motDePasse,
|
||||
profileImageUrl: profileImageUrl ?? this.profileImageUrl,
|
||||
eventsCount: eventsCount ?? this.eventsCount,
|
||||
friendsCount: friendsCount ?? this.friendsCount,
|
||||
postsCount: postsCount ?? this.postsCount,
|
||||
visitedPlacesCount: visitedPlacesCount ?? this.visitedPlacesCount,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'UserModel('
|
||||
'userId: $userId, '
|
||||
'name: $userFirstName $userLastName, '
|
||||
'email: $email'
|
||||
')';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,19 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:logger/logger.dart';
|
||||
import '../../domain/entities/friend.dart';
|
||||
|
||||
import '../../data/repositories/friends_repository_impl.dart';
|
||||
import '../../data/services/secure_storage.dart';
|
||||
import '../../domain/entities/friend.dart';
|
||||
import '../../domain/entities/friend_request.dart';
|
||||
|
||||
/// [FriendsProvider] est un `ChangeNotifier` qui gère la logique de gestion des amis.
|
||||
/// Il interagit avec le [FriendsRepositoryImpl] pour effectuer des appels API et gérer
|
||||
/// la liste des amis de l'utilisateur, avec une gestion avancée de la pagination,
|
||||
/// du statut des amis et de la gestion des erreurs.
|
||||
class FriendsProvider with ChangeNotifier {
|
||||
class FriendsProvider with ChangeNotifier { // Nombre d'amis à récupérer par page
|
||||
|
||||
/// Constructeur de [FriendsProvider] qui nécessite l'instance d'un [FriendsRepositoryImpl].
|
||||
FriendsProvider({required this.friendsRepository});
|
||||
final FriendsRepositoryImpl friendsRepository;
|
||||
final Logger _logger = Logger(); // Utilisation du logger pour une traçabilité complète des actions.
|
||||
|
||||
@@ -16,15 +22,32 @@ class FriendsProvider with ChangeNotifier {
|
||||
bool _isLoading = false; // Indicateur de chargement
|
||||
bool _hasMore = true; // Indicateur de pagination
|
||||
int _currentPage = 0; // Numéro de la page actuelle pour la pagination
|
||||
final int _friendsPerPage = 10; // Nombre d'amis à récupérer par page
|
||||
final int _friendsPerPage = 10;
|
||||
|
||||
/// Constructeur de [FriendsProvider] qui nécessite l'instance d'un [FriendsRepositoryImpl].
|
||||
FriendsProvider({required this.friendsRepository});
|
||||
// Liste des demandes d'amitié envoyées
|
||||
List<FriendRequest> _sentRequests = [];
|
||||
bool _isLoadingSentRequests = false;
|
||||
int _currentSentRequestPage = 0;
|
||||
|
||||
// Liste des demandes d'amitié reçues
|
||||
List<FriendRequest> _receivedRequests = [];
|
||||
bool _isLoadingReceivedRequests = false;
|
||||
int _currentReceivedRequestPage = 0;
|
||||
|
||||
final int _requestsPerPage = 10;
|
||||
|
||||
// Getters pour accéder à l'état actuel des données
|
||||
bool get isLoading => _isLoading;
|
||||
bool get hasMore => _hasMore;
|
||||
List<Friend> get friendsList => _friendsList;
|
||||
List<FriendRequest> get sentRequests => _sentRequests;
|
||||
List<FriendRequest> get receivedRequests => _receivedRequests;
|
||||
bool get isLoadingSentRequests => _isLoadingSentRequests;
|
||||
bool get isLoadingReceivedRequests => _isLoadingReceivedRequests;
|
||||
|
||||
// Pour compatibilité avec l'ancien code
|
||||
List<FriendRequest> get pendingRequests => _receivedRequests;
|
||||
bool get isLoadingRequests => _isLoadingReceivedRequests;
|
||||
|
||||
/// Récupère la liste des amis pour un utilisateur donné avec pagination.
|
||||
///
|
||||
@@ -63,10 +86,10 @@ class FriendsProvider with ChangeNotifier {
|
||||
_logger.i('[LOG] Plus d\'amis à charger.');
|
||||
} else {
|
||||
// Ajout des amis à la liste, en excluant l'utilisateur connecté
|
||||
for (var friend in newFriends) {
|
||||
for (final friend in newFriends) {
|
||||
if (friend.friendId != userId) {
|
||||
_friendsList.add(friend);
|
||||
_logger.i("[LOG] Ami ajouté : ID = ${friend.friendId}, Nom = ${friend.friendFirstName} ${friend.friendLastName}");
|
||||
_logger.i('[LOG] Ami ajouté : ID = ${friend.friendId}, Nom = ${friend.friendFirstName} ${friend.friendLastName}');
|
||||
} else {
|
||||
_logger.w("[WARN] L'utilisateur connecté est exclu de la liste des amis : ${friend.friendId}");
|
||||
}
|
||||
@@ -171,4 +194,192 @@ class FriendsProvider with ChangeNotifier {
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
/// Ajoute un nouvel ami.
|
||||
///
|
||||
/// [friendId] : L'identifiant unique de l'ami à ajouter.
|
||||
///
|
||||
/// Cette méthode :
|
||||
/// - Loggue chaque étape.
|
||||
/// - Envoie la demande d'ami via l'API.
|
||||
/// - Rafraîchit la liste des amis si l'ajout réussit.
|
||||
Future<void> addFriend(String friendId) async {
|
||||
try {
|
||||
// Récupérer le userId de l'utilisateur actuel
|
||||
final currentUserId = await _getCurrentUserId();
|
||||
if (currentUserId == null || currentUserId.isEmpty) {
|
||||
throw Exception('Utilisateur non connecté');
|
||||
}
|
||||
|
||||
_logger.i('[LOG] Ajout de l\'ami: userId=$currentUserId, friendId=$friendId');
|
||||
await friendsRepository.addFriend(currentUserId, friendId);
|
||||
_logger.i('[LOG] Demande d\'ami envoyée avec succès');
|
||||
|
||||
// Rafraîchir la liste des amis après l'ajout
|
||||
// Note: L'ami ne sera visible qu'après acceptation de la demande
|
||||
} catch (e) {
|
||||
_logger.e('[ERROR] Erreur lors de l\'ajout de l\'ami : $e');
|
||||
rethrow; // Propager l'erreur pour que l'UI puisse l'afficher
|
||||
} finally {
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
/// Récupère l'ID de l'utilisateur actuel depuis le stockage sécurisé
|
||||
Future<String?> _getCurrentUserId() async {
|
||||
try {
|
||||
final secureStorage = SecureStorage();
|
||||
return await secureStorage.getUserId();
|
||||
} catch (e) {
|
||||
_logger.e('[ERROR] Erreur lors de la récupération de l\'userId : $e');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// Récupère les demandes d'amitié en attente pour l'utilisateur actuel (compatibilité).
|
||||
Future<void> fetchPendingRequests({bool loadMore = false}) async {
|
||||
await fetchReceivedRequests(loadMore: loadMore);
|
||||
}
|
||||
|
||||
/// Récupère les demandes d'amitié envoyées par l'utilisateur actuel.
|
||||
Future<void> fetchSentRequests({bool loadMore = false}) async {
|
||||
try {
|
||||
final currentUserId = await _getCurrentUserId();
|
||||
if (currentUserId == null || currentUserId.isEmpty) {
|
||||
throw Exception('Utilisateur non connecté');
|
||||
}
|
||||
|
||||
if (!loadMore) {
|
||||
_currentSentRequestPage = 0;
|
||||
_sentRequests = [];
|
||||
}
|
||||
|
||||
_isLoadingSentRequests = true;
|
||||
notifyListeners();
|
||||
|
||||
final page = loadMore ? _currentSentRequestPage + 1 : 0;
|
||||
final requests = await friendsRepository.getSentFriendRequests(
|
||||
currentUserId,
|
||||
page,
|
||||
_requestsPerPage,
|
||||
);
|
||||
|
||||
if (loadMore) {
|
||||
_sentRequests.addAll(requests);
|
||||
_currentSentRequestPage = page;
|
||||
} else {
|
||||
_sentRequests = requests;
|
||||
_currentSentRequestPage = 0;
|
||||
}
|
||||
|
||||
_logger.i('[LOG] ${requests.length} demandes d\'amitié envoyées récupérées');
|
||||
} catch (e) {
|
||||
_logger.e('[ERROR] Erreur lors de la récupération des demandes envoyées : $e');
|
||||
rethrow;
|
||||
} finally {
|
||||
_isLoadingSentRequests = false;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
/// Récupère les demandes d'amitié reçues par l'utilisateur actuel.
|
||||
Future<void> fetchReceivedRequests({bool loadMore = false}) async {
|
||||
try {
|
||||
final currentUserId = await _getCurrentUserId();
|
||||
if (currentUserId == null || currentUserId.isEmpty) {
|
||||
throw Exception('Utilisateur non connecté');
|
||||
}
|
||||
|
||||
if (!loadMore) {
|
||||
_currentReceivedRequestPage = 0;
|
||||
_receivedRequests = [];
|
||||
}
|
||||
|
||||
_isLoadingReceivedRequests = true;
|
||||
notifyListeners();
|
||||
|
||||
final page = loadMore ? _currentReceivedRequestPage + 1 : 0;
|
||||
final requests = await friendsRepository.getReceivedFriendRequests(
|
||||
currentUserId,
|
||||
page,
|
||||
_requestsPerPage,
|
||||
);
|
||||
|
||||
if (loadMore) {
|
||||
_receivedRequests.addAll(requests);
|
||||
_currentReceivedRequestPage = page;
|
||||
} else {
|
||||
_receivedRequests = requests;
|
||||
_currentReceivedRequestPage = 0;
|
||||
}
|
||||
|
||||
_logger.i('[LOG] ${requests.length} demandes d\'amitié reçues récupérées');
|
||||
} catch (e) {
|
||||
_logger.e('[ERROR] Erreur lors de la récupération des demandes reçues : $e');
|
||||
rethrow;
|
||||
} finally {
|
||||
_isLoadingReceivedRequests = false;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
/// Accepte une demande d'amitié.
|
||||
Future<void> acceptFriendRequest(String friendshipId) async {
|
||||
try {
|
||||
_logger.i('[LOG] Acceptation de la demande d\'amitié: $friendshipId');
|
||||
await friendsRepository.acceptFriendRequest(friendshipId);
|
||||
|
||||
// Retirer la demande de la liste des demandes reçues
|
||||
_receivedRequests.removeWhere((req) => req.friendshipId == friendshipId);
|
||||
|
||||
// Rafraîchir la liste des amis
|
||||
final currentUserId = await _getCurrentUserId();
|
||||
if (currentUserId != null) {
|
||||
await fetchFriends(currentUserId);
|
||||
}
|
||||
|
||||
_logger.i('[LOG] Demande d\'amitié acceptée avec succès');
|
||||
} catch (e) {
|
||||
_logger.e('[ERROR] Erreur lors de l\'acceptation de la demande : $e');
|
||||
rethrow;
|
||||
} finally {
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
/// Rejette une demande d'amitié.
|
||||
Future<void> rejectFriendRequest(String friendshipId) async {
|
||||
try {
|
||||
_logger.i('[LOG] Rejet de la demande d\'amitié: $friendshipId');
|
||||
await friendsRepository.rejectFriendRequest(friendshipId);
|
||||
|
||||
// Retirer la demande de la liste des demandes reçues
|
||||
_receivedRequests.removeWhere((req) => req.friendshipId == friendshipId);
|
||||
|
||||
_logger.i('[LOG] Demande d\'amitié rejetée avec succès');
|
||||
} catch (e) {
|
||||
_logger.e('[ERROR] Erreur lors du rejet de la demande : $e');
|
||||
rethrow;
|
||||
} finally {
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
/// Annule une demande d'amitié envoyée.
|
||||
Future<void> cancelFriendRequest(String friendshipId) async {
|
||||
try {
|
||||
_logger.i('[LOG] Annulation de la demande d\'amitié: $friendshipId');
|
||||
await friendsRepository.cancelFriendRequest(friendshipId);
|
||||
|
||||
// Retirer la demande de la liste des demandes envoyées
|
||||
_sentRequests.removeWhere((req) => req.friendshipId == friendshipId);
|
||||
|
||||
_logger.i('[LOG] Demande d\'amitié annulée avec succès');
|
||||
} catch (e) {
|
||||
_logger.e('[ERROR] Erreur lors de l\'annulation de la demande : $e');
|
||||
rethrow;
|
||||
} finally {
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:afterwork/domain/entities/friend.dart';
|
||||
import '../../domain/entities/friend.dart';
|
||||
import '../../domain/entities/friend_request.dart';
|
||||
|
||||
/// Interface [FriendsRepository] définissant les méthodes pour gérer les amis.
|
||||
/// Cette interface permet de séparer la logique métier des appels API et de la gestion des données.
|
||||
@@ -17,10 +18,11 @@ abstract class FriendsRepository {
|
||||
|
||||
/// Envoie une demande pour ajouter un nouvel ami via l'API.
|
||||
///
|
||||
/// [friend] : Objet [Friend] représentant l'ami à ajouter.
|
||||
/// [userId] : Identifiant unique de l'utilisateur qui envoie la demande.
|
||||
/// [friendId] : Identifiant unique de l'ami à ajouter.
|
||||
///
|
||||
/// Retourne un `Future<void>`. En cas d'erreur, l'implémentation peut lancer une exception.
|
||||
Future<void> addFriend(Friend friend);
|
||||
Future<void> addFriend(String userId, String friendId);
|
||||
|
||||
/// Supprime un ami existant via l'API.
|
||||
///
|
||||
@@ -45,4 +47,52 @@ abstract class FriendsRepository {
|
||||
///
|
||||
/// Retourne un `Future<void>`. En cas d'erreur, l'implémentation peut lancer une exception.
|
||||
Future<void> updateFriendStatus(String friendId, String status);
|
||||
|
||||
/// Récupère les demandes d'amitié en attente pour un utilisateur.
|
||||
///
|
||||
/// [userId] : Identifiant unique de l'utilisateur.
|
||||
/// [page] : Numéro de la page pour la pagination.
|
||||
/// [size] : Nombre d'éléments par page.
|
||||
///
|
||||
/// Retourne une liste de demandes d'amitié en attente.
|
||||
Future<List<FriendRequest>> getPendingFriendRequests(String userId, int page, int size);
|
||||
|
||||
/// Récupère les demandes d'amitié envoyées par un utilisateur.
|
||||
///
|
||||
/// [userId] : Identifiant unique de l'utilisateur.
|
||||
/// [page] : Numéro de la page pour la pagination.
|
||||
/// [size] : Nombre d'éléments par page.
|
||||
///
|
||||
/// Retourne une liste de demandes d'amitié envoyées.
|
||||
Future<List<FriendRequest>> getSentFriendRequests(String userId, int page, int size);
|
||||
|
||||
/// Récupère les demandes d'amitié reçues par un utilisateur.
|
||||
///
|
||||
/// [userId] : Identifiant unique de l'utilisateur.
|
||||
/// [page] : Numéro de la page pour la pagination.
|
||||
/// [size] : Nombre d'éléments par page.
|
||||
///
|
||||
/// Retourne une liste de demandes d'amitié reçues.
|
||||
Future<List<FriendRequest>> getReceivedFriendRequests(String userId, int page, int size);
|
||||
|
||||
/// Accepte une demande d'amitié.
|
||||
///
|
||||
/// [friendshipId] : Identifiant unique de la relation d'amitié.
|
||||
///
|
||||
/// Retourne un `Future<void>`. En cas d'erreur, l'implémentation peut lancer une exception.
|
||||
Future<void> acceptFriendRequest(String friendshipId);
|
||||
|
||||
/// Rejette une demande d'amitié.
|
||||
///
|
||||
/// [friendshipId] : Identifiant unique de la relation d'amitié.
|
||||
///
|
||||
/// Retourne un `Future<void>`. En cas d'erreur, l'implémentation peut lancer une exception.
|
||||
Future<void> rejectFriendRequest(String friendshipId);
|
||||
|
||||
/// Annule une demande d'amitié envoyée (supprime la relation).
|
||||
///
|
||||
/// [friendshipId] : Identifiant unique de la relation d'amitié.
|
||||
///
|
||||
/// Retourne un `Future<void>`. En cas d'erreur, l'implémentation peut lancer une exception.
|
||||
Future<void> cancelFriendRequest(String friendshipId);
|
||||
}
|
||||
|
||||
@@ -1,178 +1,685 @@
|
||||
import 'dart:convert';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:logger/logger.dart';
|
||||
|
||||
import '../../core/constants/env_config.dart';
|
||||
import '../../core/constants/urls.dart';
|
||||
import '../../core/errors/exceptions.dart';
|
||||
import '../../domain/entities/friend.dart';
|
||||
import '../../domain/entities/friend_request.dart';
|
||||
import 'friends_repository.dart';
|
||||
|
||||
/// Implémentation de [FriendsRepository] pour gérer les appels API relatifs aux amis.
|
||||
/// Chaque action est loguée pour une traçabilité complète et une gestion des erreurs avancée.
|
||||
///
|
||||
/// Cette classe gère toutes les opérations sur les amis via l'API backend,
|
||||
/// avec gestion d'erreurs robuste, timeouts, et validation des réponses.
|
||||
///
|
||||
/// **Usage:**
|
||||
/// ```dart
|
||||
/// final repository = FriendsRepositoryImpl(client: http.Client());
|
||||
/// final friends = await repository.fetchFriends('user123', 0, 20);
|
||||
/// ```
|
||||
class FriendsRepositoryImpl implements FriendsRepository {
|
||||
final http.Client client;
|
||||
final Logger _logger = Logger(); // Logger pour suivre toutes les actions.
|
||||
|
||||
/// Crée une nouvelle instance de [FriendsRepositoryImpl].
|
||||
///
|
||||
/// [client] Le client HTTP à utiliser pour les requêtes
|
||||
FriendsRepositoryImpl({required this.client});
|
||||
|
||||
/// Récupère la liste paginée des amis pour un utilisateur donné via l'API.
|
||||
///
|
||||
/// [userId] : Identifiant unique de l'utilisateur.
|
||||
/// [page] : Page actuelle pour la pagination.
|
||||
/// [size] : Nombre d'amis par page.
|
||||
///
|
||||
/// Retourne une liste d'objets [Friend] ou une liste vide en cas d'erreur.
|
||||
@override
|
||||
Future<List<Friend>> fetchFriends(String userId, int page, int size) async {
|
||||
/// Client HTTP pour effectuer les requêtes réseau
|
||||
final http.Client client;
|
||||
|
||||
/// Headers par défaut pour les requêtes
|
||||
static const Map<String, String> _defaultHeaders = {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
};
|
||||
|
||||
/// Timeout pour les requêtes réseau
|
||||
Duration get _timeout => Duration(seconds: EnvConfig.networkTimeout);
|
||||
|
||||
// ============================================================================
|
||||
// MÉTHODES PRIVÉES UTILITAIRES
|
||||
// ============================================================================
|
||||
|
||||
/// Effectue une requête HTTP avec gestion d'erreurs et timeout.
|
||||
Future<http.Response> _performRequest(
|
||||
String method,
|
||||
Uri uri, {
|
||||
Map<String, String>? headers,
|
||||
Object? body,
|
||||
}) async {
|
||||
try {
|
||||
_logger.i("[LOG] Chargement des amis pour l'utilisateur : $userId, page : $page, taille : $size");
|
||||
|
||||
final uri = Uri.parse('${Urls.baseUrl}/friends/list/$userId?page=$page&size=$size');
|
||||
_logger.d('[LOG] URL appelée : $uri');
|
||||
|
||||
final response = await client.get(uri);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
_logger.i("[LOG] Liste des amis récupérée avec succès.");
|
||||
final List<dynamic> friendsJson = json.decode(response.body);
|
||||
_logger.i("[LOG] Nombre d'amis récupérés (excluant l'utilisateur lui-même) : ${friendsJson.length}");
|
||||
|
||||
return friendsJson.map((json) {
|
||||
_logger.i("[LOG] Conversion JSON -> Friend : $json");
|
||||
final friend = Friend.fromJson(json as Map<String, dynamic>);
|
||||
_logger.i("[LOG] Création d'un objet Friend : ID = ${friend.friendId}, Nom = ${friend.friendFirstName} ${friend.friendLastName}");
|
||||
return friend;
|
||||
}).toList();
|
||||
} else {
|
||||
_logger.e("[ERROR] Échec de la récupération des amis. Code HTTP : ${response.statusCode}");
|
||||
return [];
|
||||
}
|
||||
} catch (e) {
|
||||
_logger.e("[ERROR] Exception lors de la récupération des amis : $e");
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/// Envoie une demande pour ajouter un nouvel ami via l'API.
|
||||
///
|
||||
/// [friend] : Objet [Friend] représentant l'ami à ajouter.
|
||||
///
|
||||
/// Loggue chaque étape et lève une exception en cas d'erreur.
|
||||
@override
|
||||
Future<void> addFriend(Friend friend) async {
|
||||
try {
|
||||
_logger.i("[LOG] Tentative d'ajout de l'ami : ${friend.friendFirstName} ${friend.friendLastName}");
|
||||
|
||||
final uri = Uri.parse('${Urls.baseUrl}/friends/send');
|
||||
final response = await client.post(
|
||||
uri,
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: json.encode(friend.toJson()),
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
_logger.i("[LOG] Ami ajouté avec succès : ${friend.friendId}");
|
||||
} else {
|
||||
_logger.e("[ERROR] Échec lors de l'ajout de l'ami. Code HTTP : ${response.statusCode}");
|
||||
throw Exception("Erreur lors de l'ajout de l'ami");
|
||||
}
|
||||
} catch (e) {
|
||||
_logger.e("[ERROR] Exception lors de l'ajout de l'ami : $e");
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
/// Supprime un ami existant via l'API.
|
||||
///
|
||||
/// [friendId] : Identifiant unique de l'ami à supprimer.
|
||||
///
|
||||
/// Loggue l'action et lève une exception en cas d'erreur.
|
||||
@override
|
||||
Future<void> removeFriend(String friendId) async {
|
||||
try {
|
||||
_logger.i("[LOG] Tentative de suppression de l'ami avec l'ID : $friendId");
|
||||
|
||||
final uri = Uri.parse('${Urls.baseUrl}/friends/$friendId');
|
||||
final response = await client.delete(uri);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
_logger.i("[LOG] Ami supprimé avec succès : $friendId");
|
||||
} else {
|
||||
_logger.e("[ERROR] Échec lors de la suppression de l'ami. Code HTTP : ${response.statusCode}");
|
||||
throw Exception("Erreur lors de la suppression de l'ami");
|
||||
}
|
||||
} catch (e) {
|
||||
_logger.e("[ERROR] Exception lors de la suppression de l'ami : $e");
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
/// Récupère les détails d'un ami en utilisant son identifiant `friendId`.
|
||||
///
|
||||
/// [friendId] : Identifiant unique de l'ami.
|
||||
/// [userId] : Identifiant unique de l'utilisateur connecté.
|
||||
///
|
||||
/// Retourne un `Future<Friend?>` avec les informations de l'ami ou `null` en cas d'échec.
|
||||
@override
|
||||
Future<Friend?> getFriendDetails(String friendId, String userId) async {
|
||||
try {
|
||||
_logger.i("[LOG] Récupération des détails de l'ami avec ID : $friendId pour l'utilisateur : $userId");
|
||||
|
||||
final uri = Uri.parse('${Urls.baseUrl}/friends/details');
|
||||
_logger.d("[LOG] URL pour les détails de l'ami : $uri");
|
||||
|
||||
final headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
final requestHeaders = {
|
||||
..._defaultHeaders,
|
||||
if (headers != null) ...headers,
|
||||
};
|
||||
|
||||
http.Response response;
|
||||
|
||||
switch (method.toUpperCase()) {
|
||||
case 'GET':
|
||||
response = await client
|
||||
.get(uri, headers: requestHeaders)
|
||||
.timeout(_timeout);
|
||||
break;
|
||||
case 'POST':
|
||||
response = await client
|
||||
.post(uri, headers: requestHeaders, body: body)
|
||||
.timeout(_timeout);
|
||||
break;
|
||||
case 'DELETE':
|
||||
response = await client
|
||||
.delete(uri, headers: requestHeaders)
|
||||
.timeout(_timeout);
|
||||
break;
|
||||
case 'PATCH':
|
||||
response = await client
|
||||
.patch(uri, headers: requestHeaders, body: body)
|
||||
.timeout(_timeout);
|
||||
break;
|
||||
default:
|
||||
throw ArgumentError('Méthode HTTP non supportée: $method');
|
||||
}
|
||||
|
||||
return response;
|
||||
} on SocketException {
|
||||
throw ServerException(
|
||||
'Erreur de connexion réseau. Vérifiez votre connexion Internet.',
|
||||
statusCode: null,
|
||||
);
|
||||
} on HttpException catch (e) {
|
||||
throw ServerException(
|
||||
'Erreur HTTP: ${e.message}',
|
||||
statusCode: null,
|
||||
);
|
||||
} on FormatException catch (e) {
|
||||
throw ServerException(
|
||||
'Erreur de format de réponse: ${e.message}',
|
||||
statusCode: null,
|
||||
);
|
||||
} catch (e) {
|
||||
if (e is ServerException) rethrow;
|
||||
throw ServerException(
|
||||
'Erreur inattendue: $e',
|
||||
statusCode: null,
|
||||
originalError: e,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse une réponse JSON et gère les erreurs.
|
||||
dynamic _parseJsonResponse(
|
||||
http.Response response,
|
||||
List<int> expectedStatusCodes,
|
||||
) {
|
||||
if (!expectedStatusCodes.contains(response.statusCode)) {
|
||||
_handleErrorResponse(response);
|
||||
}
|
||||
|
||||
try {
|
||||
if (response.body.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
return json.decode(response.body);
|
||||
} on FormatException catch (e) {
|
||||
throw ServerException(
|
||||
'Erreur de parsing JSON: ${e.message}',
|
||||
statusCode: response.statusCode,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Gère les erreurs de réponse HTTP.
|
||||
void _handleErrorResponse(http.Response response) {
|
||||
String errorMessage;
|
||||
|
||||
try {
|
||||
if (response.body.isNotEmpty) {
|
||||
final errorBody = json.decode(response.body);
|
||||
|
||||
// Gérer le format Bean Validation (Quarkus)
|
||||
if (errorBody is Map && errorBody.containsKey('attributeName')) {
|
||||
final attributeName = errorBody['attributeName'] as String? ?? 'champ';
|
||||
final value = errorBody['value'] as String?;
|
||||
final objectName = errorBody['objectName'] as String? ?? '';
|
||||
|
||||
// Construire un message d'erreur plus clair
|
||||
if (attributeName == 'friendId' && value != null) {
|
||||
errorMessage = 'L\'identifiant "$value" n\'est pas valide. Veuillez utiliser l\'ID utilisateur (UUID) et non l\'email.';
|
||||
} else {
|
||||
errorMessage = 'Erreur de validation sur le champ "$attributeName"';
|
||||
if (value != null) {
|
||||
errorMessage += ': valeur "$value" invalide';
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Essayer plusieurs formats de réponse d'erreur standard
|
||||
errorMessage = errorBody['message'] as String? ??
|
||||
errorBody['error'] as String? ??
|
||||
errorBody['errorMessage'] as String? ??
|
||||
(errorBody is Map && errorBody.isNotEmpty
|
||||
? errorBody.values.first.toString()
|
||||
: 'Erreur serveur inconnue');
|
||||
}
|
||||
|
||||
// Log détaillé pour le débogage
|
||||
if (EnvConfig.enableDetailedLogs) {
|
||||
_log('Réponse d\'erreur du serveur (${response.statusCode}): ${response.body}');
|
||||
_log('Message d\'erreur extrait: $errorMessage');
|
||||
}
|
||||
} else {
|
||||
errorMessage = 'Erreur serveur (${response.statusCode})';
|
||||
}
|
||||
} catch (e) {
|
||||
// Si le parsing JSON échoue, utiliser le body brut
|
||||
errorMessage = response.body.isNotEmpty
|
||||
? response.body
|
||||
: 'Erreur serveur (${response.statusCode})';
|
||||
|
||||
if (EnvConfig.enableDetailedLogs) {
|
||||
_log('Erreur lors du parsing de la réponse d\'erreur: $e');
|
||||
_log('Body brut: ${response.body}');
|
||||
}
|
||||
}
|
||||
|
||||
switch (response.statusCode) {
|
||||
case 400:
|
||||
throw ValidationException(errorMessage);
|
||||
case 401:
|
||||
throw UnauthorizedException(errorMessage);
|
||||
case 404:
|
||||
throw ServerException(
|
||||
'Ressource non trouvée: $errorMessage',
|
||||
statusCode: 404,
|
||||
);
|
||||
case 409:
|
||||
throw ConflictException(errorMessage);
|
||||
case 500:
|
||||
case 502:
|
||||
case 503:
|
||||
throw ServerException(
|
||||
'Erreur serveur: $errorMessage',
|
||||
statusCode: response.statusCode,
|
||||
);
|
||||
default:
|
||||
throw ServerException(
|
||||
errorMessage,
|
||||
statusCode: response.statusCode,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Vérifie si une chaîne est un UUID valide
|
||||
bool _isValidUUID(String value) {
|
||||
final uuidRegex = RegExp(
|
||||
r'^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$',
|
||||
caseSensitive: false,
|
||||
);
|
||||
return uuidRegex.hasMatch(value);
|
||||
}
|
||||
|
||||
/// Log un message si le mode debug est activé.
|
||||
void _log(String message) {
|
||||
if (EnvConfig.enableDetailedLogs) {
|
||||
print('[FriendsRepositoryImpl] $message');
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// MÉTHODES PUBLIQUES
|
||||
// ============================================================================
|
||||
|
||||
/// Récupère la liste paginée des amis pour un utilisateur donné.
|
||||
///
|
||||
/// [userId] L'identifiant unique de l'utilisateur
|
||||
/// [page] Le numéro de page (commence à 0)
|
||||
/// [size] Le nombre d'amis par page
|
||||
///
|
||||
/// Returns une liste d'objets [Friend]
|
||||
///
|
||||
/// Throws [ServerException] en cas d'erreur
|
||||
///
|
||||
/// **Exemple:**
|
||||
/// ```dart
|
||||
/// final friends = await repository.fetchFriends('user123', 0, 20);
|
||||
/// ```
|
||||
@override
|
||||
Future<List<Friend>> fetchFriends(String userId, int page, int size) async {
|
||||
_log('Récupération des amis pour l\'utilisateur $userId (page $page, taille $size)');
|
||||
|
||||
if (userId.isEmpty) {
|
||||
throw ValidationException('L\'ID utilisateur ne peut pas être vide');
|
||||
}
|
||||
|
||||
if (page < 0) {
|
||||
throw ValidationException('Le numéro de page doit être >= 0');
|
||||
}
|
||||
|
||||
if (size <= 0) {
|
||||
throw ValidationException('La taille de page doit être > 0');
|
||||
}
|
||||
|
||||
try {
|
||||
final uri = Uri.parse('${Urls.friendsBase}/list/$userId')
|
||||
.replace(queryParameters: {
|
||||
'page': page.toString(),
|
||||
'size': size.toString(),
|
||||
});
|
||||
|
||||
final response = await _performRequest('GET', uri);
|
||||
|
||||
// Gérer le cas 404 comme une liste vide
|
||||
if (response.statusCode == 404) {
|
||||
_log('Aucun ami trouvé (404) - retour d\'une liste vide');
|
||||
return [];
|
||||
}
|
||||
|
||||
final jsonResponse = _parseJsonResponse(response, [200]) as List<dynamic>?;
|
||||
|
||||
if (jsonResponse == null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
final friends = jsonResponse
|
||||
.map((json) => Friend.fromJson(json as Map<String, dynamic>))
|
||||
.toList();
|
||||
|
||||
_log('${friends.length} amis récupérés avec succès');
|
||||
return friends;
|
||||
} catch (e) {
|
||||
_log('Erreur lors de la récupération des amis: $e');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
/// Envoie une demande pour ajouter un nouvel ami.
|
||||
///
|
||||
/// [userId] L'identifiant unique de l'utilisateur qui envoie la demande
|
||||
/// [friendId] L'identifiant unique de l'ami à ajouter
|
||||
///
|
||||
/// Throws [ServerException] en cas d'erreur
|
||||
///
|
||||
/// **Exemple:**
|
||||
/// ```dart
|
||||
/// await repository.addFriend('user-uuid', 'friend-uuid');
|
||||
/// ```
|
||||
@override
|
||||
Future<void> addFriend(String userId, String friendId) async {
|
||||
_log('Ajout de l\'ami: userId=$userId, friendId=$friendId');
|
||||
|
||||
if (userId.isEmpty) {
|
||||
throw ValidationException('L\'ID de l\'utilisateur ne peut pas être vide');
|
||||
}
|
||||
|
||||
if (friendId.isEmpty) {
|
||||
throw ValidationException('L\'ID de l\'ami ne peut pas être vide');
|
||||
}
|
||||
|
||||
try {
|
||||
final uri = Uri.parse('${Urls.friendsBase}/send');
|
||||
|
||||
// Le backend attend userId et friendId dans FriendshipCreateOneRequestDTO
|
||||
final bodyJson = {
|
||||
'userId': userId,
|
||||
'friendId': friendId,
|
||||
};
|
||||
final body = jsonEncode(bodyJson);
|
||||
|
||||
// Log détaillé du body envoyé
|
||||
if (EnvConfig.enableDetailedLogs) {
|
||||
_log('Envoi de la demande d\'ami à: $uri');
|
||||
_log('Body JSON: $body');
|
||||
}
|
||||
|
||||
final response = await _performRequest(
|
||||
'POST',
|
||||
uri,
|
||||
body: body,
|
||||
);
|
||||
|
||||
// Log de la réponse
|
||||
if (EnvConfig.enableDetailedLogs) {
|
||||
_log('Réponse du serveur (${response.statusCode}): ${response.body}');
|
||||
}
|
||||
|
||||
if (![200, 201].contains(response.statusCode)) {
|
||||
_handleErrorResponse(response);
|
||||
}
|
||||
|
||||
_log('Ami ajouté avec succès: $friendId');
|
||||
} catch (e) {
|
||||
_log('Erreur lors de l\'ajout de l\'ami: $e');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
/// Supprime un ami existant.
|
||||
///
|
||||
/// [friendId] L'identifiant unique de l'ami à supprimer
|
||||
///
|
||||
/// Throws [ServerException] en cas d'erreur
|
||||
///
|
||||
/// **Exemple:**
|
||||
/// ```dart
|
||||
/// await repository.removeFriend('friend123');
|
||||
/// ```
|
||||
@override
|
||||
Future<void> removeFriend(String friendId) async {
|
||||
_log('Suppression de l\'ami $friendId');
|
||||
|
||||
if (friendId.isEmpty) {
|
||||
throw ValidationException('L\'ID de l\'ami ne peut pas être vide');
|
||||
}
|
||||
|
||||
try {
|
||||
final uri = Uri.parse('${Urls.friendsBase}/$friendId');
|
||||
final response = await _performRequest('DELETE', uri);
|
||||
|
||||
if (![200, 204].contains(response.statusCode)) {
|
||||
_handleErrorResponse(response);
|
||||
}
|
||||
|
||||
_log('Ami $friendId supprimé avec succès');
|
||||
} catch (e) {
|
||||
_log('Erreur lors de la suppression de l\'ami $friendId: $e');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
/// Récupère les détails d'un ami.
|
||||
///
|
||||
/// [friendId] L'identifiant unique de l'ami
|
||||
/// [userId] L'identifiant unique de l'utilisateur connecté
|
||||
///
|
||||
/// Returns un [Friend] avec les informations de l'ami, ou `null` si non trouvé
|
||||
///
|
||||
/// **Exemple:**
|
||||
/// ```dart
|
||||
/// final friend = await repository.getFriendDetails('friend123', 'user123');
|
||||
/// ```
|
||||
@override
|
||||
Future<Friend?> getFriendDetails(String friendId, String userId) async {
|
||||
_log('Récupération des détails de l\'ami $friendId pour l\'utilisateur $userId');
|
||||
|
||||
if (friendId.isEmpty || userId.isEmpty) {
|
||||
throw ValidationException('Les IDs ne peuvent pas être vides');
|
||||
}
|
||||
|
||||
try {
|
||||
final uri = Uri.parse('${Urls.friendsBase}/details');
|
||||
final body = jsonEncode({
|
||||
'friendId': friendId,
|
||||
'userId': userId,
|
||||
});
|
||||
|
||||
final response = await client.post(uri, headers: headers, body: body);
|
||||
_logger.d("[LOG] Réponse de l'API : ${response.body}");
|
||||
final response = await _performRequest(
|
||||
'POST',
|
||||
uri,
|
||||
body: body,
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final friendJson = json.decode(response.body);
|
||||
_logger.i("[LOG] Détails de l'ami récupérés : $friendJson");
|
||||
return Friend.fromJson(friendJson);
|
||||
} else {
|
||||
_logger.e("[ERROR] Échec de la récupération des détails. Code HTTP : ${response.statusCode}");
|
||||
// Gérer le cas 404 comme null
|
||||
if (response.statusCode == 404) {
|
||||
_log('Ami $friendId non trouvé (404)');
|
||||
return null;
|
||||
}
|
||||
|
||||
final jsonResponse = _parseJsonResponse(response, [200]) as Map<String, dynamic>?;
|
||||
|
||||
if (jsonResponse == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final friend = Friend.fromJson(jsonResponse);
|
||||
_log('Détails de l\'ami $friendId récupérés avec succès');
|
||||
return friend;
|
||||
} catch (e) {
|
||||
_logger.e("[ERROR] Exception lors de la récupération des détails de l'ami : $e");
|
||||
return null;
|
||||
_log('Erreur lors de la récupération des détails de l\'ami $friendId: $e');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
/// Met à jour le statut d'un ami (par exemple, "accepté", "bloqué").
|
||||
///
|
||||
/// [friendId] : Identifiant unique de l'ami.
|
||||
/// [status] : Nouveau statut sous forme de chaîne de caractères.
|
||||
/// [friendId] L'identifiant unique de l'ami
|
||||
/// [status] Le nouveau statut sous forme de chaîne de caractères
|
||||
///
|
||||
/// Loggue chaque étape et lève une exception en cas d'échec.
|
||||
/// Throws [ServerException] en cas d'erreur
|
||||
///
|
||||
/// **Exemple:**
|
||||
/// ```dart
|
||||
/// await repository.updateFriendStatus('friend123', 'accepted');
|
||||
/// ```
|
||||
@override
|
||||
Future<void> updateFriendStatus(String friendId, String status) async {
|
||||
try {
|
||||
_logger.i("[LOG] Mise à jour du statut de l'ami avec l'ID : $friendId, nouveau statut : $status");
|
||||
_log('Mise à jour du statut de l\'ami $friendId: $status');
|
||||
|
||||
final uri = Uri.parse('${Urls.baseUrl}/friends/$friendId/status');
|
||||
final response = await client.patch(
|
||||
if (friendId.isEmpty) {
|
||||
throw ValidationException('L\'ID de l\'ami ne peut pas être vide');
|
||||
}
|
||||
|
||||
if (status.isEmpty) {
|
||||
throw ValidationException('Le statut ne peut pas être vide');
|
||||
}
|
||||
|
||||
try {
|
||||
final uri = Uri.parse('${Urls.friendsBase}/$friendId/status');
|
||||
final body = jsonEncode({'status': status});
|
||||
|
||||
final response = await _performRequest(
|
||||
'PATCH',
|
||||
uri,
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: jsonEncode({'status': status}),
|
||||
body: body,
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
_logger.i("[LOG] Statut de l'ami mis à jour avec succès : $friendId");
|
||||
} else {
|
||||
_logger.e("[ERROR] Erreur lors de la mise à jour du statut. Code HTTP : ${response.statusCode}");
|
||||
throw Exception("Erreur lors de la mise à jour du statut");
|
||||
if (response.statusCode != 200) {
|
||||
_handleErrorResponse(response);
|
||||
}
|
||||
|
||||
_log('Statut de l\'ami $friendId mis à jour avec succès');
|
||||
} catch (e) {
|
||||
_logger.e("[ERROR] Exception lors de la mise à jour du statut de l'ami : $e");
|
||||
_log('Erreur lors de la mise à jour du statut de l\'ami $friendId: $e');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
/// Récupère les demandes d'amitié en attente pour un utilisateur.
|
||||
///
|
||||
/// [userId] L'identifiant unique de l'utilisateur
|
||||
/// [page] Le numéro de la page pour la pagination
|
||||
/// [size] La taille de la page
|
||||
///
|
||||
/// Retourne une liste de [FriendRequest]
|
||||
@override
|
||||
Future<List<FriendRequest>> getPendingFriendRequests(String userId, int page, int size) async {
|
||||
_log('Récupération des demandes d\'amitié en attente pour l\'utilisateur $userId (page $page, taille $size)');
|
||||
|
||||
try {
|
||||
final uri = Uri.parse(Urls.getPendingFriendRequestsWithUserId(userId, page: page, size: size));
|
||||
final response = await _performRequest('GET', uri);
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
_handleErrorResponse(response);
|
||||
}
|
||||
|
||||
final jsonResponse = _parseJsonResponse(response, [200]) as List<dynamic>;
|
||||
final requests = jsonResponse
|
||||
.map((json) => FriendRequest.fromJson(json as Map<String, dynamic>))
|
||||
.toList();
|
||||
|
||||
_log('${requests.length} demandes d\'amitié en attente récupérées avec succès');
|
||||
return requests;
|
||||
} catch (e) {
|
||||
_log('Erreur lors de la récupération des demandes d\'amitié en attente: $e');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
/// Accepte une demande d'amitié.
|
||||
///
|
||||
/// [friendshipId] L'identifiant unique de la relation d'amitié
|
||||
///
|
||||
/// Throws [ServerException] en cas d'erreur
|
||||
@override
|
||||
Future<void> acceptFriendRequest(String friendshipId) async {
|
||||
_log('Acceptation de la demande d\'amitié: $friendshipId');
|
||||
|
||||
if (friendshipId.isEmpty) {
|
||||
throw ValidationException('L\'ID de la relation d\'amitié ne peut pas être vide');
|
||||
}
|
||||
|
||||
try {
|
||||
final uri = Uri.parse(Urls.acceptFriendRequestWithId(friendshipId));
|
||||
final response = await _performRequest('PATCH', uri);
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
_handleErrorResponse(response);
|
||||
}
|
||||
|
||||
_log('Demande d\'amitié acceptée avec succès: $friendshipId');
|
||||
} catch (e) {
|
||||
_log('Erreur lors de l\'acceptation de la demande d\'amitié: $e');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
/// Rejette une demande d'amitié.
|
||||
///
|
||||
/// [friendshipId] L'identifiant unique de la relation d'amitié
|
||||
///
|
||||
/// Throws [ServerException] en cas d'erreur
|
||||
@override
|
||||
Future<void> rejectFriendRequest(String friendshipId) async {
|
||||
_log('Rejet de la demande d\'amitié: $friendshipId');
|
||||
|
||||
if (friendshipId.isEmpty) {
|
||||
throw ValidationException('L\'ID de la relation d\'amitié ne peut pas être vide');
|
||||
}
|
||||
|
||||
try {
|
||||
final uri = Uri.parse(Urls.rejectFriendRequestWithId(friendshipId));
|
||||
final response = await _performRequest('PATCH', uri);
|
||||
|
||||
if (response.statusCode != 204) {
|
||||
_handleErrorResponse(response);
|
||||
}
|
||||
|
||||
_log('Demande d\'amitié rejetée avec succès: $friendshipId');
|
||||
} catch (e) {
|
||||
_log('Erreur lors du rejet de la demande d\'amitié: $e');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
/// Récupère les demandes d'amitié envoyées par un utilisateur.
|
||||
///
|
||||
/// [userId] L'identifiant unique de l'utilisateur
|
||||
/// [page] Le numéro de page (commence à 0)
|
||||
/// [size] Le nombre de demandes par page
|
||||
///
|
||||
/// Returns une liste d'objets [FriendRequest]
|
||||
///
|
||||
/// Throws [ServerException] en cas d'erreur
|
||||
@override
|
||||
Future<List<FriendRequest>> getSentFriendRequests(String userId, int page, int size) async {
|
||||
_log('Récupération des demandes d\'amitié envoyées pour l\'utilisateur $userId (page $page, taille $size)');
|
||||
|
||||
try {
|
||||
final uri = Uri.parse(Urls.getSentFriendRequestsWithUserId(userId, page: page, size: size));
|
||||
final response = await _performRequest('GET', uri);
|
||||
|
||||
if (response.statusCode == 404) {
|
||||
_log('Aucune demande envoyée trouvée (404) - retour d\'une liste vide');
|
||||
return [];
|
||||
}
|
||||
|
||||
final jsonResponse = _parseJsonResponse(response, [200]) as List<dynamic>;
|
||||
final requests = jsonResponse
|
||||
.map((json) => FriendRequest.fromJson(json as Map<String, dynamic>))
|
||||
.toList();
|
||||
|
||||
_log('${requests.length} demandes d\'amitié envoyées récupérées avec succès');
|
||||
return requests;
|
||||
} catch (e) {
|
||||
_log('Erreur lors de la récupération des demandes d\'amitié envoyées: $e');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
/// Récupère les demandes d'amitié reçues par un utilisateur.
|
||||
///
|
||||
/// [userId] L'identifiant unique de l'utilisateur
|
||||
/// [page] Le numéro de page (commence à 0)
|
||||
/// [size] Le nombre de demandes par page
|
||||
///
|
||||
/// Returns une liste d'objets [FriendRequest]
|
||||
///
|
||||
/// Throws [ServerException] en cas d'erreur
|
||||
@override
|
||||
Future<List<FriendRequest>> getReceivedFriendRequests(String userId, int page, int size) async {
|
||||
_log('Récupération des demandes d\'amitié reçues pour l\'utilisateur $userId (page $page, taille $size)');
|
||||
|
||||
try {
|
||||
final uri = Uri.parse(Urls.getReceivedFriendRequestsWithUserId(userId, page: page, size: size));
|
||||
final response = await _performRequest('GET', uri);
|
||||
|
||||
if (response.statusCode == 404) {
|
||||
_log('Aucune demande reçue trouvée (404) - retour d\'une liste vide');
|
||||
return [];
|
||||
}
|
||||
|
||||
final jsonResponse = _parseJsonResponse(response, [200]) as List<dynamic>;
|
||||
|
||||
if (EnvConfig.enableDetailedLogs) {
|
||||
_log('Réponse JSON brute: $jsonResponse');
|
||||
}
|
||||
|
||||
final requests = jsonResponse
|
||||
.map((json) {
|
||||
if (EnvConfig.enableDetailedLogs) {
|
||||
_log('Parsing demande: $json');
|
||||
}
|
||||
return FriendRequest.fromJson(json as Map<String, dynamic>);
|
||||
})
|
||||
.toList();
|
||||
|
||||
_log('${requests.length} demandes d\'amitié reçues récupérées avec succès');
|
||||
if (EnvConfig.enableDetailedLogs && requests.isNotEmpty) {
|
||||
_log('Première demande: userId=${requests.first.userId}, friendId=${requests.first.friendId}, userFullName=${requests.first.userFullName}');
|
||||
}
|
||||
return requests;
|
||||
} catch (e) {
|
||||
_log('Erreur lors de la récupération des demandes d\'amitié reçues: $e');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
/// Annule une demande d'amitié envoyée (supprime la relation).
|
||||
///
|
||||
/// [friendshipId] L'identifiant unique de la relation d'amitié
|
||||
///
|
||||
/// Throws [ServerException] en cas d'erreur
|
||||
@override
|
||||
Future<void> cancelFriendRequest(String friendshipId) async {
|
||||
_log('Annulation de la demande d\'amitié: $friendshipId');
|
||||
|
||||
if (friendshipId.isEmpty) {
|
||||
throw ValidationException('L\'ID de la relation d\'amitié ne peut pas être vide');
|
||||
}
|
||||
|
||||
try {
|
||||
// Utiliser l'endpoint DELETE existant pour supprimer la relation
|
||||
final uri = Uri.parse('${Urls.friendsBase}/$friendshipId');
|
||||
final response = await _performRequest('DELETE', uri);
|
||||
|
||||
if (response.statusCode != 204) {
|
||||
_handleErrorResponse(response);
|
||||
}
|
||||
|
||||
_log('Demande d\'amitié annulée avec succès: $friendshipId');
|
||||
} catch (e) {
|
||||
_log('Erreur lors de l\'annulation de la demande d\'amitié: $e');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user