diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/backend/Makefile.am | 6 | ||||
-rw-r--r-- | src/backend/taler-merchant-httpd.c | 8 | ||||
-rw-r--r-- | src/backend/taler-merchant-httpd_private-patch-instances-ID.c | 369 | ||||
-rw-r--r-- | src/backend/taler-merchant-httpd_private-patch-instances-ID.h | 43 | ||||
-rw-r--r-- | src/backend/taler-merchant-httpd_private-patch-products-ID.c | 254 | ||||
-rw-r--r-- | src/backend/taler-merchant-httpd_private-patch-products-ID.h | 43 | ||||
-rw-r--r-- | src/backend/taler-merchant-httpd_private-post-products.c | 262 | ||||
-rw-r--r-- | src/backend/taler-merchant-httpd_private-post-products.h | 43 | ||||
-rw-r--r-- | src/include/taler_merchantdb_plugin.h | 35 |
9 files changed, 1060 insertions, 3 deletions
diff --git a/src/backend/Makefile.am b/src/backend/Makefile.am index 31245456..646841db 100644 --- a/src/backend/Makefile.am +++ b/src/backend/Makefile.am @@ -35,8 +35,12 @@ taler_merchant_httpd_SOURCES = \ taler-merchant-httpd_private-get-products-ID.h \ taler-merchant-httpd_private-patch-instances-ID.c \ taler-merchant-httpd_private-patch-instances-ID.h \ + taler-merchant-httpd_private-patch-products-ID.c \ + taler-merchant-httpd_private-patch-products-ID.h \ taler-merchant-httpd_private-post-instances.c \ - taler-merchant-httpd_private-post-instances.h + taler-merchant-httpd_private-post-instances.h \ + taler-merchant-httpd_private-post-products.c \ + taler-merchant-httpd_private-post-products.h DEAD = \ taler-merchant-httpd_check-payment.c taler-merchant-httpd_check-payment.h \ diff --git a/src/backend/taler-merchant-httpd.c b/src/backend/taler-merchant-httpd.c index cc722487..58b68451 100644 --- a/src/backend/taler-merchant-httpd.c +++ b/src/backend/taler-merchant-httpd.c @@ -35,6 +35,7 @@ #include "taler-merchant-httpd_private-get-products.h" #include "taler-merchant-httpd_private-get-products-ID.h" #include "taler-merchant-httpd_private-patch-instances-ID.h" +#include "taler-merchant-httpd_private-patch-products-ID.h" #include "taler-merchant-httpd_private-post-instances.h" /** @@ -782,6 +783,13 @@ url_handler (void *cls, .have_id_segment = true, .handler = &TMH_private_delete_instances_ID }, + /* PATCH /products/$ID/: */ + { + .url_prefix = "/", + .method = MHD_HTTP_METHOD_PATCH, + .have_id_segment = true, + .handler = &TMH_private_patch_products_ID + }, { NULL } diff --git a/src/backend/taler-merchant-httpd_private-patch-instances-ID.c b/src/backend/taler-merchant-httpd_private-patch-instances-ID.c new file mode 100644 index 00000000..9e151493 --- /dev/null +++ b/src/backend/taler-merchant-httpd_private-patch-instances-ID.c @@ -0,0 +1,369 @@ +/* + This file is part of TALER + (C) 2020 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 + published by the Free Software Foundation; either version 3, + or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, + see <http://www.gnu.org/licenses/> +*/ + +/** + * @file backend/taler-merchant-httpd_private-patch-instances.c + * @brief implementing PATCH /instances/$ID request handling + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler-merchant-httpd_private-patch-instances-ID.h" +#include <taler/taler_json_lib.h> + + +/** + * How often do we retry the simple INSERT database transaction? + */ +#define MAX_RETRIES 3 + + +/** + * Free memory used by @a wm + * + * @param wm wire method to free + */ +static void +free_wm (struct TMH_WireMethod *wm) +{ + json_decref (wm->j_wire); + GNUNET_free (wm->wire_method); + GNUNET_free (wm); +} + + +/** + * PATCH configuration of an existing instance, given its configuration. + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[in,out] hc context with further information about the request + * @return MHD result code + */ +MHD_RESULT +TMH_private_patch_instances_ID (const struct TMH_RequestHandler *rh, + struct MHD_Connection *connection, + struct TMH_HandlerContext *hc) +{ + struct TMH_MerchantInstance *mi = hc->instance; + struct TALER_MERCHANTDB_InstanceSettings is; + json_t *payto_uris; + const char *name; + struct TMH_WireMethod *wm_head = NULL; + struct TMH_WireMethod *wm_tail = NULL; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_json ("payto_uris", + &payto_uris), + GNUNET_JSON_spec_string ("name", + &name), + GNUNET_JSON_spec_json ("address", + &is.address), + GNUNET_JSON_spec_json ("jurisdiction", + &is.jurisdiction), + TALER_JSON_spec_amount ("default_max_deposit_fee", + &is.default_max_deposit_fee), + TALER_JSON_spec_amount ("default_max_wire_fee", + &is.default_max_wire_fee), + GNUNET_JSON_spec_uint32 ("default_wire_fee_amortization", + &is.default_wire_fee_amortization), + GNUNET_JSON_spec_relative_time ("default_wire_transfer_delay", + &is.default_wire_transfer_delay), + GNUNET_JSON_spec_relative_time ("default_pay_delay", + &is.default_pay_delay), + GNUNET_JSON_spec_end () + }; + enum GNUNET_DB_QueryStatus qs; + + GNUNET_assert (NULL != mi); + { + enum GNUNET_GenericReturnValue res; + + res = TALER_MHD_parse_json_data (connection, + hc->request_body, + spec); + /* json is malformed */ + if (GNUNET_NO == res) + { + GNUNET_break_op (0); + return MHD_YES; + } + /* other internal errors might have occurred */ + if (GNUNET_SYSERR == res) + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_INTERNAL_INVARIANT_FAILURE, + "Impossible to parse the order"); + } + if (! json_is_array (payto_uris)) + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_PATCH_INSTANCES_BAD_PAYTO_URIS, + "Invalid bank account information"); + for (unsigned int i = 0; i<MAX_RETRIES; i++) + { + /* Cleanup after earlier loops */ + { + struct TMH_WireMethod *wm; + + while (NULL != (wm = wm_head)) + { + GNUNET_CONTAINER_DLL_remove (wm_head, + wm_tail, + wm); + free_wm (wm); + } + } + if (GNUNET_OK != + TMH_db->start (TMH_db->cls, + "PATCH /instances")) + { + GNUNET_JSON_parse_free (spec); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_PATCH_INSTANCES_DB_START_ERROR, + "failed to start database transaction"); + } + /* Check for equality of settings */ + if (! ( (0 == strcmp (mi->settings.name, + name)) && + (1 == json_equal (mi->settings.address, + is.address)) && + (1 == json_equal (mi->settings.jurisdiction, + is.jurisdiction)) && + (0 == TALER_amount_cmp_currency ( + &mi->settings.default_max_deposit_fee, + &is.default_max_deposit_fee)) && + (0 == TALER_amount_cmp (&mi->settings.default_max_deposit_fee, + &is.default_max_deposit_fee)) && + (0 == TALER_amount_cmp_currency (&mi->settings.default_max_wire_fee, + &is.default_max_wire_fee)) && + (0 == TALER_amount_cmp (&mi->settings.default_max_wire_fee, + &is.default_max_wire_fee)) && + (mi->settings.default_wire_fee_amortization == + is.default_wire_fee_amortization) && + (mi->settings.default_wire_transfer_delay.rel_value_us == + is.default_wire_transfer_delay.rel_value_us) && + (mi->settings.default_pay_delay.rel_value_us == + is.default_pay_delay.rel_value_us) ) ) + { + qs = TMH_db->update_instance (TMH_db->cls, + &mi->settings); + if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs) + { + TMH_db->rollback (TMH_db->cls); + if (GNUNET_DB_STATUS_SOFT_ERROR == qs) + goto retry; + else + goto giveup; + } + } + + /* Check for changes in accounts */ + { + unsigned int len = json_array_size (payto_uris); + bool matches[GNUNET_NZL (len)]; + bool matched; + + for (struct TMH_WireMethod *wm = mi->wm_head; + NULL != wm; + wm = wm->next) + { + const char *uri = json_string_value (json_object_get (wm->j_wire, + "payto_uri")); + GNUNET_assert (NULL != uri); + matched = false; + for (unsigned int i = 0; i<len; i++) + { + const char *str = json_string_value (json_array_get (payto_uris, + i)); + if (NULL == str) + { + GNUNET_break_op (0); + TMH_db->rollback (TMH_db->cls); + GNUNET_JSON_parse_free (spec); + GNUNET_assert (NULL == wm_head); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_POST_INSTANCES_BAD_PAYTO_URIS, + "Invalid bank account information"); + } + if ( (strcasecmp (uri, + str)) ) + { + if (matches[i]) + { + GNUNET_break (0); + TMH_db->rollback (TMH_db->cls); + GNUNET_JSON_parse_free (spec); + GNUNET_assert (NULL == wm_head); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_POST_INSTANCES_BAD_PAYTO_URIS, + "Invalid bank account information"); + } + matches[i] = true; + matched = true; + break; + } + } + /* delete unmatched (= removed) accounts */ + if (! matched) + { + /* Account was REMOVED */ + wm->deleting = true; + qs = TMH_db->inactivate_account (TMH_db->cls, + &wm->h_wire); + if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs) + { + TMH_db->rollback (TMH_db->cls); + if (GNUNET_DB_STATUS_SOFT_ERROR == qs) + goto retry; + else + goto giveup; + } + } + } + /* Find _new_ accounts */ + for (unsigned int i = 0; i<len; i++) + { + struct TALER_MERCHANTDB_AccountDetails ad; + struct TMH_WireMethod *wm; + + if (matches[i]) + continue; /* account existed */ + ad.payto_uri = json_string_value (json_array_get (payto_uris, + i)); + GNUNET_assert (NULL != ad.payto_uri); + GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE, + &ad.salt, + sizeof (ad.salt)); + wm = GNUNET_new (struct TMH_WireMethod); + wm->j_wire = json_pack ("{s:O, s:s}", + "payto_uri", ad.payto_uri, + "salt", GNUNET_JSON_from_data_auto (&ad.salt)); + GNUNET_assert (NULL != wm->j_wire); + wm->wire_method + = TALER_payto_get_method (ad.payto_uri); + GNUNET_assert (NULL != wm->wire_method); + /* This also tests for things like the IBAN being malformed */ + if (GNUNET_OK != + TALER_JSON_merchant_wire_signature_hash (wm->j_wire, + &wm->h_wire)) + { + free_wm (wm); + while (NULL != (wm = wm_head)) + { + GNUNET_CONTAINER_DLL_remove (wm_head, + wm_tail, + wm); + free_wm (wm); + } + TMH_db->rollback (TMH_db->cls); + GNUNET_JSON_parse_free (spec); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_POST_INSTANCES_BAD_PAYTO_URIS, + "Invalid bank account information"); + } + wm->active = true; + GNUNET_CONTAINER_DLL_insert (wm_head, + wm_tail, + wm); + ad.h_wire = wm->h_wire; + ad.active = true; + qs = TMH_db->insert_account (TMH_db->cls, + mi->settings.id, + &ad); + if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs) + { + TMH_db->rollback (TMH_db->cls); + if (GNUNET_DB_STATUS_SOFT_ERROR == qs) + goto retry; + else + goto giveup; + } + } + } + + qs = TMH_db->commit (TMH_db->cls); +retry: + if (GNUNET_DB_STATUS_SOFT_ERROR == qs) + continue; + break; + } /* for(... MAX_RETRIES) */ +giveup: + if (0 > qs) + { + struct TMH_WireMethod *wm; + + while (NULL != (wm = wm_head)) + { + GNUNET_CONTAINER_DLL_remove (wm_head, + wm_tail, + wm); + free_wm (wm); + } + GNUNET_JSON_parse_free (spec); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_PATCH_INSTANCES_DB_COMMIT_ERROR, + "failed to add instance to database"); + } + /* Deactivate existing wire methods that were removed above */ + for (struct TMH_WireMethod *wm = mi->wm_head; + NULL != wm; + wm = wm->next) + { + /* We did not flip the 'active' bits earlier because the + DB transaction could still fail. Now it is time to update our + runtime state. */ + if (wm->deleting) + wm->active = false; + } + + /* Update our 'settings' */ + GNUNET_free (mi->settings.name); + is.id = mi->settings.id; + mi->settings = is; + mi->settings.name = GNUNET_strdup (name); + + /* Add 'new' wire methods to our list */ + { + struct TMH_WireMethod *wm; + + while (NULL != (wm = wm_head)) + { + GNUNET_CONTAINER_DLL_remove (wm_head, + wm_tail, + wm); + GNUNET_CONTAINER_DLL_insert (mi->wm_head, + mi->wm_tail, + wm); + } + } + + GNUNET_JSON_parse_free (spec); + return TALER_MHD_reply_static (connection, + MHD_HTTP_NO_CONTENT, + NULL, + NULL, + 0); +} + + +/* end of taler-merchant-httpd_private-patch-instances-ID.c */ diff --git a/src/backend/taler-merchant-httpd_private-patch-instances-ID.h b/src/backend/taler-merchant-httpd_private-patch-instances-ID.h new file mode 100644 index 00000000..dde86a9b --- /dev/null +++ b/src/backend/taler-merchant-httpd_private-patch-instances-ID.h @@ -0,0 +1,43 @@ +/* + This file is part of TALER + (C) 2020 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 + published by the Free Software Foundation; either version 3, + or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, + see <http://www.gnu.org/licenses/> +*/ + +/** + * @file backend/taler-merchant-httpd_private-patch-instances-ID.h + * @brief implementing POST /instances request handling + * @author Christian Grothoff + */ +#ifndef TALER_MERCHANT_HTTPD_PRIVATE_PATCH_INSTANCES_ID_H +#define TALER_MERCHANT_HTTPD_PRIVATE_PATCH_INSTANCES_ID_H +#include "taler-merchant-httpd.h" + + +/** + * PATCH configuration of an existing instance, given its configuration. + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[in,out] hc context with further information about the request + * @return MHD result code + */ +MHD_RESULT +TMH_private_patch_instances_ID (const struct TMH_RequestHandler *rh, + struct MHD_Connection *connection, + struct TMH_HandlerContext *hc); + +#endif diff --git a/src/backend/taler-merchant-httpd_private-patch-products-ID.c b/src/backend/taler-merchant-httpd_private-patch-products-ID.c new file mode 100644 index 00000000..8177207c --- /dev/null +++ b/src/backend/taler-merchant-httpd_private-patch-products-ID.c @@ -0,0 +1,254 @@ +/* + This file is part of TALER + (C) 2020 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 + published by the Free Software Foundation; either version 3, + or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, + see <http://www.gnu.org/licenses/> +*/ + +/** + * @file backend/taler-merchant-httpd_private-patch-products.c + * @brief implementing PATCH /products/$ID request handling + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler-merchant-httpd_private-patch-products-ID.h" +#include <taler/taler_json_lib.h> + + +/** + * How often do we retry the simple INSERT database transaction? + */ +#define MAX_RETRIES 3 + + +/** + * Determine the cause of the PATCH failure in more detail and report. + * + * @param connection connection to report on + * @param instance_id instance we are processing + * @param product_id ID of the product to patch + * @param pd product details we failed to set + */ +static MHD_RESULT +determine_cause (struct MHD_Connection *connection, + const char *instance_id, + const char *product_id, + const struct TALER_MERCHANTDB_ProductDetails *pd) +{ + struct TALER_MERCHANTDB_ProductDetails pdx; + enum GNUNET_DB_QueryStatus qs; + + qs = TMH_db->lookup_product (TMH_db->cls, + instance_id, + product_id, + &pdx); + switch (qs) + { + case GNUNET_DB_STATUS_HARD_ERROR: + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_PRODUCTS_PATCH_DB_COMMIT_HARD_ERROR, + "Failed to get existing product"); + case GNUNET_DB_STATUS_SOFT_ERROR: + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_INTERNAL_INVARIANT_FAILURE, + "Serialization error for single-statment request"); + case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_NOT_FOUND, + TALER_EC_PRODUCTS_PATCH_UNKNOWN_PRODUCT, + "The specified product is unknown"); + case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: + break; /* do below */ + } + + { + enum TALER_ErrorCode ec; + const char *hint; + + ec = TALER_EC_INTERNAL_INVARIANT_FAILURE; + hint = "transaction failed for causes unknown"; + if (pdx.total_lost > pd->total_lost) + { + ec = TALER_EC_PRODUCTS_PATCH_TOTAL_LOST_REDUCED; + hint = "total lost cannot be lowered"; + } + if (pdx.total_sold > pd->total_sold) + { + ec = TALER_EC_PRODUCTS_PATCH_TOTAL_SOLD_REDUCED; + hint = "total sold cannot be lowered"; + } + if (pdx.total_stocked > pd->total_stocked) + { + ec = TALER_EC_PRODUCTS_PATCH_TOTAL_STOCKED_REDUCED; + hint = "total stocked cannot be lowered"; + } + GNUNET_free (pdx.description); + json_decref (pdx.description_i18n); + GNUNET_free (pdx.unit); + json_decref (pdx.taxes); + json_decref (pdx.image); + json_decref (pdx.location); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_CONFLICT, + ec, + hint); + } +} + + +/** + * PATCH configuration of an existing instance, given its configuration. + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[in,out] hc context with further information about the request + * @return MHD result code + */ +MHD_RESULT +TMH_private_patch_products_ID (const struct TMH_RequestHandler *rh, + struct MHD_Connection *connection, + struct TMH_HandlerContext *hc) +{ + struct TMH_MerchantInstance *mi = hc->instance; + const char *product_id = hc->infix; + struct TALER_MERCHANTDB_ProductDetails pd; + int64_t total_stocked; + enum GNUNET_DB_QueryStatus qs; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_string ("description", + (const char **) &pd.description), + GNUNET_JSON_spec_json ("description_i18n", + &pd.description_i18n), + GNUNET_JSON_spec_string ("unit", + (const char **) &pd.unit), + TALER_JSON_spec_amount ("price", + &pd.price), + GNUNET_JSON_spec_json ("image", + &pd.image), + GNUNET_JSON_spec_json ("taxes", + &pd.taxes), + GNUNET_JSON_spec_json ("location", + &pd.taxes), + GNUNET_JSON_spec_int64 ("total_stocked", + &total_stocked), + GNUNET_JSON_spec_absolute_time ("next_restock", + &pd.next_restock), + GNUNET_JSON_spec_end () + }; + + GNUNET_assert (NULL != mi); + GNUNET_assert (NULL != product_id); + { + enum GNUNET_GenericReturnValue res; + + res = TALER_MHD_parse_json_data (connection, + hc->request_body, + spec); + /* json is malformed */ + if (GNUNET_NO == res) + { + GNUNET_break_op (0); + return MHD_YES; + } + /* other internal errors might have occurred */ + if (GNUNET_SYSERR == res) + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_INTERNAL_INVARIANT_FAILURE, + "Impossible to parse the order"); + } + if (-1 == total_stocked) + pd.total_stocked = UINT64_MAX; + else + pd.total_stocked = (uint64_t) total_stocked; + if (NULL != json_object_get (hc->request_body, + "next_restock")) + { + enum GNUNET_GenericReturnValue res; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_absolute_time ("next_restock", + &pd.next_restock), + GNUNET_JSON_spec_end () + }; + + res = TALER_MHD_parse_json_data (connection, + hc->request_body, + spec); + /* json is malformed */ + if (GNUNET_NO == res) + { + GNUNET_break_op (0); + GNUNET_JSON_parse_free (spec); + return MHD_YES; + } + /* other internal errors might have occurred */ + if (GNUNET_SYSERR == res) + { + GNUNET_JSON_parse_free (spec); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_INTERNAL_INVARIANT_FAILURE, + "Impossible to parse the restock time"); + } + } + else + { + pd.next_restock.abs_value_us = 0; + } + + qs = TMH_db->update_product (TMH_db->cls, + mi->settings.id, + product_id, + &pd); + { + MHD_RESULT ret; + switch (qs) + { + case GNUNET_DB_STATUS_HARD_ERROR: + ret = TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_PRODUCTS_PATCH_DB_COMMIT_HARD_ERROR, + "Failed to commit change"); + break; + case GNUNET_DB_STATUS_SOFT_ERROR: + GNUNET_break (0); + ret = TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_INTERNAL_INVARIANT_FAILURE, + "Serialization error for single-statment request"); + break; + case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: + ret = determine_cause (connection, + mi->settings.id, + product_id, + &pd); + break; + case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: + ret = TALER_MHD_reply_static (connection, + MHD_HTTP_NO_CONTENT, + NULL, + NULL, + 0); + break; + } + GNUNET_JSON_parse_free (spec); + return ret; + } +} + + +/* end of taler-merchant-httpd_private-patch-products-ID.c */ diff --git a/src/backend/taler-merchant-httpd_private-patch-products-ID.h b/src/backend/taler-merchant-httpd_private-patch-products-ID.h new file mode 100644 index 00000000..dde86a9b --- /dev/null +++ b/src/backend/taler-merchant-httpd_private-patch-products-ID.h @@ -0,0 +1,43 @@ +/* + This file is part of TALER + (C) 2020 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 + published by the Free Software Foundation; either version 3, + or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, + see <http://www.gnu.org/licenses/> +*/ + +/** + * @file backend/taler-merchant-httpd_private-patch-instances-ID.h + * @brief implementing POST /instances request handling + * @author Christian Grothoff + */ +#ifndef TALER_MERCHANT_HTTPD_PRIVATE_PATCH_INSTANCES_ID_H +#define TALER_MERCHANT_HTTPD_PRIVATE_PATCH_INSTANCES_ID_H +#include "taler-merchant-httpd.h" + + +/** + * PATCH configuration of an existing instance, given its configuration. + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[in,out] hc context with further information about the request + * @return MHD result code + */ +MHD_RESULT +TMH_private_patch_instances_ID (const struct TMH_RequestHandler *rh, + struct MHD_Connection *connection, + struct TMH_HandlerContext *hc); + +#endif diff --git a/src/backend/taler-merchant-httpd_private-post-products.c b/src/backend/taler-merchant-httpd_private-post-products.c new file mode 100644 index 00000000..498e51c9 --- /dev/null +++ b/src/backend/taler-merchant-httpd_private-post-products.c @@ -0,0 +1,262 @@ +/* + This file is part of TALER + (C) 2020 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 + published by the Free Software Foundation; either version 3, + or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, + see <http://www.gnu.org/licenses/> +*/ + +/** + * @file backend/taler-merchant-httpd_private-post-products.c + * @brief implementing POST /products request handling + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler-merchant-httpd_private-post-products.h" +#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)) && + (0 == 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_stocked == p2->total_stocked) && + (p1->total_sold == p2->total_sold) && + (p1->total_lost == p2->total_lost) && + (1 == json_equal (p1->image, + p2->image)) && + (1 == json_equal (p1->location, + p2->location)) && + (p1->next_restock.abs_value_us == + p2->next_restock.abs_value_us) ); +} + + +/** + * Generate an instance, given its configuration. + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[in,out] hc context with further information about the request + * @return MHD result code + */ +MHD_RESULT +TMH_private_post_products (const struct TMH_RequestHandler *rh, + struct MHD_Connection *connection, + struct TMH_HandlerContext *hc) +{ + struct TMH_MerchantInstance *mi = hc->instance; + struct TALER_MERCHANTDB_ProductDetails pd; + const char *product_id; + int64_t total_stocked; + enum GNUNET_DB_QueryStatus qs; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_string ("product_id", + &product_id), + GNUNET_JSON_spec_string ("description", + (const char **) &pd.description), + GNUNET_JSON_spec_json ("description_i18n", + &pd.description_i18n), + GNUNET_JSON_spec_string ("unit", + (const char **) &pd.unit), + TALER_JSON_spec_amount ("price", + &pd.price), + GNUNET_JSON_spec_json ("image", + &pd.image), + GNUNET_JSON_spec_json ("taxes", + &pd.taxes), + GNUNET_JSON_spec_json ("location", + &pd.taxes), + GNUNET_JSON_spec_int64 ("total_stocked", + &total_stocked), + GNUNET_JSON_spec_absolute_time ("next_restock", + &pd.next_restock), + GNUNET_JSON_spec_end () + }; + + GNUNET_assert (NULL != mi); + { + enum GNUNET_GenericReturnValue res; + + res = TALER_MHD_parse_json_data (connection, + hc->request_body, + spec); + /* json is malformed */ + if (GNUNET_NO == res) + { + GNUNET_break_op (0); + return MHD_YES; + } + /* other internal errors might have occurred */ + if (GNUNET_SYSERR == res) + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_INTERNAL_INVARIANT_FAILURE, + "Impossible to parse the order"); + } + + if (-1 == total_stocked) + pd.total_stocked = UINT64_MAX; + else + pd.total_stocked = (uint64_t) total_stocked; + if (NULL != json_object_get (hc->request_body, + "next_restock")) + { + enum GNUNET_GenericReturnValue res; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_absolute_time ("next_restock", + &pd.next_restock), + GNUNET_JSON_spec_end () + }; + + res = TALER_MHD_parse_json_data (connection, + hc->request_body, + spec); + /* json is malformed */ + if (GNUNET_NO == res) + { + GNUNET_break_op (0); + GNUNET_JSON_parse_free (spec); + return MHD_YES; + } + /* other internal errors might have occurred */ + if (GNUNET_SYSERR == res) + { + GNUNET_JSON_parse_free (spec); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_INTERNAL_INVARIANT_FAILURE, + "Impossible to parse the restock time"); + } + } + else + { + pd.next_restock.abs_value_us = 0; + } + + for (unsigned int i = 0; i<MAX_RETRIES; i++) + { + /* 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_PRODUCTS_POST_DB_START_ERROR, + "Failed to start transaction"); + } + 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 */ + break; + 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? */ + if (products_equal (&pd, + &epd)) + { + TMH_db->rollback (TMH_db->cls); + GNUNET_JSON_parse_free (spec); + return TALER_MHD_reply_static (connection, + MHD_HTTP_NO_CONTENT, + NULL, + NULL, + 0); + } + else + { + TMH_db->rollback (TMH_db->cls); + GNUNET_JSON_parse_free (spec); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_CONFLICT, + TALER_EC_PRODUCTS_POST_CONFLICT_PRODUCT_EXISTS, + "different product exists under this product ID"); + } + } + + qs = TMH_db->insert_product (TMH_db->cls, + mi->settings.id, + product_id, + &pd); + if (GNUNET_DB_STATUS_SOFT_ERROR == qs) + goto retry; + qs = TMH_db->commit (TMH_db->cls); + if (GNUNET_DB_STATUS_SOFT_ERROR != qs) + break; +retry: + if (GNUNET_DB_STATUS_SOFT_ERROR == qs) + { + TMH_db->rollback (TMH_db->cls); + continue; + } + } + GNUNET_JSON_parse_free (spec); + if (qs < 0) + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + (GNUNET_DB_STATUS_SOFT_ERROR == qs) + ? + TALER_EC_PRODUCTS_POST_DB_COMMIT_SOFT_ERROR + : + TALER_EC_PRODUCTS_POST_DB_COMMIT_HARD_ERROR, + "Failed to commit transaction"); + return TALER_MHD_reply_static (connection, + MHD_HTTP_NO_CONTENT, + NULL, + NULL, + 0); +} + + +/* end of taler-merchant-httpd_private-post-products.c */ diff --git a/src/backend/taler-merchant-httpd_private-post-products.h b/src/backend/taler-merchant-httpd_private-post-products.h new file mode 100644 index 00000000..0874bc2d --- /dev/null +++ b/src/backend/taler-merchant-httpd_private-post-products.h @@ -0,0 +1,43 @@ +/* + This file is part of TALER + (C) 2020 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 + published by the Free Software Foundation; either version 3, + or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, + see <http://www.gnu.org/licenses/> +*/ + +/** + * @file backend/taler-merchant-httpd_private-post-products.h + * @brief implementing POST /products request handling + * @author Christian Grothoff + */ +#ifndef TALER_MERCHANT_HTTPD_PRIVATE_POST_PRODUCTS_H +#define TALER_MERCHANT_HTTPD_PRIVATE_POST_PRODUCTS_H +#include "taler-merchant-httpd.h" + + +/** + * Generate an instance, given its configuration. + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[in,out] hc context with further information about the request + * @return MHD result code + */ +MHD_RESULT +TMH_private_post_products (const struct TMH_RequestHandler *rh, + struct MHD_Connection *connection, + struct TMH_HandlerContext *hc); + +#endif diff --git a/src/include/taler_merchantdb_plugin.h b/src/include/taler_merchantdb_plugin.h index c20cdbb7..28913f59 100644 --- a/src/include/taler_merchantdb_plugin.h +++ b/src/include/taler_merchantdb_plugin.h @@ -496,8 +496,8 @@ struct TALER_MERCHANTDB_Plugin * @return database result code */ enum GNUNET_DB_QueryStatus - (*patch_instance)(void *cls, - const struct TALER_MERCHANTDB_InstanceSettings *is); + (*update_instance)(void *cls, + const struct TALER_MERCHANTDB_InstanceSettings *is); /** * Set an instance's account in our database to "inactive". @@ -555,6 +555,37 @@ struct TALER_MERCHANTDB_Plugin const char *instance_id, const char *product_id); + /** + * Insert details about a particular product. + * + * @param cls closure + * @param instance_id instance to insert product for + * @param product_id product identifier of product to insert + * @param pd the product details to insert + * @return database result code + */ + enum GNUNET_DB_QueryStatus + (*insert_product)(void *cls, + const char *instance_id, + const char *product_id, + const struct TALER_MERCHANTDB_ProductDetails *pd); + + /** + * Update details about a particular product. + * + * @param cls closure + * @param instance_id instance to lookup products for + * @param product_id product to lookup + * @param[out] pd set to the product details on success, can be NULL + * (in that case we only want to check if the product exists) + * @return database result code + */ + enum GNUNET_DB_QueryStatus + (*update_product)(void *cls, + const char *instance_id, + const char *product_id, + struct TALER_MERCHANTDB_ProductDetails *pd); + /* ****************** OLD API ******************** */ |