189 lines
6.4 KiB
Dart
189 lines
6.4 KiB
Dart
import 'dart:convert';
|
|
|
|
import 'package:shared_preferences/shared_preferences.dart';
|
|
|
|
import '../models/auth_session.dart';
|
|
import 'local_seed_repository.dart';
|
|
|
|
abstract class AuthRepository {
|
|
Future<AuthSession?> restoreSession();
|
|
Future<AuthSession> signIn({required String email, required String password});
|
|
Future<AuthSession> signUp({required String name, required String email, required String password});
|
|
Future<void> signOut();
|
|
}
|
|
|
|
class LocalAuthRepository implements AuthRepository {
|
|
const LocalAuthRepository();
|
|
|
|
static const String _tokenKey = 'auth_token';
|
|
static const String _emailKey = 'auth_email';
|
|
static const String _nameKey = 'auth_name';
|
|
static const String _sessionKey = 'auth_session_json';
|
|
static const String _usersKey = 'auth_users_json';
|
|
|
|
@override
|
|
Future<AuthSession?> restoreSession() async {
|
|
final prefs = await SharedPreferences.getInstance();
|
|
final sessionJson = prefs.getString(_sessionKey);
|
|
if (sessionJson != null && sessionJson.trim().isNotEmpty) {
|
|
final decoded = jsonDecode(sessionJson);
|
|
if (decoded is Map<String, dynamic>) {
|
|
final token = decoded['token']?.toString();
|
|
final email = decoded['email']?.toString();
|
|
final name = decoded['displayName']?.toString();
|
|
if (token != null && email != null && token.isNotEmpty && email.isNotEmpty) {
|
|
return AuthSession(token: token, email: email, displayName: name ?? email);
|
|
}
|
|
}
|
|
}
|
|
|
|
final token = prefs.getString(_tokenKey);
|
|
final email = prefs.getString(_emailKey);
|
|
final name = prefs.getString(_nameKey);
|
|
|
|
if (token == null || email == null) {
|
|
return null;
|
|
}
|
|
|
|
return AuthSession(token: token, email: email, displayName: name ?? email);
|
|
}
|
|
|
|
@override
|
|
Future<AuthSession> signIn({required String email, required String password}) {
|
|
return _authenticate(
|
|
endpoint: '/auth/login',
|
|
body: <String, dynamic>{'email': email, 'password': password},
|
|
);
|
|
}
|
|
|
|
@override
|
|
Future<AuthSession> signUp({required String name, required String email, required String password}) {
|
|
return _authenticate(
|
|
endpoint: '/auth/register',
|
|
body: <String, dynamic>{'name': name, 'email': email, 'password': password},
|
|
);
|
|
}
|
|
|
|
Future<AuthSession> _authenticate({required String endpoint, required Map<String, dynamic> body}) async {
|
|
final email = body['email']?.toString().trim();
|
|
final password = body['password']?.toString();
|
|
if (email == null || password == null || email.isEmpty) {
|
|
throw AuthException('Ingresa correo y contraseña válidos.');
|
|
}
|
|
|
|
final seedData = await LocalSeedRepository.instance.load();
|
|
final profile = seedData.profileForCredentials(email, password);
|
|
if (profile != null) {
|
|
final session = AuthSession(
|
|
token: 'local-${profile.email}',
|
|
email: profile.email,
|
|
displayName: profile.name,
|
|
);
|
|
await _persistSession(session);
|
|
return session;
|
|
}
|
|
|
|
final localUsers = await _loadLocalUsers();
|
|
final normalizedEmail = email.toLowerCase();
|
|
final localMatch = localUsers.where((user) {
|
|
final userEmail = user['email']?.toString().trim().toLowerCase();
|
|
final userPassword = user['password']?.toString();
|
|
return userEmail == normalizedEmail && userPassword == password;
|
|
}).toList(growable: false);
|
|
|
|
if (localMatch.isNotEmpty) {
|
|
final user = localMatch.first;
|
|
final displayName = user['name']?.toString().trim();
|
|
final session = AuthSession(
|
|
token: 'local-${email.toLowerCase()}',
|
|
email: email,
|
|
displayName: displayName == null || displayName.isEmpty ? email : displayName,
|
|
);
|
|
await _persistSession(session);
|
|
return session;
|
|
}
|
|
|
|
if (endpoint == '/auth/register') {
|
|
final displayName = body['name']?.toString().trim();
|
|
final alreadyInSeed = seedData.profileForCredentials(email, password) != null ||
|
|
seedData.demoProfiles.any((profile) => profile.email.trim().toLowerCase() == normalizedEmail);
|
|
final alreadyInLocal = localUsers.any((user) => user['email']?.toString().trim().toLowerCase() == normalizedEmail);
|
|
if (alreadyInSeed || alreadyInLocal) {
|
|
throw AuthException('Este correo ya está registrado.');
|
|
}
|
|
|
|
final createdUser = <String, dynamic>{
|
|
'name': displayName == null || displayName.isEmpty ? email : displayName,
|
|
'email': email,
|
|
'password': password,
|
|
'createdAt': DateTime.now().toIso8601String(),
|
|
};
|
|
await _saveLocalUsers(<Map<String, dynamic>>[...localUsers, createdUser]);
|
|
|
|
final session = AuthSession(
|
|
token: 'local-$email',
|
|
email: email,
|
|
displayName: displayName == null || displayName.isEmpty ? email : displayName,
|
|
);
|
|
await _persistSession(session);
|
|
return session;
|
|
}
|
|
|
|
throw AuthException('Usuario o contraseña no encontrados en los perfiles locales.');
|
|
}
|
|
|
|
Future<void> _persistSession(AuthSession session) async {
|
|
final prefs = await SharedPreferences.getInstance();
|
|
await prefs.setString(_tokenKey, session.token);
|
|
await prefs.setString(_emailKey, session.email);
|
|
await prefs.setString(_nameKey, session.displayName);
|
|
await prefs.setString(
|
|
_sessionKey,
|
|
jsonEncode(
|
|
<String, dynamic>{
|
|
'token': session.token,
|
|
'email': session.email,
|
|
'displayName': session.displayName,
|
|
},
|
|
),
|
|
);
|
|
}
|
|
|
|
Future<List<Map<String, dynamic>>> _loadLocalUsers() async {
|
|
final prefs = await SharedPreferences.getInstance();
|
|
final raw = prefs.getString(_usersKey);
|
|
if (raw == null || raw.trim().isEmpty) {
|
|
return <Map<String, dynamic>>[];
|
|
}
|
|
|
|
final decoded = jsonDecode(raw);
|
|
if (decoded is! List) {
|
|
return <Map<String, dynamic>>[];
|
|
}
|
|
|
|
return decoded.whereType<Map<String, dynamic>>().toList(growable: false);
|
|
}
|
|
|
|
Future<void> _saveLocalUsers(List<Map<String, dynamic>> users) async {
|
|
final prefs = await SharedPreferences.getInstance();
|
|
await prefs.setString(_usersKey, jsonEncode(users));
|
|
}
|
|
|
|
@override
|
|
Future<void> signOut() async {
|
|
final prefs = await SharedPreferences.getInstance();
|
|
await prefs.remove(_tokenKey);
|
|
await prefs.remove(_emailKey);
|
|
await prefs.remove(_nameKey);
|
|
await prefs.remove(_sessionKey);
|
|
}
|
|
}
|
|
|
|
class AuthException implements Exception {
|
|
AuthException(this.message);
|
|
|
|
final String message;
|
|
|
|
@override
|
|
String toString() => message;
|
|
} |