Skip to content

Commit

Permalink
feat: 942 - new methods updatePrice, updateProof; new proof parameters
Browse files Browse the repository at this point in the history
New files:
* `update_price_parameters.dart`: Parameters for the "update price" API query.
* `update_proof_parameters.dart`: Parameters for the "update proof" API query.

Impacted files:
* `api_prices_test.dart`: added tests for `updatePrice` and `updateProof`; refactored the access to `uriHelper`
* `get_prices_parameters.dart`: added 2 parameters for `getPrices` queries
* `get_proofs_parameters.dart`: added 9 parameters for `getProofs` queries
* `http_helper.dart`: minor refactoring for PATCH queries in Prices
* `open_prices_api_client.dart`: new methods `updatePrice` and `updateProof`; new parameters for `uploadProof`; refactored `createPrice`
* `openfoodfacts.dart`: added the new 2 files
* `price.dart`: added field `updated`
* `price.g.dart`: generated
* `proof.dart`: added fields `locationOSMId`, `locationOSMType`, `locationId`, `date`, `currency`, `updated` and `location`
* `proof.g.dart`: generated
  • Loading branch information
monsieurtanuki committed Jun 26, 2024
1 parent b7366f3 commit 181c179
Show file tree
Hide file tree
Showing 12 changed files with 661 additions and 51 deletions.
4 changes: 3 additions & 1 deletion lib/openfoodfacts.dart
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export 'src/model/product_stats.dart';
export 'src/model/product_tag.dart';
export 'src/model/recommended_daily_intake.dart';
export 'src/model/robotoff_question.dart';
export 'src/model/robotoff_question_order.dart';
export 'src/model/search_result.dart';
export 'src/model/send_image.dart';
export 'src/model/sign_up_status.dart';
Expand Down Expand Up @@ -115,6 +116,8 @@ export 'src/prices/price_user.dart';
export 'src/prices/proof.dart';
export 'src/prices/proof_type.dart';
export 'src/prices/session.dart';
export 'src/prices/update_price_parameters.dart';
export 'src/prices/update_proof_parameters.dart';
export 'src/search/autocomplete_search_result.dart';
export 'src/search/autocomplete_single_result.dart';
export 'src/search/fuzziness.dart';
Expand All @@ -138,7 +141,6 @@ export 'src/utils/product_fields.dart';
export 'src/utils/product_helper.dart';
export 'src/utils/product_query_configurations.dart';
export 'src/utils/product_search_query_configuration.dart';
export 'src/model/robotoff_question_order.dart';
export 'src/utils/server_type.dart';
export 'src/utils/suggestion_manager.dart';
export 'src/utils/tag_type.dart';
Expand Down
140 changes: 103 additions & 37 deletions lib/src/open_prices_api_client.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import 'dart:convert';

import 'package:http/http.dart';
import 'package:http_parser/http_parser.dart';
import 'package:http/http.dart' as http;
import 'package:path/path.dart';

import 'prices/currency.dart';
import 'prices/maybe_error.dart';
import 'prices/price.dart';
import 'prices/proof.dart';
Expand All @@ -20,6 +23,8 @@ import 'prices/location_osm_type.dart';
import 'prices/price_product.dart';
import 'prices/proof_type.dart';
import 'prices/session.dart';
import 'prices/update_price_parameters.dart';
import 'prices/update_proof_parameters.dart';
import 'utils/http_helper.dart';
import 'utils/open_food_api_configuration.dart';
import 'utils/uri_helper.dart';
Expand Down Expand Up @@ -325,44 +330,27 @@ class OpenPricesAPIClient {
path: '/api/v1/prices',
uriHelper: uriHelper,
);
final StringBuffer body = StringBuffer();
body.write('{');
if (price.productCode != null) {
body.write('"product_code": "${price.productCode}",');
}
if (price.productName != null) {
body.write('"product_name": "${price.productName}",');
}
if (price.categoryTag != null) {
body.write('"category_tag": "${price.categoryTag}",');
}
if (price.labelsTags != null) {
body.write('"labels_tags": "${price.labelsTags}",');
}
if (price.originsTags != null) {
body.write('"origins_tags": "${price.originsTags}",');
}
if (price.proofId != null) {
body.write('"proof_id": ${price.proofId},');
}
if (price.pricePer != null) {
body.write('"price_per": "${price.pricePer!.offTag}",');
}
if (price.priceWithoutDiscount != null) {
body.write('"price_without_discount": ${price.priceWithoutDiscount},');
}
if (price.priceIsDiscounted != null) {
body.write('"price_is_discounted": ${price.priceIsDiscounted},');
}
body.write('"price": ${price.price},');
body.write('"currency": "${price.currency.name}",');
body.write('"location_osm_id": ${price.locationOSMId},');
body.write('"location_osm_type": "${price.locationOSMType.offTag}",');
body.write('"date": "${GetParametersHelper.formatDate(price.date)}"');
body.write('}');
final Map<String, dynamic> body = <String, dynamic>{
if (price.productCode != null) 'product_code': price.productCode,
if (price.productName != null) 'product_name': price.productName,
if (price.categoryTag != null) 'category_tag': price.categoryTag,
if (price.labelsTags != null) 'labels_tags': price.labelsTags,
if (price.originsTags != null) 'origins_tags': price.originsTags,
if (price.proofId != null) 'proof_id': price.proofId,
if (price.pricePer != null) 'price_per': price.pricePer!.offTag,
if (price.priceWithoutDiscount != null)
'price_without_discount': price.priceWithoutDiscount,
if (price.priceIsDiscounted != null)
'price_is_discounted': price.priceIsDiscounted,
'price': price.price,
'currency': price.currency.name,
'location_osm_id': price.locationOSMId,
'location_osm_type': price.locationOSMType.offTag,
'date': GetParametersHelper.formatDate(price.date),
};
final Response response = await HttpHelper().doPostJsonRequest(
uri,
body.toString(),
jsonEncode(body),
uriHelper: uriHelper,
bearerToken: bearerToken,
);
Expand All @@ -377,6 +365,39 @@ class OpenPricesAPIClient {
return MaybeError<Price>.responseError(response);
}

/// Updates a price.
///
/// This endpoint requires authentication.
/// A user can update only owned prices.
static Future<MaybeError<Price>> updatePrice(
final int priceId, {
required final UpdatePriceParameters parameters,
final UriProductHelper uriHelper = uriHelperFoodProd,
required final String bearerToken,
}) async {
final Uri uri = getUri(
path: '/api/v1/prices/$priceId',
uriHelper: uriHelper,
);
final Response response = await HttpHelper().doPatchRequest(
uri,
parameters.toJson(),
null,
uriHelper: uriHelper,
bearerToken: bearerToken,
addUserAgentParameters: false,
);
if (response.statusCode == 200) {
try {
final dynamic decodedResponse = HttpHelper().jsonDecodeUtf8(response);
return MaybeError<Price>.value(Price.fromJson(decodedResponse));
} catch (e) {
//
}
}
return MaybeError<Price>.responseError(response);
}

/// Deletes a price.
/// A user can delete only owned prices.
/// Returns true if successful.
Expand Down Expand Up @@ -433,6 +454,10 @@ class OpenPricesAPIClient {
required final ProofType proofType,
required final Uri imageUri,
required final MediaType mediaType,
final int? locationOSMId,
final LocationOSMType? locationOSMType,
final DateTime? date,
final Currency? currency,
required final String bearerToken,
final UriProductHelper uriHelper = uriHelperFoodProd,
}) async {
Expand All @@ -449,6 +474,11 @@ class OpenPricesAPIClient {
request.fields.addAll(
<String, String>{
'type': proofType.offTag,
if (locationOSMId != null) 'location_osm_id': locationOSMId.toString(),
if (locationOSMType != null)
'location_osm_type': locationOSMType.offTag,
if (date != null) 'date': GetParametersHelper.formatDate(date),
if (currency != null) 'currency': currency.name,
},
);
final List<int> fileBytes = await UriReader.instance!.readAsBytes(imageUri);
Expand Down Expand Up @@ -506,8 +536,44 @@ class OpenPricesAPIClient {
return MaybeError<Proof>.responseError(response);
}

/// Updates a proof.
///
/// This endpoint requires authentication.
/// A user can update only owned proofs.
static Future<MaybeError<Proof>> updateProof(
final int proofId, {
required final UpdateProofParameters parameters,
final UriProductHelper uriHelper = uriHelperFoodProd,
required final String bearerToken,
}) async {
final Uri uri = getUri(
path: '/api/v1/proofs/$proofId',
uriHelper: uriHelper,
);
final Response response = await HttpHelper().doPatchRequest(
uri,
parameters.toJson(),
null,
uriHelper: uriHelper,
bearerToken: bearerToken,
addUserAgentParameters: false,
);
if (response.statusCode == 200) {
try {
final dynamic decodedResponse = HttpHelper().jsonDecodeUtf8(response);
return MaybeError<Proof>.value(Proof.fromJson(decodedResponse));
} catch (e) {
//
}
}
return MaybeError<Proof>.responseError(response);
}

/// Deletes a proof.
/// A user can delete only owned proofs. Can delete only proofs that are not associated with prices. A moderator can delete not owned proofs.
///
/// A user can delete only owned proofs.
/// Can delete only proofs that are not associated with prices.
/// A moderator can delete not owned proofs.
/// Returns true if successful.
static Future<MaybeError<bool>> deleteProof({
required final int proofId,
Expand Down
4 changes: 4 additions & 0 deletions lib/src/prices/get_prices_parameters.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,10 @@ class GetPricesParameters extends GetParametersHelper<GetPricesOrderField> {
DateTime? dateGte;
DateTime? dateLt;
DateTime? dateLte;
int? proofId;
String? owner;
DateTime? createdGte;
DateTime? createdLte;

@override
Map<String, String> getQueryParameters() {
Expand All @@ -43,8 +45,10 @@ class GetPricesParameters extends GetParametersHelper<GetPricesOrderField> {
addNonNullDate(dateGte, 'date__gte', dayOnly: true);
addNonNullDate(dateLt, 'date__lt', dayOnly: true);
addNonNullDate(dateLte, 'date__lte', dayOnly: true);
addNonNullInt(proofId, 'proof_id');
addNonNullString(owner, 'owner');
addNonNullDate(createdGte, 'created__gte', dayOnly: false);
addNonNullDate(createdLte, 'created__lte', dayOnly: false);
return result;
}
}
20 changes: 20 additions & 0 deletions lib/src/prices/get_proofs_parameters.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import 'get_price_count_parameters_helper.dart';
import 'get_proofs_order.dart';
import 'proof_type.dart';
import 'currency.dart';
import 'location_osm_type.dart';

/// Parameters for the "get proofs" API query.
///
Expand All @@ -9,13 +11,31 @@ class GetProofsParameters
extends GetPriceCountParametersHelper<GetProofsOrderField> {
String? owner;
ProofType? type;
int? locationOSMId;
LocationOSMType? locationOSMType;
int? locationId;
Currency? currency;
DateTime? date;
DateTime? dateGt;
DateTime? dateGte;
DateTime? dateLt;
DateTime? dateLte;

/// Returns the parameters as a query parameter map.
@override
Map<String, String> getQueryParameters() {
super.getQueryParameters();
addNonNullString(owner, 'owner');
addNonNullString(type?.offTag, 'type');
addNonNullInt(locationOSMId, 'location_osm_id');
addNonNullString(locationOSMType?.offTag, 'location_osm_type');
addNonNullInt(locationId, 'location_id');
addNonNullString(currency?.name, 'currency');
addNonNullDate(date, 'date', dayOnly: true);
addNonNullDate(dateGt, 'date__gt', dayOnly: true);
addNonNullDate(dateGte, 'date__gte', dayOnly: true);
addNonNullDate(dateLt, 'date__lt', dayOnly: true);
addNonNullDate(dateLte, 'date__lte', dayOnly: true);
return result;
}
}
4 changes: 4 additions & 0 deletions lib/src/prices/price.dart
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,10 @@ class Price extends JsonObject {
@JsonKey(fromJson: JsonHelper.stringTimestampToDate)
late DateTime created;

/// Latest update timestamp. Read-only.
@JsonKey(fromJson: JsonHelper.nullableStringTimestampToDate)
DateTime? updated;

Price();

factory Price.fromJson(Map<String, dynamic> json) => _$PriceFromJson(json);
Expand Down
4 changes: 3 additions & 1 deletion lib/src/prices/price.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

45 changes: 43 additions & 2 deletions lib/src/prices/proof.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import 'package:json_annotation/json_annotation.dart';
import 'proof_type.dart';

import 'currency.dart';
import 'location.dart';
import 'location_osm_type.dart';
import 'proof_type.dart';
import '../interface/json_object.dart';
import '../open_prices_api_client.dart';
import '../utils/json_helper.dart';
Expand All @@ -25,14 +28,44 @@ class Proof extends JsonObject {
@JsonKey()
late String mimetype;

/// Proof type. Read-only.
/// Proof type.
@JsonKey()
ProofType? type;

/// Number of prices for this proof. Read-only.
@JsonKey(name: 'price_count')
late int priceCount;

/// ID of the location in OpenStreetMap.
///
/// The store where the product was bought.
@JsonKey(name: 'location_osm_id')
int? locationOSMId;

/// Type of the OpenStreetMap location object.
///
/// Stores can be represented as nodes, ways or relations in OpenStreetMap.
/// It is necessary to be able to fetch the correct information about the
/// store using the ID.
@JsonKey(name: 'location_osm_type')
LocationOSMType? locationOSMType;

/// Location ID.
@JsonKey(name: 'location_id')
int? locationId;

/// Date when the product was bought.
@JsonKey(fromJson: JsonHelper.nullableStringTimestampToDate)
DateTime? date;

/// Currency of the price.
///
/// The currency must be a valid currency code.
/// See https://en.wikipedia.org/wiki/ISO_4217 for a list of valid currency
/// codes.
@JsonKey()
Currency? currency;

/// Owner. Read-only.
@JsonKey()
late String owner;
Expand All @@ -41,6 +74,14 @@ class Proof extends JsonObject {
@JsonKey(fromJson: JsonHelper.stringTimestampToDate)
late DateTime created;

/// Latest update timestamp. Read-only.
@JsonKey(fromJson: JsonHelper.nullableStringTimestampToDate)
DateTime? updated;

/// Location. Read-only.
@JsonKey()
Location? location;

Proof();

factory Proof.fromJson(Map<String, dynamic> json) => _$ProofFromJson(json);
Expand Down
Loading

0 comments on commit 181c179

Please sign in to comment.