2023-07-29 18:10:10 +02:00
|
|
|
import 'package:http/http.dart' as http;
|
|
|
|
import 'dart:convert';
|
|
|
|
import 'dart:html';
|
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
|
|
|
|
|
|
|
class ApiClient {
|
|
|
|
final String baseUrl;
|
2023-07-29 18:27:11 +02:00
|
|
|
String? token;
|
|
|
|
String? refreshToken;
|
2023-07-29 18:10:10 +02:00
|
|
|
http.Client httpClient = http.Client();
|
|
|
|
|
|
|
|
ApiClient({
|
|
|
|
required this.baseUrl,
|
|
|
|
}) {
|
|
|
|
if (window.localStorage.containsKey('token')) {
|
2023-07-29 18:27:11 +02:00
|
|
|
token = window.localStorage['token'];
|
2023-07-29 18:10:10 +02:00
|
|
|
}
|
|
|
|
if (window.localStorage.containsKey('refreshToken')) {
|
2023-07-29 18:27:11 +02:00
|
|
|
refreshToken = window.localStorage['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';
|
|
|
|
}
|
|
|
|
|
2023-07-29 18:27:11 +02:00
|
|
|
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}');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-30 13:09:41 +02:00
|
|
|
Future<void> delete(String path) async {
|
2023-07-29 18:10:10 +02:00
|
|
|
final response = await httpClient.delete(
|
|
|
|
Uri.parse('$baseUrl$path'),
|
2023-07-29 18:27:11 +02:00
|
|
|
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),
|
2023-07-29 18:27:11 +02:00
|
|
|
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;
|
|
|
|
window.localStorage['token'] = token;
|
|
|
|
|
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;
|
|
|
|
window.localStorage['refreshToken'] = refreshToken;
|
|
|
|
}
|
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;
|
|
|
|
window.localStorage['token'] = token!;
|
|
|
|
refreshToken = response['refresh_token'] as String;
|
|
|
|
window.localStorage['refreshToken'] = refreshToken!;
|
|
|
|
}
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
void logout() {
|
|
|
|
token = null;
|
|
|
|
refreshToken = null;
|
|
|
|
window.localStorage.remove('token');
|
|
|
|
window.localStorage.remove('refreshToken');
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
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
|
|
|
}
|
2023-07-30 13:09:41 +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, {
|
2023-07-30 13:09:41 +02:00
|
|
|
required double grams,
|
|
|
|
required int productId,
|
|
|
|
required int mealId,
|
2023-08-28 14:45:32 +02:00
|
|
|
}) async {
|
2023-07-30 13:09:41 +02:00
|
|
|
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
|
|
|
|
2023-08-28 14:45:32 +02:00
|
|
|
Future<void> addMeal(
|
|
|
|
{required String name, required int diaryId, required int order}) async {
|
2023-07-30 14:40:45 +02:00
|
|
|
await post("/meal", {
|
|
|
|
"name": name,
|
|
|
|
"diary_id": diaryId,
|
|
|
|
"order": order,
|
|
|
|
});
|
|
|
|
}
|
2023-07-30 15:05:20 +02:00
|
|
|
|
2023-10-27 17:10:09 +02:00
|
|
|
Future<void> addMealFromPreset(
|
|
|
|
{required String name, required int diaryId, required int order, required int presetId}) async {
|
|
|
|
await post("/meal/from_preset", {
|
|
|
|
"name": name,
|
|
|
|
"diary_id": diaryId,
|
|
|
|
"order": order,
|
|
|
|
"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
|
|
|
}
|