fooder-app/lib/client.dart

329 lines
8.1 KiB
Dart
Raw Normal View History

2023-07-29 18:10:10 +02:00
import 'package:http/http.dart' as http;
import 'dart:convert';
2023-07-30 20:44:50 +02:00
import 'package:intl/intl.dart';
2023-10-27 17:10:09 +02:00
import 'package:fooder/models/meal.dart';
2023-07-29 18:10:10 +02:00
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
2023-07-29 18:10:10 +02:00
class ApiClient {
final String baseUrl;
String? token;
String? refreshToken;
2023-07-29 18:10:10 +02:00
http.Client httpClient = http.Client();
2024-03-30 14:07:10 +01:00
final FlutterSecureStorage storage = const FlutterSecureStorage();
2023-07-29 18:10:10 +02:00
ApiClient({
required this.baseUrl,
});
static Future<ApiClient> create({required String baseUrl}) async {
var client = ApiClient(baseUrl: baseUrl);
client.loadToken();
return client;
}
Future<void> loadToken() async {
Map<String, String> allValues = await storage.readAll();
if (allValues.containsKey('token')) {
token = allValues['token'];
2023-07-29 18:10:10 +02:00
}
if (allValues.containsKey('refreshToken')) {
refreshToken = allValues['refreshToken'];
2023-07-29 18:10:10 +02:00
}
}
2023-07-30 13:42:38 +02:00
Map<String, String> headers({bool forGet = false, bool forLogin = false}) {
if (token == null && !forLogin) {
2023-07-29 18:10:10 +02:00
throw Exception('Not logged in');
}
final headers = {
'Accept': 'application/json',
};
2023-07-29 20:55:32 +02:00
if (!forGet) {
headers['Content-Type'] = 'application/json';
}
if (token != null) {
headers['Authorization'] = 'Bearer $token';
2023-07-29 18:10:10 +02:00
}
return headers;
}
2023-07-29 23:54:51 +02:00
Map<String, dynamic> _jsonDecode(http.Response response) {
try {
return jsonDecode(utf8.decode(response.bodyBytes));
} catch (e) {
throw Exception('Response returned status code: ${response.statusCode}');
}
}
2023-07-29 18:10:10 +02:00
Future<Map<String, dynamic>> get(String path) async {
final response = await httpClient.get(
Uri.parse('$baseUrl$path'),
2023-07-29 20:55:32 +02:00
headers: headers(forGet: true),
2023-07-29 18:10:10 +02:00
);
2023-07-29 20:01:56 +02:00
2023-08-27 00:03:50 +02:00
if (response.statusCode == 401) {
2023-08-27 00:02:33 +02:00
await refresh();
return await get(path);
}
2023-07-29 20:01:56 +02:00
if (response.statusCode != 200) {
throw Exception('Response returned status code: ${response.statusCode}');
}
2023-07-29 23:54:51 +02:00
return _jsonDecode(response);
2023-07-29 18:10:10 +02:00
}
2023-08-28 14:45:32 +02:00
Future<Map<String, dynamic>> post(String path, Map<String, dynamic> body,
{bool forLogin = false}) async {
2023-07-29 18:10:10 +02:00
final response = await httpClient.post(
Uri.parse('$baseUrl$path'),
body: jsonEncode(body),
2023-07-30 13:42:38 +02:00
headers: headers(forLogin: forLogin),
2023-07-29 18:10:10 +02:00
);
2023-07-29 20:01:56 +02:00
2023-08-27 00:03:50 +02:00
if (response.statusCode == 401) {
2023-08-27 00:02:33 +02:00
await refresh();
return await post(path, body, forLogin: forLogin);
}
2023-07-29 20:01:56 +02:00
if (response.statusCode != 200) {
throw Exception('Response returned status code: ${response.statusCode}');
}
2023-07-29 23:54:51 +02:00
return _jsonDecode(response);
2023-07-29 18:10:10 +02:00
}
2023-10-27 17:10:09 +02:00
Future<void> postNoResult(String path, Map<String, dynamic> 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<void> delete(String path) async {
2023-07-29 18:10:10 +02:00
final response = await httpClient.delete(
Uri.parse('$baseUrl$path'),
headers: headers(),
2023-07-29 18:10:10 +02:00
);
2023-07-29 20:01:56 +02:00
2023-08-27 00:03:50 +02:00
if (response.statusCode == 401) {
2023-08-27 00:02:33 +02:00
await refresh();
return await delete(path);
}
2023-07-29 20:01:56 +02:00
if (response.statusCode != 200) {
throw Exception('Response returned status code: ${response.statusCode}');
}
2023-07-29 18:10:10 +02:00
}
2023-08-28 14:45:32 +02:00
Future<Map<String, dynamic>> patch(
String path, Map<String, dynamic> body) async {
2023-07-29 18:10:10 +02:00
final response = await httpClient.patch(
Uri.parse('$baseUrl$path'),
body: jsonEncode(body),
headers: headers(),
2023-07-29 18:10:10 +02:00
);
2023-07-29 20:01:56 +02:00
2023-08-27 00:03:50 +02:00
if (response.statusCode == 401) {
2023-08-27 00:02:33 +02:00
await refresh();
return await patch(path, body);
}
2023-07-29 20:01:56 +02:00
if (response.statusCode != 200) {
throw Exception('Response returned status code: ${response.statusCode}');
}
2023-07-29 23:54:51 +02:00
return _jsonDecode(response);
2023-07-29 18:10:10 +02:00
}
2023-07-29 18:42:29 +02:00
Future<void> login(String username, String password) async {
2023-07-29 18:10:10 +02:00
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');
}
2023-08-28 14:45:32 +02:00
2023-07-29 23:54:51 +02:00
final token = _jsonDecode(response)['access_token'];
2023-07-29 18:10:10 +02:00
this.token = token;
await storage.write(key: 'token', value: token);
2023-07-29 18:10:10 +02:00
2023-07-29 23:54:51 +02:00
final refreshToken = _jsonDecode(response)['refresh_token'];
2023-07-29 18:10:10 +02:00
this.refreshToken = refreshToken;
await storage.write(key: 'refreshToken', value: refreshToken);
2023-07-29 18:10:10 +02:00
}
2023-07-29 20:01:56 +02:00
Future<void> refresh() async {
if (refreshToken == null) {
throw Exception("No valid refresh token found");
}
2023-08-28 14:45:32 +02:00
final response = await post("/token/refresh", {
"refresh_token": refreshToken,
});
2023-07-29 20:01:56 +02:00
token = response['access_token'] as String;
await storage.write(key: 'token', value: token);
2023-07-29 20:01:56 +02:00
refreshToken = response['refresh_token'] as String;
await storage.write(key: 'refreshToken', value: refreshToken);
2023-07-29 20:01:56 +02:00
}
2023-07-29 20:55:32 +02:00
2023-07-29 23:54:51 +02:00
Future<Map<String, dynamic>> getDiary({required DateTime date}) async {
2023-07-30 20:44:50 +02:00
var formatter = DateFormat('yyyy-MM-dd');
2023-07-29 23:54:51 +02:00
var params = {
2023-07-30 20:44:50 +02:00
"date": formatter.format(date),
2023-07-29 23:54:51 +02:00
};
var response = await get("/diary?${Uri(queryParameters: params).query}");
return response;
}
Future<void> logout() async {
2023-07-29 23:54:51 +02:00
token = null;
refreshToken = null;
await storage.deleteAll();
2023-07-29 23:54:51 +02:00
}
Future<Map<String, dynamic>> getProducts(String q) async {
2023-08-28 14:45:32 +02:00
var response =
await get("/product?${Uri(queryParameters: {"q": q}).query}");
2023-07-29 23:54:51 +02:00
return response;
}
2024-03-25 19:46:03 +01:00
Future<Map<String, dynamic>> getProductByBarcode(String barcode) async {
2024-03-30 14:07:10 +01:00
var response = await get("/product/by_barcode?${Uri(queryParameters: {
"barcode": barcode
}).query}");
2024-03-25 19:46:03 +01:00
return response;
}
2023-10-27 17:10:09 +02:00
Future<Map<String, dynamic>> getPresets(String? q) async {
var response = await get("/preset?${Uri(queryParameters: {"q": q}).query}");
return response;
}
2023-07-29 23:54:51 +02:00
Future<void> addEntry({
required double grams,
required int productId,
required int mealId,
2023-08-28 14:45:32 +02:00
}) async {
2023-07-29 23:54:51 +02:00
var entry = {
"grams": grams,
"product_id": productId,
"meal_id": mealId,
};
await post("/entry", entry);
2023-07-29 20:55:32 +02:00
}
Future<void> deleteEntry(int id) async {
await delete("/entry/$id");
}
2023-10-27 17:10:09 +02:00
Future<void> deleteMeal(int id) async {
await delete("/meal/$id");
}
Future<void> deletePreset(int id) async {
await delete("/preset/$id");
}
2023-08-28 14:45:32 +02:00
Future<void> updateEntry(
int id, {
required double grams,
required int productId,
required int mealId,
2023-08-28 14:45:32 +02:00
}) async {
var entry = {
"grams": grams,
"product_id": productId,
"meal_id": mealId,
};
await patch("/entry/$id", entry);
}
2023-07-30 13:42:38 +02:00
Future<void> register(String username, String password) async {
try {
2023-08-28 14:45:32 +02:00
await post(
"/user",
{
2023-07-30 13:42:38 +02:00
"username": username,
"password": password,
},
forLogin: true,
);
} catch (e) {
throw Exception("Failed to register");
}
}
2023-07-30 14:40:45 +02:00
2024-03-30 14:07:10 +01:00
Future<void> addMeal({required String name, required int diaryId}) async {
2023-07-30 14:40:45 +02:00
await post("/meal", {
"name": name,
"diary_id": diaryId,
});
}
2023-07-30 15:05:20 +02:00
2023-10-27 17:10:09 +02:00
Future<void> addMealFromPreset(
2024-03-30 14:07:10 +01:00
{required String name,
required int diaryId,
required int presetId}) async {
2023-10-27 17:10:09 +02:00
await post("/meal/from_preset", {
"name": name,
"diary_id": diaryId,
"preset_id": presetId,
});
}
2023-07-30 15:05:20 +02:00
Future<Map<String, dynamic>> addProduct({
required String name,
required double protein,
required double carb,
required double fat,
2023-07-30 20:44:50 +02:00
required double fiber,
2023-07-30 15:05:20 +02:00
}) async {
var response = await post("/product", {
"name": name,
"protein": protein,
"carb": carb,
"fat": fat,
2023-07-30 20:44:50 +02:00
"fiber": fiber,
2023-07-30 15:05:20 +02:00
});
return response;
}
2023-10-27 17:10:09 +02:00
Future<void> saveMeal(Meal meal, String name) async {
await postNoResult("/meal/${meal.id}/save", {
"name": name,
});
}
2023-07-29 18:10:10 +02:00
}