diff --git a/example/lib/main.dart b/example/lib/main.dart index fcf429fdc8..4b6418bc80 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,6 +1,30 @@ import 'dart:async'; import 'package:openfoodfacts/openfoodfacts.dart'; +import 'package:json_annotation/src/json_value.dart'; +import 'package:openfoodfacts/src/nutripatrol/get_ticket.dart'; + +void main() { + OpenFoodAPIConfiguration.userAgent = UserAgent( + name: 'openfoodfacts-dart', + version: '1.0.0', + url: '', + ); + getTickets(); +} + +/// Get the ticket by its ID +/// The result will be a MaybeError that can be parsed +void getTicket() async { + await NutripatrolApiClient.getTicket(ticketId: 2); +} + +/// Get all tickets +/// The result will be a MaybeError that can be parsed +void getTickets() async { + await NutripatrolApiClient.getTickets( + status: TicketStatus.open, type_: TicketType.image, page: 1); +} /// request a product from the OpenFoodFacts database Future getProduct() async { diff --git a/lib/openfoodfacts.dart b/lib/openfoodfacts.dart index 83b5e46a51..62a40a2e1f 100644 --- a/lib/openfoodfacts.dart +++ b/lib/openfoodfacts.dart @@ -153,3 +153,4 @@ export 'src/utils/unit_helper.dart'; export 'src/utils/uri_helper.dart'; export 'src/utils/uri_reader.dart'; export 'src/robot_off_api_client.dart'; +export 'src/nutripatrol_api_client.dart'; \ No newline at end of file diff --git a/lib/src/nutripatrol/create_flag.dart b/lib/src/nutripatrol/create_flag.dart new file mode 100644 index 0000000000..8296214e66 --- /dev/null +++ b/lib/src/nutripatrol/create_flag.dart @@ -0,0 +1,98 @@ +import 'package:json_annotation/json_annotation.dart'; + +import '../interface/json_object.dart'; + +part 'create_flag.g.dart'; + +@JsonSerializable() +class Flag extends JsonObject { + @JsonKey() + late String id; + + @JsonKey() + String? barcode; + + @JsonKey() + late Type type; + + @JsonKey() + String? url; + + @JsonKey(name: 'user_id') + late String userId; + + @JsonKey() + late Source source; + + @JsonKey() + double? confidence; + + @JsonKey(name: 'image_id') + String? imageId; + + @JsonKey() + late Flavor flavor; + + @JsonKey() + String? reason; + + @JsonKey() + String? comment; + + @JsonKey(name: 'created_at') + late DateTime CreatedAt; + + @JsonKey(name: 'ticket_id') + late int ticketId; + + @JsonKey(name: 'device_id') + late String deviceId; + + Flag(); + + factory Flag.fromJson(Map json) => _$FlagFromJson(json); + + @override Map toJson() => _$FlagToJson(this); +} + +/// Enum for ticket type +enum Type { + @JsonValue('image') + image, + + @JsonValue('product') + product, + + @JsonValue('search') + search +} + +/// Enum for ticket type +enum Source { + @JsonValue('mobile') + mobile, + + @JsonValue('web') + web, + + @JsonValue('robotoff') + robotoff +} + +/// Enum for ticket flavor +enum Flavor { + @JsonValue('off') + off, + + @JsonValue('obf') + obf, + + @JsonValue('opff') + opff, + + @JsonValue('opf') + opf, + + @JsonValue('off-pro') + offPro +} \ No newline at end of file diff --git a/lib/src/nutripatrol/create_flag.g.dart b/lib/src/nutripatrol/create_flag.g.dart new file mode 100644 index 0000000000..8b87f2f178 --- /dev/null +++ b/lib/src/nutripatrol/create_flag.g.dart @@ -0,0 +1,60 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'create_flag.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +Flag _$FlagFromJson(Map json) => Flag() + ..id = json['id'] as String + ..barcode = json['barcode'] as String? + ..type = $enumDecode(_$TypeEnumMap, json['type']) + ..url = json['url'] as String? + ..userId = json['user_id'] as String + ..source = $enumDecode(_$SourceEnumMap, json['source']) + ..confidence = (json['confidence'] as num?)?.toDouble() + ..imageId = json['image_id'] as String? + ..flavor = $enumDecode(_$FlavorEnumMap, json['flavor']) + ..reason = json['reason'] as String? + ..comment = json['comment'] as String? + ..CreatedAt = DateTime.parse(json['created_at'] as String) + ..ticketId = (json['ticket_id'] as num).toInt() + ..deviceId = json['device_id'] as String; + +Map _$FlagToJson(Flag instance) => { + 'id': instance.id, + 'barcode': instance.barcode, + 'type': _$TypeEnumMap[instance.type]!, + 'url': instance.url, + 'user_id': instance.userId, + 'source': _$SourceEnumMap[instance.source]!, + 'confidence': instance.confidence, + 'image_id': instance.imageId, + 'flavor': _$FlavorEnumMap[instance.flavor]!, + 'reason': instance.reason, + 'comment': instance.comment, + 'created_at': instance.CreatedAt.toIso8601String(), + 'ticket_id': instance.ticketId, + 'device_id': instance.deviceId, + }; + +const _$TypeEnumMap = { + Type.image: 'image', + Type.product: 'product', + Type.search: 'search', +}; + +const _$SourceEnumMap = { + Source.mobile: 'mobile', + Source.web: 'web', + Source.robotoff: 'robotoff', +}; + +const _$FlavorEnumMap = { + Flavor.off: 'off', + Flavor.obf: 'obf', + Flavor.opff: 'opff', + Flavor.opf: 'opf', + Flavor.offPro: 'off-pro', +}; diff --git a/lib/src/nutripatrol/get_ticket.dart b/lib/src/nutripatrol/get_ticket.dart new file mode 100644 index 0000000000..747e943a88 --- /dev/null +++ b/lib/src/nutripatrol/get_ticket.dart @@ -0,0 +1,85 @@ +import 'package:json_annotation/json_annotation.dart'; + +import '../interface/json_object.dart'; + +part 'get_ticket.g.dart'; + +@JsonSerializable() +class Ticket extends JsonObject { + /// Flag ID. Read-only. + @JsonKey() + late int id; + + /// Barcode of the product. Read-only. + @JsonKey() + String? barcode; + + /// Type of the ticket. + @JsonKey() + late TicketType type; + + /// Url of the ticket. Read-only. + @JsonKey() + late String url; + + /// Status of the ticket. + @JsonKey() + late TicketStatus status; + + /// Image id of the ticket. Read-only. + @JsonKey(name: 'image_id') + String? imageId; + + /// Flavor of the ticket. + @JsonKey() + late Flavor flavor; + + /// created date of the ticket. Read-only. + @JsonKey(name: 'created_at') + late String CreatedAt; + + Ticket(); + + factory Ticket.fromJson(Map json) => _$TicketFromJson(json); + + @override Map toJson() => _$TicketToJson(this); +} + +/// Enum for ticket type +enum TicketType { + @JsonValue('image') + image, + + @JsonValue('product') + product, + + @JsonValue('search') + search +} + +/// Enum for ticket status +enum TicketStatus { + @JsonValue('open') + open, + + @JsonValue('closed') + closed +} + +/// Enum for ticket flavor +enum Flavor { + @JsonValue('off') + off, + + @JsonValue('obf') + obf, + + @JsonValue('opff') + opff, + + @JsonValue('opf') + opf, + + @JsonValue('off-pro') + offPro +} \ No newline at end of file diff --git a/lib/src/nutripatrol/get_ticket.g.dart b/lib/src/nutripatrol/get_ticket.g.dart new file mode 100644 index 0000000000..079d7e827e --- /dev/null +++ b/lib/src/nutripatrol/get_ticket.g.dart @@ -0,0 +1,47 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'get_ticket.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +Ticket _$TicketFromJson(Map json) => Ticket() + ..id = (json['id'] as num).toInt() + ..barcode = json['barcode'] as String? + ..type = $enumDecode(_$TicketTypeEnumMap, json['type']) + ..url = json['url'] as String + ..status = $enumDecode(_$TicketStatusEnumMap, json['status']) + ..imageId = json['image_id'] as String? + ..flavor = $enumDecode(_$FlavorEnumMap, json['flavor']) + ..CreatedAt = json['created_at'] as String; + +Map _$TicketToJson(Ticket instance) => { + 'id': instance.id, + 'barcode': instance.barcode, + 'type': _$TicketTypeEnumMap[instance.type]!, + 'url': instance.url, + 'status': _$TicketStatusEnumMap[instance.status]!, + 'image_id': instance.imageId, + 'flavor': _$FlavorEnumMap[instance.flavor]!, + 'created_at': instance.CreatedAt, + }; + +const _$TicketTypeEnumMap = { + TicketType.image: 'image', + TicketType.product: 'product', + TicketType.search: 'search', +}; + +const _$TicketStatusEnumMap = { + TicketStatus.open: 'open', + TicketStatus.closed: 'closed', +}; + +const _$FlavorEnumMap = { + Flavor.off: 'off', + Flavor.obf: 'obf', + Flavor.opff: 'opff', + Flavor.opf: 'opf', + Flavor.offPro: 'off-pro', +}; diff --git a/lib/src/nutripatrol/get_tickets.dart b/lib/src/nutripatrol/get_tickets.dart new file mode 100644 index 0000000000..214fb41766 --- /dev/null +++ b/lib/src/nutripatrol/get_tickets.dart @@ -0,0 +1,23 @@ +import 'package:json_annotation/json_annotation.dart'; +import 'get_ticket.dart'; + +import '../interface/json_object.dart'; + +part 'get_tickets.g.dart'; + +@JsonSerializable() +class Tickets extends JsonObject { + /// List of Tickets + @JsonKey() + late List tickets; + + /// Max Page + @JsonKey(name: 'max_page') + late int maxPage; + + Tickets(); + + factory Tickets.fromJson(Map json) => _$TicketsFromJson(json); + + @override Map toJson() => _$TicketsToJson(this); +} \ No newline at end of file diff --git a/lib/src/nutripatrol/get_tickets.g.dart b/lib/src/nutripatrol/get_tickets.g.dart new file mode 100644 index 0000000000..b65e9c90b3 --- /dev/null +++ b/lib/src/nutripatrol/get_tickets.g.dart @@ -0,0 +1,18 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'get_tickets.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +Tickets _$TicketsFromJson(Map json) => Tickets() + ..tickets = (json['tickets'] as List) + .map((e) => Ticket.fromJson(e as Map)) + .toList() + ..maxPage = (json['max_page'] as num).toInt(); + +Map _$TicketsToJson(Tickets instance) => { + 'tickets': instance.tickets, + 'max_page': instance.maxPage, + }; diff --git a/lib/src/nutripatrol_api_client.dart b/lib/src/nutripatrol_api_client.dart new file mode 100644 index 0000000000..5e75c6bd1a --- /dev/null +++ b/lib/src/nutripatrol_api_client.dart @@ -0,0 +1,101 @@ +import 'package:http/http.dart'; +import 'package:openfoodfacts/src/nutripatrol/get_tickets.dart'; +import 'package:openfoodfacts/src/prices/maybe_error.dart'; +import 'utils/http_helper.dart'; +import 'utils/open_food_api_configuration.dart'; +import 'utils/uri_helper.dart'; + +import 'nutripatrol/get_ticket.dart'; + +/// Client calls of the Nutripatrol API. +/// +/// cf. [Nutripatrol](https://nutripatrol.openfoodfacts.org/api/docs) +class NutripatrolApiClient { + NutripatrolApiClient._(); + + /// Subdomain of the Nutripatrol API. + static const String _subdomain = 'nutripatrol'; + + static String _getHost(final UriProductHelper uriHelper) => + uriHelper.getHost(_subdomain); + + static Uri getUri({ + required final String path, + final Map? queryParameters, + final UriProductHelper uriHelper = uriHelperFoodProd, + final bool? addUserAgentParameters, + }) => + uriHelper.getUri( + path: path, + queryParameters: queryParameters, + forcedHost: _getHost(uriHelper), + addUserAgentParameters: addUserAgentParameters, + ); + + /// Get a ticket by its ID. + /// + /// [ticketId] is the ID of the ticket. + static Future> getTicket({ + required final int ticketId, + final UriProductHelper uriHelper = uriHelperFoodProd, + }) async { + assert(ticketId >= 0, "The id must be >= 0"); + final Uri uri = getUri( + path: '/api/v1/tickets/$ticketId', + uriHelper: uriHelper, + ); + final Response response = await HttpHelper().doGetRequest( + uri, + uriHelper: uriHelper, + ); + if (response.statusCode == 200) { + try { + final dynamic decodedResponse = HttpHelper().jsonDecodeUtf8(response); + return MaybeError.value(Ticket.fromJson(decodedResponse)); + } catch (_) { + // + } + } + return MaybeError.responseError(response); + } + + /// Get all tickets. + static Future> getTickets({ + final TicketStatus status = TicketStatus.open, + final TicketType type_ = TicketType.image, + final int? page, + final int? pageSize, + final UriProductHelper uriHelper = uriHelperFoodProd, + }) async { + final Map queryParameters = {}; + queryParameters['status'] = status.toString().split('.').last; + queryParameters['type'] = type_.toString().split('.').last; + if (page != null) queryParameters['page'] = page.toString(); + if (pageSize != null) queryParameters['page_size'] = pageSize.toString(); + + final Uri uri = getUri( + path: '/api/v1/tickets', + queryParameters: queryParameters, + uriHelper: uriHelper, + ); + final Response response = await HttpHelper().doGetRequest( + uri, + uriHelper: uriHelper, + ); + if (response.statusCode == 200) { + try { + final dynamic decodedResponse = HttpHelper().jsonDecodeUtf8(response); + final List tickets = decodedResponse['tickets']; + return MaybeError.value( + Tickets.fromJson({ + 'tickets': tickets, + 'max_page': decodedResponse['max_page'], + }), + ); + } catch (_) { + // + } + } + return MaybeError.responseError(response); + } +}