Files
fin65-mobile/lib/services/api_service.dart
T

119 lines
3.7 KiB
Dart

import 'dart:convert';
import 'package:http/http.dart' as http;
import '../config/app_config.dart';
import '../models/account.dart';
import '../models/category.dart';
import '../models/dashboard.dart';
import '../models/transaction_item.dart';
import 'offline_store.dart';
class ApiService {
ApiService({OfflineStore? store}) : _store = store ?? OfflineStore.instance;
final OfflineStore _store;
static const String _baseUrlKey = 'api_base_url';
Future<String> getBaseUrl() async {
return await _store.getSetting(_baseUrlKey) ?? AppConfig.defaultApiBaseUrl;
}
Future<void> setBaseUrl(String value) async {
await _store.setSetting(
_baseUrlKey,
value.trim().replaceAll(RegExp(r'/$'), ''),
);
}
Future<Uri> _uri(String path) async {
final baseUrl = await getBaseUrl();
return Uri.parse('$baseUrl$path');
}
Future<dynamic> _getJson(String path) async {
final res = await http.get(await _uri(path)).timeout(const Duration(seconds: 5));
return _handleResponse(res);
}
Future<dynamic> _postJson(String path, Map<String, dynamic> body) async {
final res = await http
.post(
await _uri(path),
headers: {'Content-Type': 'application/json'},
body: jsonEncode(body),
)
.timeout(const Duration(seconds: 8));
return _handleResponse(res);
}
dynamic _handleResponse(http.Response res) {
if (res.statusCode >= 200 && res.statusCode < 300) {
if (res.body.isEmpty) return null;
return jsonDecode(utf8.decode(res.bodyBytes));
}
String message = 'Erreur HTTP ${res.statusCode}';
try {
final body = jsonDecode(utf8.decode(res.bodyBytes));
message = body['detail']?.toString() ?? body.toString();
} catch (_) {}
throw Exception(message);
}
Future<bool> healthCheck() async {
final data = await _getJson('/health');
return data is Map && data['status'] == 'ok';
}
Future<DashboardData> getDashboard() async {
final data = await _getJson('/dashboard');
return DashboardData.fromJson(data as Map<String, dynamic>);
}
Future<List<Account>> getAccounts() async {
final data = await _getJson('/accounts?active_only=true') as List<dynamic>;
return data.map((e) => Account.fromJson(e as Map<String, dynamic>)).toList();
}
Future<List<Category>> getCategories() async {
final data = await _getJson('/categories') as List<dynamic>;
return data.map((e) => Category.fromJson(e as Map<String, dynamic>)).toList();
}
Future<List<TransactionItem>> getTransactions({int limit = AppConfig.defaultTransactionLimit}) async {
final data = await _getJson('/transactions?limit=$limit') as List<dynamic>;
return data.map((e) => TransactionItem.fromJson(e as Map<String, dynamic>)).toList();
}
Future<TransactionItem> createTransactionFromPayload(Map<String, dynamic> payload) async {
final cleanPayload = Map<String, dynamic>.from(payload);
cleanPayload.removeWhere((_, value) => value == null);
final data = await _postJson('/transactions', cleanPayload);
return TransactionItem.fromJson(data as Map<String, dynamic>);
}
Future<TransactionItem> createTransaction({
required String label,
required double amount,
required String type,
required int accountId,
int? targetAccountId,
int? categoryId,
String? note,
DateTime? date,
}) async {
return createTransactionFromPayload({
'date': (date ?? DateTime.now()).toIso8601String(),
'label': label,
'amount': amount,
'type': type,
'account_id': accountId,
'target_account_id': targetAccountId,
'category_id': categoryId,
'created_from': 'android',
'note': note,
});
}
}