aboutsummaryrefslogtreecommitdiff
path: root/src/backend
diff options
context:
space:
mode:
authorChristian Grothoff <christian@grothoff.org>2024-05-26 12:30:26 +0200
committerChristian Grothoff <christian@grothoff.org>2024-05-26 12:30:26 +0200
commitffdfeb863a03cc2cf9a92731f7b44b0c058d6a2d (patch)
treeb63a5da281434b4cfe29276caccc5e00b6a91afc /src/backend
parent79c0c75009f9d292949382db02e3a2331d005bd8 (diff)
expand POST /products to allow specifying categories the product is in
Diffstat (limited to 'src/backend')
-rw-r--r--src/backend/taler-merchant-httpd_private-post-products.c285
1 files changed, 125 insertions, 160 deletions
diff --git a/src/backend/taler-merchant-httpd_private-post-products.c b/src/backend/taler-merchant-httpd_private-post-products.c
index 3cad91a9..3edc0c16 100644
--- a/src/backend/taler-merchant-httpd_private-post-products.c
+++ b/src/backend/taler-merchant-httpd_private-post-products.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- (C) 2020 Taler Systems SA
+ (C) 2020-2024 Taler Systems SA
TALER is free software; you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
@@ -28,50 +28,6 @@
#include <taler/taler_json_lib.h>
-/**
- * How often do we retry the simple INSERT database transaction?
- */
-#define MAX_RETRIES 3
-
-
-/**
- * Check if the two products are identical.
- *
- * @param p1 product to compare
- * @param p2 other product to compare
- * @return true if they are 'equal', false if not or of payto_uris is not an array
- */
-static bool
-products_equal (const struct TALER_MERCHANTDB_ProductDetails *p1,
- const struct TALER_MERCHANTDB_ProductDetails *p2)
-{
- return ( (0 == strcmp (p1->description,
- p2->description)) &&
- (1 == json_equal (p1->description_i18n,
- p2->description_i18n)) &&
- (0 == strcmp (p1->unit,
- p2->unit)) &&
- (GNUNET_OK ==
- TALER_amount_cmp_currency (&p1->price,
- &p2->price)) &&
- (0 == TALER_amount_cmp (&p1->price,
- &p2->price)) &&
- (1 == json_equal (p1->taxes,
- p2->taxes)) &&
- (p1->total_stock == p2->total_stock) &&
- (p1->total_sold == p2->total_sold) &&
- (p1->total_lost == p2->total_lost) &&
- (p1->minimum_age == p2->minimum_age) &&
- (0 == strcmp (p1->image,
- p2->image)) &&
- (1 == json_equal (p1->address,
- p2->address)) &&
- (GNUNET_TIME_timestamp_cmp (p1->next_restock,
- ==,
- p2->next_restock) ) );
-}
-
-
MHD_RESULT
TMH_private_post_products (const struct TMH_RequestHandler *rh,
struct MHD_Connection *connection,
@@ -79,9 +35,9 @@ TMH_private_post_products (const struct TMH_RequestHandler *rh,
{
struct TMH_MerchantInstance *mi = hc->instance;
struct TALER_MERCHANTDB_ProductDetails pd = { 0 };
+ const json_t *categories = NULL;
const char *product_id;
int64_t total_stock;
- enum GNUNET_DB_QueryStatus qs;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_string ("product_id",
&product_id),
@@ -103,6 +59,10 @@ TMH_private_post_products (const struct TMH_RequestHandler *rh,
GNUNET_JSON_spec_json ("taxes",
&pd.taxes),
NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_array_const ("categories",
+ &categories),
+ NULL),
GNUNET_JSON_spec_int64 ("total_stock",
&total_stock),
GNUNET_JSON_spec_mark_optional (
@@ -119,6 +79,13 @@ TMH_private_post_products (const struct TMH_RequestHandler *rh,
NULL),
GNUNET_JSON_spec_end ()
};
+ size_t num_cats = 0;
+ uint64_t *cats = NULL;
+ bool conflict;
+ bool no_instance;
+ ssize_t no_cat;
+ enum GNUNET_DB_QueryStatus qs;
+ MHD_RESULT ret;
GNUNET_assert (NULL != mi);
{
@@ -138,13 +105,33 @@ TMH_private_post_products (const struct TMH_RequestHandler *rh,
if (total_stock < -1)
{
GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "total_stock");
+ ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "total_stock");
+ goto cleanup;
}
+ num_cats = json_array_size (categories);
+ cats = GNUNET_new_array (num_cats,
+ uint64_t);
+ {
+ size_t idx;
+ json_t *val;
+ json_array_foreach (categories, idx, val)
+ {
+ if (! json_is_integer (val))
+ {
+ GNUNET_break_op (0);
+ ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "categories");
+ goto cleanup;
+ }
+ cats[idx] = json_integer_value (val);
+ }
+ }
if (-1 == total_stock)
pd.total_stock = INT64_MAX;
@@ -162,31 +149,31 @@ TMH_private_post_products (const struct TMH_RequestHandler *rh,
if (! TMH_taxes_array_valid (pd.taxes))
{
GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "taxes");
+ ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "taxes");
+ goto cleanup;
}
if (! TMH_location_object_valid (pd.address))
{
GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "address");
+ ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "address");
+ goto cleanup;
}
if (! TALER_JSON_check_i18n (pd.description_i18n))
{
GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "description_i18n");
+ ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "description_i18n");
+ goto cleanup;
}
if (NULL == pd.image)
@@ -194,110 +181,88 @@ TMH_private_post_products (const struct TMH_RequestHandler *rh,
if (! TMH_image_data_url_valid (pd.image))
{
GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "image");
+ ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "image");
+ goto cleanup;
}
- /* finally, interact with DB until no serialization error */
- for (unsigned int i = 0; i<MAX_RETRIES; i++)
+ qs = TMH_db->insert_product (TMH_db->cls,
+ mi->settings.id,
+ product_id,
+ &pd,
+ num_cats,
+ cats,
+ &no_instance,
+ &conflict,
+ &no_cat);
+ switch (qs)
{
- /* Test if an product of this id is known */
- struct TALER_MERCHANTDB_ProductDetails epd;
-
- if (GNUNET_OK !=
- TMH_db->start (TMH_db->cls,
- "/post products"))
- {
- GNUNET_break (0);
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_START_FAILED,
- NULL);
- }
- qs = TMH_db->lookup_product (TMH_db->cls,
- mi->settings.id,
- product_id,
- &epd);
- switch (qs)
- {
- case GNUNET_DB_STATUS_HARD_ERROR:
- /* Clean up and fail hard */
- GNUNET_break (0);
- TMH_db->rollback (TMH_db->cls);
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- NULL);
- case GNUNET_DB_STATUS_SOFT_ERROR:
- /* restart transaction */
- goto retry;
- case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- /* Good, we can proceed! */
- break;
- case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
- /* idempotency check: is epd == pd? */
- {
- bool eq;
-
- eq = products_equal (&pd,
- &epd);
- TALER_MERCHANTDB_product_details_free (&epd);
- TMH_db->rollback (TMH_db->cls);
- GNUNET_JSON_parse_free (spec);
- return eq
- ? TALER_MHD_reply_static (connection,
- MHD_HTTP_NO_CONTENT,
- NULL,
- NULL,
- 0)
- : TALER_MHD_reply_with_error (connection,
- MHD_HTTP_CONFLICT,
- TALER_EC_MERCHANT_PRIVATE_POST_PRODUCTS_CONFLICT_PRODUCT_EXISTS,
- product_id);
- }
- } /* end switch (qs) */
-
- qs = TMH_db->insert_product (TMH_db->cls,
- mi->settings.id,
- product_id,
- &pd);
- if (GNUNET_DB_STATUS_HARD_ERROR == qs)
- {
- TMH_db->rollback (TMH_db->cls);
- break;
- }
- if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
- {
- qs = TMH_db->commit (TMH_db->cls);
- if (GNUNET_DB_STATUS_SOFT_ERROR != qs)
- break;
- }
-retry:
- GNUNET_assert (GNUNET_DB_STATUS_SOFT_ERROR == qs);
- TMH_db->rollback (TMH_db->cls);
- } /* for RETRIES loop */
- GNUNET_JSON_parse_free (spec);
- if (qs < 0)
- {
- GNUNET_break (0);
- return TALER_MHD_reply_with_error (
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ ret = TALER_MHD_reply_with_error (
connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
(GNUNET_DB_STATUS_SOFT_ERROR == qs)
? TALER_EC_GENERIC_DB_SOFT_FAILURE
: TALER_EC_GENERIC_DB_COMMIT_FAILED,
NULL);
+ goto cleanup;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_INVARIANT_FAILURE,
+ NULL);
+ goto cleanup;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ break;
}
- return TALER_MHD_reply_static (connection,
- MHD_HTTP_NO_CONTENT,
- NULL,
- NULL,
- 0);
+ if (no_instance)
+ {
+ ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_MERCHANT_GENERIC_INSTANCE_UNKNOWN,
+ mi->settings.id);
+ goto cleanup;
+ }
+ if (conflict)
+ {
+ ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_MERCHANT_PRIVATE_POST_PRODUCTS_CONFLICT_PRODUCT_EXISTS,
+ product_id);
+ goto cleanup;
+ }
+ if (-1 != no_cat)
+ {
+ char nocats[24];
+
+ GNUNET_break_op (0);
+ TMH_db->rollback (TMH_db->cls);
+ GNUNET_snprintf (nocats,
+ sizeof (nocats),
+ "%llu",
+ (unsigned long long) no_cat);
+ ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_MERCHANT_GENERIC_CATEGORY_UNKNOWN,
+ nocats);
+ goto cleanup;
+ }
+ ret = TALER_MHD_reply_static (connection,
+ MHD_HTTP_NO_CONTENT,
+ NULL,
+ NULL,
+ 0);
+cleanup:
+ GNUNET_JSON_parse_free (spec);
+ GNUNET_free (cats);
+ return ret;
}