gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-merchant] 17/277: implement PATCH handlers


From: gnunet
Subject: [taler-merchant] 17/277: implement PATCH handlers
Date: Sun, 05 Jul 2020 20:48:50 +0200

This is an automated email from the git hooks/post-receive script.

grothoff pushed a commit to branch master
in repository merchant.

commit 3817d2e6cccdcee6021eccb6a1adcff4f0dce2ef
Author: Christian Grothoff <christian@grothoff.org>
AuthorDate: Sun Apr 19 22:02:31 2020 +0200

    implement PATCH handlers
---
 src/backend/Makefile.am                            |   6 +-
 src/backend/taler-merchant-httpd.c                 |   8 +
 ...ler-merchant-httpd_private-patch-instances-ID.c | 369 +++++++++++++++++++++
 ...ler-merchant-httpd_private-patch-instances-ID.h |  43 +++
 ...aler-merchant-httpd_private-patch-products-ID.c | 254 ++++++++++++++
 ...aler-merchant-httpd_private-patch-products-ID.h |  43 +++
 .../taler-merchant-httpd_private-post-products.c   | 262 +++++++++++++++
 .../taler-merchant-httpd_private-post-products.h   |  43 +++
 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 3124545..646841d 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 cc72248..58b6845 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 0000000..9e15149
--- /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 0000000..dde86a9
--- /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 0000000..8177207
--- /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 0000000..dde86a9
--- /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 0000000..498e51c
--- /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 0000000..0874bc2
--- /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 c20cdbb..28913f5 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 ******************** */
 

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]