feat: refactor API paths for authentication, inventory, and recipes; add contract tests for repositories

This commit is contained in:
Nils-Johan Gynther
2026-04-22 10:21:07 +02:00
parent 655adf66ae
commit c163821bad
8 changed files with 246 additions and 19 deletions
@@ -1,5 +1,6 @@
import '../../../core/api/api_client.dart';
import '../../../core/api/api_exception.dart';
import '../../../core/api/api_paths.dart';
import '../../../core/platform/token_storage.dart';
class AuthRepository {
@@ -11,7 +12,7 @@ class AuthRepository {
Future<String> login(String username, String password) async {
try {
final data = await _api.postJson(
'/auth/login',
AuthApiPaths.login,
body: {'username': username, 'password': password},
);
@@ -1,4 +1,5 @@
import '../../../core/api/api_client.dart';
import '../../../core/api/api_paths.dart';
import '../domain/inventory_item.dart';
import '../domain/inventory_consumption.dart';
@@ -20,21 +21,21 @@ class InventoryRepository {
? ''
: '?${params.entries.map((e) => '${e.key}=${Uri.encodeComponent(e.value)}').join('&')}';
final data = await _api.getJson('/inventory$query', token: token);
final data = await _api.getJson('${InventoryApiPaths.list}$query', token: token);
final list = data as List<dynamic>;
return list.map((e) => InventoryItem.fromJson(e as Map<String, dynamic>)).toList();
}
Future<InventoryItem> fetchInventoryItem(int id, {String? token}) async {
final data = await _api.getJson('/inventory/$id', token: token);
return InventoryItem.fromJson(data as Map<String, dynamic>);
final items = await fetchInventory(token: token);
return items.firstWhere((item) => item.id == id);
}
Future<InventoryItem> createInventoryItem(
Map<String, dynamic> body, {
String? token,
}) async {
final data = await _api.postJson('/inventory', body: body, token: token);
final data = await _api.postJson(InventoryApiPaths.list, body: body, token: token);
return InventoryItem.fromJson(data as Map<String, dynamic>);
}
@@ -43,12 +44,12 @@ class InventoryRepository {
Map<String, dynamic> body, {
String? token,
}) async {
final data = await _api.patchJson('/inventory/$id', body: body, token: token);
final data = await _api.patchJson(InventoryApiPaths.update(id), body: body, token: token);
return InventoryItem.fromJson(data as Map<String, dynamic>);
}
Future<void> deleteInventoryItem(int id, {String? token}) async {
await _api.deleteJson('/inventory/$id', token: token);
await _api.deleteJson(InventoryApiPaths.remove(id), token: token);
}
Future<InventoryItem> consumeInventoryItem(
@@ -59,7 +60,7 @@ class InventoryRepository {
}) async {
final body = <String, dynamic>{'amountUsed': amountUsed};
if (comment != null && comment.isNotEmpty) body['comment'] = comment;
final data = await _api.postJson('/inventory/$id/consume', body: body, token: token);
final data = await _api.postJson(InventoryApiPaths.consume(id), body: body, token: token);
return InventoryItem.fromJson(data as Map<String, dynamic>);
}
@@ -67,7 +68,7 @@ class InventoryRepository {
int id, {
String? token,
}) async {
final data = await _api.getJson('/inventory/$id/consumption-history', token: token);
final data = await _api.getJson(InventoryApiPaths.consumptionHistory(id), token: token);
final list = data as List<dynamic>;
return list
.map((e) => InventoryConsumption.fromJson(e as Map<String, dynamic>))
@@ -3,6 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import '../../../core/api/api_error_mapper.dart';
import '../../../core/api/api_paths.dart';
import '../../../core/api/api_providers.dart';
import '../../../core/forms/form_options.dart';
import '../../auth/data/auth_providers.dart';
@@ -54,7 +55,7 @@ class _CreateInventoryScreenState
try {
final token = await ref.read(authStateProvider.future);
final api = ref.read(apiClientProvider);
final data = await api.getJson('/products', token: token);
final data = await api.getJson(ProductApiPaths.list, token: token);
if (mounted) {
setState(() {
_products = (data as List<dynamic>)
@@ -1,5 +1,6 @@
import '../../../core/api/api_client.dart';
import '../../../core/api/api_exception.dart';
import '../../../core/api/api_paths.dart';
import '../domain/parsed_recipe.dart';
import '../domain/recipe.dart';
@@ -10,7 +11,7 @@ class RecipeRepository {
Future<List<Recipe>> fetchRecipes({String? token}) async {
try {
final data = await _api.getJson('/recipes', token: token);
final data = await _api.getJson(RecipeApiPaths.list, token: token);
if (data is! List) {
throw const ApiException(
type: ApiErrorType.unknown, message: 'Ogiltigt svar fran servern.');
@@ -28,7 +29,7 @@ class RecipeRepository {
Future<Recipe> fetchRecipeDetail(int id, {String? token}) async {
try {
final data = await _api.getJson('/recipes/$id', token: token);
final data = await _api.getJson(RecipeApiPaths.detail(id), token: token);
if (data is! Map<String, dynamic>) {
throw const ApiException(
type: ApiErrorType.unknown, message: 'Ogiltigt svar fran servern.');
@@ -45,8 +46,8 @@ class RecipeRepository {
Future<Recipe> createRecipe(Map<String, dynamic> body,
{String? token}) async {
try {
final data =
await _api.postJson('/recipes', body: body, token: token);
final data =
await _api.postJson(RecipeApiPaths.list, body: body, token: token);
if (data is! Map<String, dynamic>) {
throw const ApiException(
type: ApiErrorType.unknown, message: 'Ogiltigt svar fran servern.');
@@ -63,8 +64,11 @@ class RecipeRepository {
Future<Recipe> updateRecipe(int id, Map<String, dynamic> body,
{String? token}) async {
try {
final data =
await _api.patchJson('/recipes/$id', body: body, token: token);
final data = await _api.patchJson(
RecipeApiPaths.update(id),
body: body,
token: token,
);
if (data is! Map<String, dynamic>) {
throw const ApiException(
type: ApiErrorType.unknown, message: 'Ogiltigt svar fran servern.');
@@ -80,7 +84,7 @@ class RecipeRepository {
Future<void> deleteRecipe(int id, {String? token}) async {
try {
await _api.deleteJson('/recipes/$id', token: token);
await _api.deleteJson(RecipeApiPaths.remove(id), token: token);
} on ApiException {
rethrow;
} catch (_) {
@@ -93,7 +97,7 @@ class RecipeRepository {
{String? token}) async {
try {
final data = await _api.postJson(
'/recipes/parse-markdown',
RecipeApiPaths.parseMarkdown,
body: {'markdown': markdown},
token: token,
);
@@ -4,6 +4,7 @@ import 'package:go_router/go_router.dart';
import '../../../core/api/api_error_mapper.dart';
import '../../../core/api/api_exception.dart';
import '../../../core/api/api_paths.dart';
import '../../../core/api/api_providers.dart';
import '../../../core/forms/form_options.dart';
import '../../../core/ui/async_state_views.dart';
@@ -109,7 +110,7 @@ class _RecipeEditScreenState extends ConsumerState<RecipeEditScreen> {
try {
final token = await ref.read(authStateProvider.future);
final api = ref.read(apiClientProvider);
final data = await api.getJson('/products', token: token);
final data = await api.getJson(ProductApiPaths.list, token: token);
if (!mounted) return;
final products = (data as List<dynamic>)
.map((e) => e as Map<String, dynamic>)