import 'dart:convert'; import 'package:http/http.dart' as http; import 'api_config.dart'; /// Thrown for any non-2xx response. `errors` is populated for Laravel's /// standard 422 validation shape (`{"message": ..., "errors": {field: [...]}}`). class ApiException implements Exception { ApiException(this.statusCode, this.message, {this.errors}); final int statusCode; final String message; final Map>? errors; bool get isUnauthorized => statusCode == 401; @override String toString() => message; } /// Thin wrapper over `package:http` for the `/api/v1` backend: builds the /// URL, attaches the bearer token (set by AuthService after login), decodes /// JSON, and turns non-2xx responses into [ApiException]. class ApiClient { String? _token; void setToken(String? token) => _token = token; Uri _uri(String path) => Uri.parse('${ApiConfig.apiV1Url}$path'); Map get _headers => { 'Accept': 'application/json', 'Content-Type': 'application/json', if (_token != null) 'Authorization': 'Bearer $_token', }; Future get(String path) async { return _handle(await http.get(_uri(path), headers: _headers)); } Future post(String path, [Map? body]) async { return _handle(await http.post( _uri(path), headers: _headers, body: body == null ? null : jsonEncode(body), )); } Future put(String path, [Map? body]) async { return _handle(await http.put( _uri(path), headers: _headers, body: body == null ? null : jsonEncode(body), )); } Future delete(String path) async { return _handle(await http.delete(_uri(path), headers: _headers)); } dynamic _handle(http.Response response) { final ok = response.statusCode >= 200 && response.statusCode < 300; if (response.body.isEmpty) { if (ok) return null; throw ApiException(response.statusCode, 'Ошибка запроса (${response.statusCode})'); } final decoded = jsonDecode(utf8.decode(response.bodyBytes)); if (ok) return decoded; final message = decoded is Map && decoded['message'] is String ? decoded['message'] as String : 'Ошибка запроса (${response.statusCode})'; Map>? errors; if (decoded is Map && decoded['errors'] is Map) { errors = (decoded['errors'] as Map).map( (key, value) => MapEntry(key as String, List.from(value as List)), ); } throw ApiException(response.statusCode, message, errors: errors); } }