Files
Lizandro GuarnizoandClaude Sonnet 4.6 733384091c feat: migrate prosappco from Firebase to NestJS REST API (Fase 2)
- Replace all Firebase* repositories with Api* repositories using HTTP + SharedPreferences JWT
- Remove Firebase.initializeApp() and firebase_messaging background handler from main.dart
- Update DI (app_di.dart) to inject Api* repositories instead of Firebase* ones
- Replace all Timestamp/cloud_firestore usage with ISO 8601 String dates
- Stub PhoneVerificationService (Firebase phone OTP → backend OTP when implemented)
- Add ApiService singleton with JWT management in lib/services/
- Legacy firebase_*_repository.dart files preserved for Fase 4 cleanup

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-17 16:53:11 -05:00

93 lines
3.0 KiB
Dart

import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:shared_preferences/shared_preferences.dart';
const String _baseUrl = 'https://backend.prosapp.co/api/v1';
class ApiException implements Exception {
final int statusCode;
final String message;
ApiException(this.statusCode, this.message);
@override
String toString() => 'ApiException($statusCode): $message';
}
class ApiService {
static ApiService? _instance;
static ApiService get instance => _instance ??= ApiService._();
ApiService._();
String? _token;
Future<String?> getToken() async {
if (_token != null) return _token;
final prefs = await SharedPreferences.getInstance();
_token = prefs.getString('token');
return _token;
}
Future<void> saveToken(String token) async {
_token = token;
final prefs = await SharedPreferences.getInstance();
await prefs.setString('token', token);
}
Future<void> clearToken() async {
_token = null;
final prefs = await SharedPreferences.getInstance();
await prefs.remove('token');
}
Future<Map<String, String>> _headers({bool auth = true}) async {
final headers = <String, String>{'Content-Type': 'application/json'};
if (auth) {
final token = await getToken();
if (token != null) headers['Authorization'] = 'Bearer $token';
}
return headers;
}
dynamic _parse(http.Response res) {
final body = jsonDecode(res.body);
if (res.statusCode >= 200 && res.statusCode < 300) return body;
final msg = body is Map ? (body['message'] ?? res.reasonPhrase) : res.reasonPhrase;
throw ApiException(res.statusCode, msg.toString());
}
Future<dynamic> get(String path, {bool auth = true, Map<String, String>? query}) async {
final uri = Uri.parse('$_baseUrl$path').replace(queryParameters: query);
final res = await http.get(uri, headers: await _headers(auth: auth));
return _parse(res);
}
Future<dynamic> post(String path, Map<String, dynamic> body, {bool auth = false}) async {
final res = await http.post(
Uri.parse('$_baseUrl$path'),
headers: await _headers(auth: auth),
body: jsonEncode(body),
);
return _parse(res);
}
Future<dynamic> patch(String path, Map<String, dynamic> body, {bool auth = true}) async {
final res = await http.patch(
Uri.parse('$_baseUrl$path'),
headers: await _headers(auth: auth),
body: jsonEncode(body),
);
return _parse(res);
}
Future<String> uploadFile(String filePath) async {
final token = await getToken();
final req = http.MultipartRequest('POST', Uri.parse('$_baseUrl/storage/upload'));
if (token != null) req.headers['Authorization'] = 'Bearer $token';
req.files.add(await http.MultipartFile.fromPath('file', filePath));
final streamed = await req.send();
final res = await http.Response.fromStream(streamed);
final body = jsonDecode(res.body);
if (res.statusCode >= 200 && res.statusCode < 300) return body['url'] as String;
throw ApiException(res.statusCode, body['message']?.toString() ?? 'Upload failed');
}
}