aves_mio18/lib/remote/remote_http_api.dart
2026-04-14 09:53:02 +02:00

110 lines
3.5 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'dart:convert';
import 'package:http/http.dart' as http;
import 'auth_client.dart';
class RemoteHttpApi {
RemoteHttpApi({
required this.baseUrl,
required this.auth,
this.timeout = const Duration(seconds: 20),
}) : _base = Uri.parse(baseUrl.endsWith('/') ? baseUrl : '$baseUrl/');
final String baseUrl;
final Uri _base;
final RemoteAuth auth;
final Duration timeout;
// -------------------------
// Helpers
// -------------------------
Future<http.Response> _getWithAuth(Uri uri) async {
var headers = await auth.authHeaders();
http.Response res;
try {
res = await http.get(uri, headers: headers).timeout(timeout);
} catch (e) {
throw Exception('GET fallita: errore di rete verso $uri: $e');
}
// Follow redirect 307/308 se presenti (raro ma possibile)
if ({307, 308}.contains(res.statusCode) && res.headers['location'] != null) {
final redirectUri = uri.resolve(res.headers['location']!);
try {
res = await http.get(redirectUri, headers: headers).timeout(timeout);
} catch (e) {
throw Exception('GET fallita: errore di rete verso $redirectUri: $e');
}
}
// Se token scaduto -> refresh e retry una sola volta
if (res.statusCode == 401) {
headers = await auth.refreshAndHeaders();
try {
res = await http.get(uri, headers: headers).timeout(timeout);
} catch (e) {
throw Exception('GET fallita dopo refresh token verso $uri: $e');
}
}
// Gestione errori HTTP
if (res.statusCode < 200 || res.statusCode >= 300) {
final snippet = utf8.decode(res.bodyBytes.take(300).toList());
throw Exception('HTTP ${res.statusCode} ${res.reasonPhrase} su $uri $snippet');
}
return res;
}
List<Map<String, dynamic>> _decodeList(http.Response r, {required String label}) {
final decoded = jsonDecode(utf8.decode(r.bodyBytes));
if (decoded is! List) {
throw Exception('$label: risposta non è una lista JSON');
}
return decoded.cast<Map<String, dynamic>>();
}
// -------------------------
// API
// -------------------------
Future<List<Map<String, dynamic>>> getAllPhotos() async {
final uri = _base.resolve('photos');
final r = await _getWithAuth(uri);
return _decodeList(r, label: 'getAllPhotos');
}
Future<List<Map<String, dynamic>>> getChanges(String sinceIso) async {
final uri = _base.resolve('photos/changes?since=${Uri.encodeComponent(sinceIso)}');
final r = await _getWithAuth(uri);
return _decodeList(r, label: 'getChanges');
}
Future<List<Map<String, dynamic>>> getDeletedHard(String sinceIso) async {
final uri = _base.resolve('photos/deleted_hard?since=${Uri.encodeComponent(sinceIso)}');
final r = await _getWithAuth(uri);
final decoded = jsonDecode(utf8.decode(r.bodyBytes));
if (decoded is! Map<String, dynamic>) {
throw Exception('getDeletedHard: risposta non è un oggetto JSON');
}
final deleted = (decoded['deleted'] ?? []) as List;
return deleted.cast<Map<String, dynamic>>();
}
Future<Map<String, dynamic>?> getPhotoById(String id) async {
final uri = _base.resolve('photos/$id');
final r = await _getWithAuth(uri);
final decoded = jsonDecode(utf8.decode(r.bodyBytes));
if (decoded is List && decoded.isNotEmpty) {
final first = decoded.first;
if (first is Map<String, dynamic>) return first;
throw Exception('getPhotoById: primo elemento non è un oggetto JSON');
}
return null;
}
}