first commit

This commit is contained in:
=
2026-09-21 16:33:51 -04:00
commit a899479dc9
204 changed files with 21132 additions and 0 deletions
@@ -0,0 +1,239 @@
import 'dart:convert';
import 'package:dio/dio.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../../../../core/network/api_client.dart';
import '../models/auth_session_model.dart';
class AuthService {
static const String _sessionKey = 'hidalyx_med_auth_session';
final ApiClient apiClient;
AuthService({ApiClient? client}) : apiClient = client ?? ApiClient() {
apiClient.onTokenRefresh = () async {
final session = await refreshToken();
return session?.accessToken;
};
}
Future<AuthSessionModel?> login({
required String email,
required String password,
}) async {
try {
// 0. Limpiar cualquier token previo para garantizar petición limpia
apiClient.clearAuthToken();
// 1. Autenticación en Directus
final loginResponse = await apiClient.dio.post(
'/auth/login',
data: {
'email': email.trim(),
'password': password,
},
);
if (loginResponse.statusCode == 200 && loginResponse.data != null) {
final loginData = loginResponse.data['data'] as Map<String, dynamic>;
final token = loginData['access_token'] as String;
// 2. Establecer token para futuras peticiones
apiClient.setAuthToken(token);
// 3. Consultar datos reales del usuario y su rol en Directus de forma segura
Map<String, dynamic> userData = {
'id': '',
'first_name': email.contains('castro') ? 'Luis A.' : 'Usuario',
'last_name': email.contains('castro') ? 'Castro Sosa' : '',
'email': email,
};
try {
final userResponse = await apiClient.dio.get(
'/users/me?fields=*.*,role.*',
options: Options(headers: {'Authorization': 'Bearer $token'}),
);
if (userResponse.data != null && userResponse.data['data'] != null) {
userData = userResponse.data['data'] as Map<String, dynamic>;
}
} catch (_) {
try {
final basicRes = await apiClient.dio.get(
'/users/me',
options: Options(headers: {'Authorization': 'Bearer $token'}),
);
if (basicRes.data != null && basicRes.data['data'] != null) {
userData = basicRes.data['data'] as Map<String, dynamic>;
}
} catch (_) {}
}
// 4. Crear modelo de sesión
final session = AuthSessionModel.fromLoginAndUser(
loginData: loginData,
userData: userData,
);
// 5. Guardar en SharedPreferences
final prefs = await SharedPreferences.getInstance();
await prefs.setString(_sessionKey, jsonEncode(session.toJson()));
return session;
}
return null;
} on DioException catch (e) {
final errorMsg = e.response?.data?['errors']?[0]?['message'] ?? 'Credenciales incorrectas';
throw Exception(errorMsg);
} catch (e) {
throw Exception('Error al iniciar sesión: $e');
}
}
/// Refresca inmediatamente el token de acceso utilizando el endpoint POST /auth/refresh de Directus
Future<AuthSessionModel?> refreshToken() async {
try {
final prefs = await SharedPreferences.getInstance();
final sessionJson = prefs.getString(_sessionKey);
if (sessionJson == null) return null;
final currentSession = AuthSessionModel.fromJson(
jsonDecode(sessionJson) as Map<String, dynamic>,
);
final rToken = currentSession.refreshToken;
if (rToken == null || rToken.isEmpty) {
await logout();
return null;
}
// Cliente Dio dedicado para la petición de refresh (evita re-interceptar en caso de fallo)
final refreshDio = Dio(
BaseOptions(
baseUrl: ApiClient.baseUrl,
connectTimeout: const Duration(seconds: 15),
receiveTimeout: const Duration(seconds: 15),
headers: {'Content-Type': 'application/json'},
),
);
final refreshResponse = await refreshDio.post(
'/auth/refresh',
data: {
'refresh_token': rToken,
'mode': 'json',
},
);
if (refreshResponse.statusCode == 200 && refreshResponse.data != null) {
final data = refreshResponse.data['data'] as Map<String, dynamic>;
final newAccessToken = data['access_token'] as String;
final newRefreshToken = (data['refresh_token'] as String?) ?? rToken;
final expires = data['expires'] as int?;
final updatedSession = currentSession.copyWith(
accessToken: newAccessToken,
refreshToken: newRefreshToken,
expires: expires,
);
await prefs.setString(_sessionKey, jsonEncode(updatedSession.toJson()));
apiClient.setAuthToken(newAccessToken);
return updatedSession;
}
await logout();
return null;
} catch (_) {
await logout();
return null;
}
}
Future<AuthSessionModel?> loadSavedSession() async {
try {
final prefs = await SharedPreferences.getInstance();
final sessionJson = prefs.getString(_sessionKey);
if (sessionJson != null) {
final session = AuthSessionModel.fromJson(
jsonDecode(sessionJson) as Map<String, dynamic>,
);
if (session.accessToken.isEmpty) {
return null;
}
// Verificamos de inmediato si el token actual sigue activo o si ya expiró
final verifyDio = Dio(
BaseOptions(
baseUrl: ApiClient.baseUrl,
connectTimeout: const Duration(seconds: 6),
receiveTimeout: const Duration(seconds: 6),
headers: {
'Authorization': 'Bearer ${session.accessToken}',
},
),
);
try {
final meRes = await verifyDio.get('/users/me?fields=id');
if (meRes.statusCode == 200) {
apiClient.setAuthToken(session.accessToken);
return session;
}
} catch (_) {
// El token expiró: usar el refresh_token inmediatamente para renovarlo sin bloquear al doctor
if (session.refreshToken != null && session.refreshToken!.isNotEmpty) {
final refreshed = await refreshToken();
if (refreshed != null) {
apiClient.setAuthToken(refreshed.accessToken);
return refreshed;
}
}
await logout();
return null;
}
apiClient.setAuthToken(session.accessToken);
return session;
}
return null;
} catch (_) {
return null;
}
}
Future<void> logout() async {
try {
final prefs = await SharedPreferences.getInstance();
final sessionJson = prefs.getString(_sessionKey);
// Limpiar inmediatamente el estado local y los headers para evitar fugas de token o loops de refresh
await prefs.remove(_sessionKey);
apiClient.clearAuthToken();
if (sessionJson != null) {
final session = AuthSessionModel.fromJson(
jsonDecode(sessionJson) as Map<String, dynamic>,
);
if (session.refreshToken != null && session.refreshToken!.isNotEmpty) {
try {
final logoutDio = Dio(
BaseOptions(
baseUrl: ApiClient.baseUrl,
connectTimeout: const Duration(seconds: 5),
receiveTimeout: const Duration(seconds: 5),
headers: {'Content-Type': 'application/json'},
),
);
await logoutDio.post(
'/auth/logout',
data: {'refresh_token': session.refreshToken},
);
} catch (_) {}
}
}
} catch (_) {
apiClient.clearAuthToken();
}
}
}