import 'package:http/http.dart' as http; import 'dart:convert'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; class ApiClient { final String baseUrl; String? token; String? refreshToken; http.Client httpClient = http.Client(); final FlutterSecureStorage storage = const FlutterSecureStorage(); ApiClient({ required this.baseUrl, }); static Future create({required String baseUrl}) async { var client = ApiClient(baseUrl: baseUrl); client.loadToken(); return client; } Future loadToken() async { Map allValues = await storage.readAll(); if (allValues.containsKey('token')) { token = allValues['token']; } if (allValues.containsKey('refreshToken')) { refreshToken = allValues['refreshToken']; } } Map headers({bool forGet = false, bool forLogin = false}) { if (token == null && !forLogin) { throw Exception('Not logged in'); } final headers = { 'Accept': 'application/json', }; if (!forGet) { headers['Content-Type'] = 'application/json'; } if (token != null) { headers['Authorization'] = 'Bearer $token'; } return headers; } Map _jsonDecode(http.Response response) { try { return jsonDecode(utf8.decode(response.bodyBytes)); } catch (e) { throw Exception('Response returned status code: ${response.statusCode}'); } } Future> get(String path) async { final response = await httpClient.get( Uri.parse('$baseUrl$path'), headers: headers(forGet: true), ); if (response.statusCode == 401) { await refresh(); return await get(path); } if (response.statusCode != 200) { throw Exception('Response returned status code: ${response.statusCode}'); } return _jsonDecode(response); } Future> post(String path, Map body, {bool forLogin = false}) async { final response = await httpClient.post( Uri.parse('$baseUrl$path'), body: jsonEncode(body), headers: headers(forLogin: forLogin), ); if (response.statusCode == 401) { await refresh(); return await post(path, body, forLogin: forLogin); } if (response.statusCode != 200) { throw Exception('Response returned status code: ${response.statusCode}'); } return _jsonDecode(response); } Future postNoResult(String path, Map body, {bool forLogin = false, bool empty = false}) async { final response = await httpClient.post( Uri.parse('$baseUrl$path'), body: jsonEncode(body), headers: headers(forLogin: forLogin), ); if (response.statusCode == 401) { await refresh(); return await postNoResult(path, body, forLogin: forLogin); } if (response.statusCode != 200) { throw Exception('Response returned status code: ${response.statusCode}'); } } Future delete(String path) async { final response = await httpClient.delete( Uri.parse('$baseUrl$path'), headers: headers(), ); if (response.statusCode == 401) { await refresh(); return await delete(path); } if (response.statusCode != 200) { throw Exception('Response returned status code: ${response.statusCode}'); } } Future> patch( String path, Map body) async { final response = await httpClient.patch( Uri.parse('$baseUrl$path'), body: jsonEncode(body), headers: headers(), ); if (response.statusCode == 401) { await refresh(); return await patch(path, body); } if (response.statusCode != 200) { throw Exception('Response returned status code: ${response.statusCode}'); } return _jsonDecode(response); } Future login(String username, String password) async { final headers = { 'Content-Type': 'application/x-www-form-urlencoded', 'Accept': 'application/json', }; final response = await httpClient.post( Uri.parse('$baseUrl/token'), body: { 'username': username, 'password': password, }, encoding: Encoding.getByName('utf-8'), headers: headers, ); if (response.statusCode != 200) { throw Exception('Failed to login'); } final token = _jsonDecode(response)['access_token']; this.token = token; await storage.write(key: 'token', value: token); final refreshToken = _jsonDecode(response)['refresh_token']; this.refreshToken = refreshToken; await storage.write(key: 'refreshToken', value: refreshToken); } Future refresh() async { if (refreshToken == null) { throw Exception("No valid refresh token found"); } final response = await post("/token/refresh", { "refresh_token": refreshToken, }); token = response['access_token'] as String; await storage.write(key: 'token', value: token); refreshToken = response['refresh_token'] as String; await storage.write(key: 'refreshToken', value: refreshToken); } Future logout() async { token = null; refreshToken = null; await storage.deleteAll(); } Future register(String username, String password) async { try { await post( "/user", { "username": username, "password": password, }, forLogin: true, ); } catch (e) { throw Exception("Failed to register"); } } }