Files
unionflow-mobile-apps/lib/features/communication/data/datasources/messaging_remote_datasource.dart
dahoud 45dcd2171e feat(communication): module messagerie unifié + contact policies + blocages
Aligné avec le backend MessagingResource :
- Nouveau module communication (conversations, messages, participants)
- Respect des ContactPolicy (qui peut parler à qui par rôle)
- Gestion MemberBlock (blocages individuels)
- UI : conversations list, conversation detail, broadcast, tiles
- BLoC : MessagingBloc avec events (envoyer, démarrer conversation rôle, etc.)
2026-04-15 20:26:35 +00:00

305 lines
11 KiB
Dart

/// Datasource distant pour la messagerie v4 — /api/messagerie/*
library messaging_remote_datasource;
import 'dart:convert';
import 'package:injectable/injectable.dart';
import '../../../../core/config/environment.dart';
import '../../../../core/error/exceptions.dart';
import '../../../authentication/data/datasources/keycloak_auth_service.dart';
import '../models/conversation_model.dart';
import '../models/message_model.dart';
import '../models/contact_policy_model.dart';
import 'package:http/http.dart' as http;
@lazySingleton
class MessagingRemoteDatasource {
final http.Client client;
final KeycloakAuthService authService;
MessagingRemoteDatasource({
required this.client,
required this.authService,
});
/// Headers HTTP avec authentification — rafraîchit le token si expiré
Future<Map<String, String>> _getHeaders() async {
final token = await authService.getValidAccessToken();
return {
'Content-Type': 'application/json',
'Accept': 'application/json',
if (token != null) 'Authorization': 'Bearer $token',
};
}
String get _base => '${AppConfig.apiBaseUrl}/api/messagerie';
// ── Conversations ─────────────────────────────────────────────────────────
Future<List<ConversationSummaryModel>> getMesConversations() async {
final response = await client.get(
Uri.parse('$_base/conversations'),
headers: await _getHeaders(),
);
_checkAuth(response);
if (response.statusCode == 200) {
final list = json.decode(response.body) as List<dynamic>;
return list
.map((j) => ConversationSummaryModel.fromJson(j as Map<String, dynamic>))
.toList();
}
throw ServerException('Erreur récupération conversations (${response.statusCode})');
}
Future<ConversationModel> getConversation(String id) async {
final response = await client.get(
Uri.parse('$_base/conversations/$id'),
headers: await _getHeaders(),
);
_checkAuth(response);
if (response.statusCode == 200) {
return ConversationModel.fromJson(json.decode(response.body) as Map<String, dynamic>);
}
if (response.statusCode == 404) throw NotFoundException('Conversation non trouvée');
throw ServerException('Erreur récupération conversation (${response.statusCode})');
}
Future<ConversationModel> demarrerConversationDirecte({
required String destinataireId,
required String organisationId,
String? premierMessage,
}) async {
final body = json.encode({
'destinataireId': destinataireId,
'organisationId': organisationId,
if (premierMessage != null) 'premierMessage': premierMessage,
});
final response = await client.post(
Uri.parse('$_base/conversations/directe'),
headers: await _getHeaders(),
body: body,
);
_checkAuth(response);
if (response.statusCode == 201 || response.statusCode == 200) {
return ConversationModel.fromJson(json.decode(response.body) as Map<String, dynamic>);
}
throw ServerException('Erreur démarrage conversation directe (${response.statusCode})');
}
Future<ConversationModel> demarrerConversationRole({
required String roleCible,
required String organisationId,
String? premierMessage,
}) async {
final body = json.encode({
'roleCible': roleCible,
'organisationId': organisationId,
if (premierMessage != null) 'premierMessage': premierMessage,
});
final response = await client.post(
Uri.parse('$_base/conversations/role'),
headers: await _getHeaders(),
body: body,
);
_checkAuth(response);
if (response.statusCode == 201 || response.statusCode == 200) {
return ConversationModel.fromJson(json.decode(response.body) as Map<String, dynamic>);
}
throw ServerException('Erreur démarrage conversation rôle (${response.statusCode})');
}
Future<void> archiverConversation(String id) async {
final response = await client.delete(
Uri.parse('$_base/conversations/$id'),
headers: await _getHeaders(),
);
_checkAuth(response);
if (response.statusCode != 200 && response.statusCode != 204) {
throw ServerException('Erreur archivage conversation (${response.statusCode})');
}
}
// ── Messages ──────────────────────────────────────────────────────────────
Future<MessageModel> envoyerMessage(
String conversationId, {
required String typeMessage,
String? contenu,
String? urlFichier,
int? dureeAudio,
String? messageParentId,
}) async {
final body = json.encode({
'typeMessage': typeMessage,
if (contenu != null) 'contenu': contenu,
if (urlFichier != null) 'urlFichier': urlFichier,
if (dureeAudio != null) 'dureeAudio': dureeAudio,
if (messageParentId != null) 'messageParentId': messageParentId,
});
final response = await client.post(
Uri.parse('$_base/conversations/$conversationId/messages'),
headers: await _getHeaders(),
body: body,
);
_checkAuth(response);
if (response.statusCode == 201 || response.statusCode == 200) {
return MessageModel.fromJson(json.decode(response.body) as Map<String, dynamic>);
}
throw ServerException('Erreur envoi message (${response.statusCode})');
}
Future<List<MessageModel>> getMessages(String conversationId, {int page = 0}) async {
final uri = Uri.parse('$_base/conversations/$conversationId/messages')
.replace(queryParameters: {'page': page.toString()});
final response = await client.get(uri, headers: await _getHeaders());
_checkAuth(response);
if (response.statusCode == 200) {
final list = json.decode(response.body) as List<dynamic>;
return list
.map((j) => MessageModel.fromJson(j as Map<String, dynamic>))
.toList();
}
throw ServerException('Erreur récupération messages (${response.statusCode})');
}
Future<void> marquerLu(String conversationId) async {
final response = await client.put(
Uri.parse('$_base/conversations/$conversationId/lire'),
headers: await _getHeaders(),
);
_checkAuth(response);
if (response.statusCode != 200 && response.statusCode != 204) {
throw ServerException('Erreur marquage lu (${response.statusCode})');
}
}
Future<void> supprimerMessage(String conversationId, String messageId) async {
final response = await client.delete(
Uri.parse('$_base/conversations/$conversationId/messages/$messageId'),
headers: await _getHeaders(),
);
_checkAuth(response);
if (response.statusCode != 200 && response.statusCode != 204) {
throw ServerException('Erreur suppression message (${response.statusCode})');
}
}
// ── Blocages ──────────────────────────────────────────────────────────────
Future<void> bloquerMembre({
required String membreABloquerId,
String? organisationId,
String? raison,
}) async {
final body = json.encode({
'membreABloquerId': membreABloquerId,
if (organisationId != null) 'organisationId': organisationId,
if (raison != null) 'raison': raison,
});
final response = await client.post(
Uri.parse('$_base/blocages'),
headers: await _getHeaders(),
body: body,
);
_checkAuth(response);
if (response.statusCode != 200 && response.statusCode != 204) {
throw ServerException('Erreur blocage membre (${response.statusCode})');
}
}
Future<void> debloquerMembre(String membreId, {String? organisationId}) async {
final uri = Uri.parse('$_base/blocages/$membreId').replace(
queryParameters: {
if (organisationId != null) 'organisationId': organisationId,
},
);
final response = await client.delete(uri, headers: await _getHeaders());
_checkAuth(response);
if (response.statusCode != 200 && response.statusCode != 204) {
throw ServerException('Erreur déblocage membre (${response.statusCode})');
}
}
Future<List<Map<String, dynamic>>> getMesBlocages() async {
final response = await client.get(
Uri.parse('$_base/blocages'),
headers: await _getHeaders(),
);
_checkAuth(response);
if (response.statusCode == 200) {
final list = json.decode(response.body) as List<dynamic>;
return list.map((j) => j as Map<String, dynamic>).toList();
}
throw ServerException('Erreur récupération blocages (${response.statusCode})');
}
// ── Politique de communication ────────────────────────────────────────────
Future<ContactPolicyModel> getPolitique(String organisationId) async {
final response = await client.get(
Uri.parse('$_base/politique/$organisationId'),
headers: await _getHeaders(),
);
_checkAuth(response);
if (response.statusCode == 200) {
return ContactPolicyModel.fromJson(json.decode(response.body) as Map<String, dynamic>);
}
throw ServerException('Erreur récupération politique (${response.statusCode})');
}
Future<ContactPolicyModel> mettreAJourPolitique(
String organisationId, {
required String typePolitique,
required bool autoriserMembreVersMembre,
required bool autoriserMembreVersRole,
required bool autoriserNotesVocales,
}) async {
final body = json.encode({
'typePolitique': typePolitique,
'autoriserMembreVersMembre': autoriserMembreVersMembre,
'autoriserMembreVersRole': autoriserMembreVersRole,
'autoriserNotesVocales': autoriserNotesVocales,
});
final response = await client.put(
Uri.parse('$_base/politique/$organisationId'),
headers: await _getHeaders(),
body: body,
);
_checkAuth(response);
if (response.statusCode == 200) {
return ContactPolicyModel.fromJson(json.decode(response.body) as Map<String, dynamic>);
}
throw ServerException('Erreur mise à jour politique (${response.statusCode})');
}
// ── Helpers ────────────────────────────────────────────────────────────────
void _checkAuth(http.Response response) {
if (response.statusCode == 401) throw UnauthorizedException();
if (response.statusCode == 403) throw ForbiddenException('Permission insuffisante');
}
}