204 lines
7.2 KiB
Dart
204 lines
7.2 KiB
Dart
import 'dart:convert';
|
|
import 'dart:io';
|
|
import 'package:http/http.dart' as http;
|
|
import 'package:shared_preferences/shared_preferences.dart';
|
|
import '../models/user.dart';
|
|
import '../models/am_registration_data.dart';
|
|
import 'api/api_config.dart';
|
|
import 'api/tokenService.dart';
|
|
import '../utils/nir_utils.dart';
|
|
|
|
class AuthService {
|
|
static const String _currentUserKey = 'current_user';
|
|
|
|
/// Connexion de l'utilisateur
|
|
/// Retourne l'utilisateur connecté avec le flag changement_mdp_obligatoire
|
|
static Future<AppUser> login(String email, String password) async {
|
|
try {
|
|
final response = await http.post(
|
|
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.login}'),
|
|
headers: ApiConfig.headers,
|
|
body: jsonEncode({
|
|
'email': email,
|
|
'password': password,
|
|
}),
|
|
);
|
|
|
|
if (response.statusCode == 200 || response.statusCode == 201) {
|
|
final data = jsonDecode(response.body);
|
|
// API renvoie access_token / refresh_token (snake_case)
|
|
final accessToken = data['access_token'] as String? ?? data['accessToken'] as String?;
|
|
final refreshToken = data['refresh_token'] as String? ?? data['refreshToken'] as String?;
|
|
if (accessToken == null) throw Exception('Token absent dans la réponse serveur');
|
|
|
|
await TokenService.saveToken(accessToken);
|
|
await TokenService.saveRefreshToken(refreshToken ?? '');
|
|
|
|
final user = await _fetchUserProfile(accessToken);
|
|
|
|
// Stocker l'utilisateur en cache
|
|
await _saveCurrentUser(user);
|
|
|
|
return user;
|
|
} else {
|
|
final error = jsonDecode(response.body);
|
|
throw Exception(error['message'] ?? 'Erreur de connexion');
|
|
}
|
|
} catch (e) {
|
|
if (e is Exception) rethrow;
|
|
throw Exception('Erreur réseau: impossible de se connecter au serveur');
|
|
}
|
|
}
|
|
|
|
/// Récupère le profil utilisateur via /auth/me
|
|
static Future<AppUser> _fetchUserProfile(String token) async {
|
|
try {
|
|
final response = await http.get(
|
|
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.authMe}'),
|
|
headers: ApiConfig.authHeaders(token),
|
|
);
|
|
|
|
if (response.statusCode == 200) {
|
|
final data = jsonDecode(response.body);
|
|
return AppUser.fromJson(data);
|
|
} else {
|
|
throw Exception('Erreur lors de la récupération du profil');
|
|
}
|
|
} catch (e) {
|
|
if (e is Exception) rethrow;
|
|
throw Exception('Erreur réseau: impossible de récupérer le profil');
|
|
}
|
|
}
|
|
|
|
/// Changement de mot de passe obligatoire
|
|
static Future<void> changePasswordRequired({
|
|
required String currentPassword,
|
|
required String newPassword,
|
|
}) async {
|
|
final token = await TokenService.getToken();
|
|
if (token == null) {
|
|
throw Exception('Non authentifié');
|
|
}
|
|
|
|
try {
|
|
final response = await http.post(
|
|
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.changePasswordRequired}'),
|
|
headers: ApiConfig.authHeaders(token),
|
|
body: jsonEncode({
|
|
'mot_de_passe_actuel': currentPassword,
|
|
'nouveau_mot_de_passe': newPassword,
|
|
'confirmation_mot_de_passe': newPassword,
|
|
}),
|
|
);
|
|
|
|
if (response.statusCode != 200 && response.statusCode != 201) {
|
|
final error = jsonDecode(response.body);
|
|
throw Exception(error['message'] ?? 'Erreur lors du changement de mot de passe');
|
|
}
|
|
|
|
// Après le changement de MDP, rafraîchir le profil utilisateur
|
|
final user = await _fetchUserProfile(token);
|
|
await _saveCurrentUser(user);
|
|
} catch (e) {
|
|
if (e is Exception) rethrow;
|
|
throw Exception('Erreur réseau: impossible de changer le mot de passe');
|
|
}
|
|
}
|
|
|
|
/// Déconnexion de l'utilisateur
|
|
static Future<void> logout() async {
|
|
await TokenService.clearAll();
|
|
final prefs = await SharedPreferences.getInstance();
|
|
await prefs.remove(_currentUserKey);
|
|
}
|
|
|
|
/// Vérifie si l'utilisateur est connecté
|
|
static Future<bool> isLoggedIn() async {
|
|
final token = await TokenService.getToken();
|
|
return token != null;
|
|
}
|
|
|
|
/// Récupère l'utilisateur connecté depuis le cache
|
|
static Future<AppUser?> getCurrentUser() async {
|
|
final prefs = await SharedPreferences.getInstance();
|
|
final userJson = prefs.getString(_currentUserKey);
|
|
|
|
if (userJson != null) {
|
|
return AppUser.fromJson(jsonDecode(userJson));
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/// Sauvegarde l'utilisateur actuel en cache
|
|
static Future<void> _saveCurrentUser(AppUser user) async {
|
|
final prefs = await SharedPreferences.getInstance();
|
|
await prefs.setString(_currentUserKey, jsonEncode(user.toJson()));
|
|
}
|
|
|
|
/// Inscription AM complète (POST /auth/register/am).
|
|
/// En cas de succès (201), aucune donnée utilisateur retournée ; rediriger vers login.
|
|
static Future<void> registerAM(AmRegistrationData data) async {
|
|
String? photoBase64;
|
|
if (data.photoPath != null && data.photoPath!.isNotEmpty && !data.photoPath!.startsWith('assets/')) {
|
|
try {
|
|
final file = File(data.photoPath!);
|
|
if (await file.exists()) {
|
|
final bytes = await file.readAsBytes();
|
|
photoBase64 = 'data:image/jpeg;base64,${base64Encode(bytes)}';
|
|
}
|
|
} catch (_) {}
|
|
}
|
|
|
|
final body = {
|
|
'email': data.email,
|
|
'prenom': data.firstName,
|
|
'nom': data.lastName,
|
|
'telephone': data.phone,
|
|
'adresse': data.streetAddress.isNotEmpty ? data.streetAddress : null,
|
|
'code_postal': data.postalCode.isNotEmpty ? data.postalCode : null,
|
|
'ville': data.city.isNotEmpty ? data.city : null,
|
|
if (photoBase64 != null) 'photo_base64': photoBase64,
|
|
'consentement_photo': data.photoConsent,
|
|
'date_naissance': data.dateOfBirth != null
|
|
? '${data.dateOfBirth!.year}-${data.dateOfBirth!.month.toString().padLeft(2, '0')}-${data.dateOfBirth!.day.toString().padLeft(2, '0')}'
|
|
: null,
|
|
'lieu_naissance_ville': data.birthCity.isNotEmpty ? data.birthCity : null,
|
|
'lieu_naissance_pays': data.birthCountry.isNotEmpty ? data.birthCountry : null,
|
|
'nir': normalizeNir(data.nir),
|
|
'numero_agrement': data.agrementNumber,
|
|
'capacite_accueil': data.capacity ?? 1,
|
|
'biographie': data.presentationText.isNotEmpty ? data.presentationText : null,
|
|
'acceptation_cgu': data.cguAccepted,
|
|
'acceptation_privacy': data.cguAccepted,
|
|
};
|
|
|
|
final response = await http.post(
|
|
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.registerAM}'),
|
|
headers: ApiConfig.headers,
|
|
body: jsonEncode(body),
|
|
);
|
|
|
|
if (response.statusCode == 200 || response.statusCode == 201) {
|
|
return;
|
|
}
|
|
|
|
final decoded = response.body.isNotEmpty ? jsonDecode(response.body) : null;
|
|
final message = decoded is Map ? (decoded['message'] as String? ?? decoded['error'] as String?) : null;
|
|
throw Exception(message ?? 'Erreur lors de l\'inscription (${response.statusCode})');
|
|
}
|
|
|
|
/// Rafraîchit le profil utilisateur depuis l'API
|
|
static Future<AppUser?> refreshCurrentUser() async {
|
|
final token = await TokenService.getToken();
|
|
if (token == null) return null;
|
|
|
|
try {
|
|
final user = await _fetchUserProfile(token);
|
|
await _saveCurrentUser(user);
|
|
return user;
|
|
} catch (e) {
|
|
return null;
|
|
}
|
|
}
|
|
} |