initial commit 2
This commit is contained in:
96
lib/services/address_repository.dart
Normal file
96
lib/services/address_repository.dart
Normal file
@@ -0,0 +1,96 @@
|
||||
import 'dart:convert';
|
||||
import 'package:http/http.dart' as http;
|
||||
import '../app_config.dart';
|
||||
import '../models/address_entry.dart';
|
||||
import '../models/address_record.dart';
|
||||
import '../models/auth_session.dart';
|
||||
|
||||
abstract class AddressRepository {
|
||||
Future<void> saveAddress({
|
||||
required AuthSession session,
|
||||
required AddressEntry address,
|
||||
});
|
||||
|
||||
Future<List<AddressRecord>> getMyAddresses({required AuthSession session});
|
||||
}
|
||||
|
||||
class HttpAddressRepository implements AddressRepository {
|
||||
const HttpAddressRepository({http.Client? client}) : _client = client;
|
||||
|
||||
final http.Client? _client;
|
||||
|
||||
@override
|
||||
Future<void> saveAddress({
|
||||
required AuthSession session,
|
||||
required AddressEntry address,
|
||||
}) async {
|
||||
final uri = Uri.parse('${AppConfig.apiBaseUrl}/addresses');
|
||||
|
||||
late final http.Response response;
|
||||
try {
|
||||
response = await (_client ?? http.Client()).post(
|
||||
uri,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
'Authorization': 'Bearer ${session.token}',
|
||||
},
|
||||
body: jsonEncode(address.toJson()),
|
||||
);
|
||||
} catch (_) {
|
||||
throw AddressException(
|
||||
'No se pudo conectar con el backend en ${AppConfig.apiBaseUrl}.',
|
||||
);
|
||||
}
|
||||
|
||||
if (response.statusCode < 200 || response.statusCode >= 300) {
|
||||
final payload = jsonDecode(response.body);
|
||||
final message = payload['message']?.toString() ?? 'Error al guardar la dirección.';
|
||||
throw AddressException(message);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<AddressRecord>> getMyAddresses({required AuthSession session}) async {
|
||||
final uri = Uri.parse('${AppConfig.apiBaseUrl}/addresses/me');
|
||||
|
||||
late final http.Response response;
|
||||
try {
|
||||
response = await (_client ?? http.Client()).get(
|
||||
uri,
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Authorization': 'Bearer ${session.token}',
|
||||
},
|
||||
);
|
||||
} catch (_) {
|
||||
throw AddressException(
|
||||
'No se pudo conectar con el backend en ${AppConfig.apiBaseUrl}.',
|
||||
);
|
||||
}
|
||||
|
||||
if (response.statusCode < 200 || response.statusCode >= 300) {
|
||||
final payload = jsonDecode(response.body);
|
||||
final message = payload['message']?.toString() ?? 'Error al obtener las direcciones.';
|
||||
throw AddressException(message);
|
||||
}
|
||||
|
||||
final decoded = jsonDecode(response.body);
|
||||
if (decoded is! List) {
|
||||
return <AddressRecord>[];
|
||||
}
|
||||
|
||||
return decoded
|
||||
.whereType<Map<String, dynamic>>()
|
||||
.map(AddressRecord.fromJson)
|
||||
.toList(growable: false);
|
||||
}
|
||||
}
|
||||
|
||||
class AddressException implements Exception {
|
||||
AddressException(this.message);
|
||||
final String message;
|
||||
|
||||
@override
|
||||
String toString() => message;
|
||||
}
|
||||
132
lib/services/auth_repository.dart
Normal file
132
lib/services/auth_repository.dart
Normal file
@@ -0,0 +1,132 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
import '../app_config.dart';
|
||||
import '../models/auth_session.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 HttpAuthRepository implements AuthRepository {
|
||||
const HttpAuthRepository({http.Client? client}) : _client = client;
|
||||
|
||||
final http.Client? _client;
|
||||
|
||||
static const String _tokenKey = 'auth_token';
|
||||
static const String _emailKey = 'auth_email';
|
||||
static const String _nameKey = 'auth_name';
|
||||
|
||||
@override
|
||||
Future<AuthSession?> restoreSession() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
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 uri = Uri.parse('${AppConfig.apiBaseUrl}$endpoint');
|
||||
|
||||
late final http.Response response;
|
||||
try {
|
||||
response = await (_client ?? http.Client()).post(
|
||||
uri,
|
||||
headers: const <String, String>{
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
body: jsonEncode(body),
|
||||
);
|
||||
} catch (_) {
|
||||
throw AuthException(
|
||||
'No se pudo conectar con el backend en ${AppConfig.apiBaseUrl}. Verifica que el servicio esté activo.',
|
||||
);
|
||||
}
|
||||
|
||||
final Map<String, dynamic> payload = _decodeJson(response.body);
|
||||
if (response.statusCode < 200 || response.statusCode >= 300) {
|
||||
final message = payload['message']?.toString() ?? 'Credenciales inválidas o usuario no disponible.';
|
||||
throw AuthException(message);
|
||||
}
|
||||
|
||||
final token = payload['token']?.toString();
|
||||
if (token == null || token.isEmpty) {
|
||||
throw AuthException('El backend respondió sin token de sesión.');
|
||||
}
|
||||
|
||||
final user = payload['user'] is Map<String, dynamic>
|
||||
? payload['user'] as Map<String, dynamic>
|
||||
: <String, dynamic>{};
|
||||
final emailValue = (user['email'] ?? body['email'])?.toString() ?? body['email'].toString();
|
||||
final displayName = (user['name'] ?? user['fullName'] ?? body['name'] ?? emailValue).toString();
|
||||
|
||||
final session = AuthSession(token: token, email: emailValue, displayName: displayName);
|
||||
await _persistSession(session);
|
||||
return session;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
Map<String, dynamic> _decodeJson(String responseBody) {
|
||||
if (responseBody.trim().isEmpty) {
|
||||
return <String, dynamic>{};
|
||||
}
|
||||
|
||||
final decoded = jsonDecode(responseBody);
|
||||
if (decoded is Map<String, dynamic>) {
|
||||
return decoded;
|
||||
}
|
||||
|
||||
return <String, dynamic>{};
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> signOut() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.remove(_tokenKey);
|
||||
await prefs.remove(_emailKey);
|
||||
await prefs.remove(_nameKey);
|
||||
}
|
||||
}
|
||||
|
||||
class AuthException implements Exception {
|
||||
AuthException(this.message);
|
||||
|
||||
final String message;
|
||||
|
||||
@override
|
||||
String toString() => message;
|
||||
}
|
||||
Reference in New Issue
Block a user