feat(import): implement recipe import functionality with file and URL support
Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import 'import_repository.dart';
|
||||
|
||||
final importRepositoryProvider = Provider<ImportRepository>(
|
||||
(_) => ImportRepository(),
|
||||
);
|
||||
@@ -0,0 +1,115 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
import '../../../core/api/api_exception.dart';
|
||||
import '../domain/quick_import_result.dart';
|
||||
|
||||
/// Handles communication with the quick-import API endpoint.
|
||||
///
|
||||
/// Two modes:
|
||||
/// • [importFile] — multipart upload (PDF / image bytes, max 10 MB).
|
||||
/// • [importUrl] — JSON body with `{ input: url }`.
|
||||
class ImportRepository {
|
||||
final http.Client _client;
|
||||
final String _baseUrl;
|
||||
|
||||
ImportRepository({http.Client? client})
|
||||
: _client = client ?? http.Client(),
|
||||
_baseUrl = const String.fromEnvironment(
|
||||
'API_BASE_URL',
|
||||
defaultValue: '/api',
|
||||
);
|
||||
|
||||
/// Upload a file (PDF or image) for recipe extraction.
|
||||
///
|
||||
/// [bytes] — raw file bytes from file_picker.
|
||||
/// [filename] — original filename (used for MIME detection on the server).
|
||||
/// [token] — JWT bearer token.
|
||||
Future<QuickImportResult> importFile({
|
||||
required Uint8List bytes,
|
||||
required String filename,
|
||||
String? token,
|
||||
}) async {
|
||||
final uri = Uri.parse('$_baseUrl/quick-import');
|
||||
final request = http.MultipartRequest('POST', uri);
|
||||
|
||||
if (token != null) {
|
||||
request.headers['Authorization'] = 'Bearer $token';
|
||||
}
|
||||
|
||||
request.files.add(
|
||||
http.MultipartFile.fromBytes('file', bytes, filename: filename),
|
||||
);
|
||||
|
||||
final streamed = await _client.send(request).timeout(
|
||||
const Duration(seconds: 120),
|
||||
onTimeout: () => throw ApiException(
|
||||
type: ApiErrorType.network,
|
||||
message: 'Importen tog för lång tid. Försök igen.',
|
||||
),
|
||||
);
|
||||
|
||||
final response = await http.Response.fromStream(streamed);
|
||||
return QuickImportResult.fromJson(_parseResponse(response));
|
||||
}
|
||||
|
||||
/// Import a recipe from a URL.
|
||||
Future<QuickImportResult> importUrl({
|
||||
required String url,
|
||||
String? token,
|
||||
}) async {
|
||||
final uri = Uri.parse('$_baseUrl/quick-import');
|
||||
final response = await _client
|
||||
.post(
|
||||
uri,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
if (token != null) 'Authorization': 'Bearer $token',
|
||||
},
|
||||
body: jsonEncode({'input': url}),
|
||||
)
|
||||
.timeout(
|
||||
const Duration(seconds: 120),
|
||||
onTimeout: () => throw ApiException(
|
||||
type: ApiErrorType.network,
|
||||
message: 'Importen tog för lång tid. Försök igen.',
|
||||
),
|
||||
);
|
||||
|
||||
return QuickImportResult.fromJson(_parseResponse(response));
|
||||
}
|
||||
|
||||
Map<String, dynamic> _parseResponse(http.Response response) {
|
||||
if (response.statusCode >= 200 && response.statusCode < 300) {
|
||||
return jsonDecode(response.body) as Map<String, dynamic>;
|
||||
}
|
||||
|
||||
Map<String, dynamic>? body;
|
||||
try {
|
||||
body = jsonDecode(response.body) as Map<String, dynamic>;
|
||||
} catch (_) {}
|
||||
|
||||
final message = body?['message'] as String? ??
|
||||
body?['error'] as String? ??
|
||||
'Import misslyckades (${response.statusCode}).';
|
||||
|
||||
if (response.statusCode == 401) {
|
||||
throw ApiException(type: ApiErrorType.unauthorized, statusCode: 401);
|
||||
}
|
||||
if (response.statusCode == 403) {
|
||||
throw ApiException(type: ApiErrorType.forbidden, statusCode: 403);
|
||||
}
|
||||
if (response.statusCode >= 500) {
|
||||
throw ApiException(
|
||||
type: ApiErrorType.server,
|
||||
statusCode: response.statusCode,
|
||||
message: message);
|
||||
}
|
||||
throw ApiException(
|
||||
type: ApiErrorType.unknown,
|
||||
statusCode: response.statusCode,
|
||||
message: message);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user