LCOV - code coverage report
Current view: top level - backend - taler-merchant-httpd_private-patch-products-ID.c (source / functions) Hit Total Coverage
Test: coverage.info Lines: 65 116 56.0 %
Date: 2025-07-10 12:24:40 Functions: 1 1 100.0 %

          Line data    Source code
       1             : /*
       2             :   This file is part of TALER
       3             :   (C) 2020--2025 Taler Systems SA
       4             : 
       5             :   TALER is free software; you can redistribute it and/or modify
       6             :   it under the terms of the GNU Affero General Public License as
       7             :   published by the Free Software Foundation; either version 3,
       8             :   or (at your option) any later version.
       9             : 
      10             :   TALER is distributed in the hope that it will be useful, but
      11             :   WITHOUT ANY WARRANTY; without even the implied warranty of
      12             :   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      13             :   GNU General Public License for more details.
      14             : 
      15             :   You should have received a copy of the GNU General Public
      16             :   License along with TALER; see the file COPYING.  If not,
      17             :   see <http://www.gnu.org/licenses/>
      18             : */
      19             : 
      20             : /**
      21             :  * @file taler-merchant-httpd_private-patch-products-ID.c
      22             :  * @brief implementing PATCH /products/$ID request handling
      23             :  * @author Christian Grothoff
      24             :  */
      25             : #include "platform.h"
      26             : #include "taler-merchant-httpd_private-patch-products-ID.h"
      27             : #include "taler-merchant-httpd_helper.h"
      28             : #include <taler/taler_json_lib.h>
      29             : 
      30             : 
      31             : /**
      32             :  * PATCH configuration of an existing instance, given its configuration.
      33             :  *
      34             :  * @param rh context of the handler
      35             :  * @param connection the MHD connection to handle
      36             :  * @param[in,out] hc context with further information about the request
      37             :  * @return MHD result code
      38             :  */
      39             : MHD_RESULT
      40           8 : TMH_private_patch_products_ID (
      41             :   const struct TMH_RequestHandler *rh,
      42             :   struct MHD_Connection *connection,
      43             :   struct TMH_HandlerContext *hc)
      44             : {
      45           8 :   struct TMH_MerchantInstance *mi = hc->instance;
      46           8 :   const char *product_id = hc->infix;
      47           8 :   struct TALER_MERCHANTDB_ProductDetails pd = {0};
      48           8 :   const json_t *categories = NULL;
      49             :   int64_t total_stock;
      50             :   enum GNUNET_DB_QueryStatus qs;
      51             :   struct GNUNET_JSON_Specification spec[] = {
      52             :     /* new in protocol v20, thus optional for backwards-compatibility */
      53           8 :     GNUNET_JSON_spec_mark_optional (
      54             :       GNUNET_JSON_spec_string ("product_name",
      55             :                                (const char **) &pd.product_name),
      56             :       NULL),
      57           8 :     GNUNET_JSON_spec_string ("description",
      58             :                              (const char **) &pd.description),
      59           8 :     GNUNET_JSON_spec_mark_optional (
      60             :       GNUNET_JSON_spec_json ("description_i18n",
      61             :                              &pd.description_i18n),
      62             :       NULL),
      63           8 :     GNUNET_JSON_spec_string ("unit",
      64             :                              (const char **) &pd.unit),
      65           8 :     TALER_JSON_spec_amount_any ("price",
      66             :                                 &pd.price),
      67           8 :     GNUNET_JSON_spec_mark_optional (
      68             :       GNUNET_JSON_spec_string ("image",
      69             :                                (const char **) &pd.image),
      70             :       NULL),
      71           8 :     GNUNET_JSON_spec_mark_optional (
      72             :       GNUNET_JSON_spec_json ("taxes",
      73             :                              &pd.taxes),
      74             :       NULL),
      75           8 :     GNUNET_JSON_spec_mark_optional (
      76             :       GNUNET_JSON_spec_array_const ("categories",
      77             :                                     &categories),
      78             :       NULL),
      79           8 :     GNUNET_JSON_spec_int64 ("total_stock",
      80             :                             &total_stock),
      81           8 :     GNUNET_JSON_spec_mark_optional (
      82             :       GNUNET_JSON_spec_uint64 ("total_lost",
      83             :                                &pd.total_lost),
      84             :       NULL),
      85           8 :     GNUNET_JSON_spec_mark_optional (
      86             :       GNUNET_JSON_spec_json ("address",
      87             :                              &pd.address),
      88             :       NULL),
      89           8 :     GNUNET_JSON_spec_mark_optional (
      90             :       GNUNET_JSON_spec_timestamp ("next_restock",
      91             :                                   &pd.next_restock),
      92             :       NULL),
      93           8 :     GNUNET_JSON_spec_mark_optional (
      94             :       GNUNET_JSON_spec_uint32 ("minimum_age",
      95             :                                &pd.minimum_age),
      96             :       NULL),
      97           8 :     GNUNET_JSON_spec_end ()
      98             :   };
      99             :   MHD_RESULT ret;
     100           8 :   size_t num_cats = 0;
     101           8 :   uint64_t *cats = NULL;
     102             :   bool no_instance;
     103             :   ssize_t no_cat;
     104             :   bool no_product;
     105             :   bool lost_reduced;
     106             :   bool sold_reduced;
     107             :   bool stock_reduced;
     108             : 
     109           8 :   pd.total_sold = 0; /* will be ignored anyway */
     110           8 :   GNUNET_assert (NULL != mi);
     111           8 :   GNUNET_assert (NULL != product_id);
     112             :   {
     113             :     enum GNUNET_GenericReturnValue res;
     114             : 
     115           8 :     res = TALER_MHD_parse_json_data (connection,
     116           8 :                                      hc->request_body,
     117             :                                      spec);
     118           8 :     if (GNUNET_OK != res)
     119             :       return (GNUNET_NO == res)
     120             :              ? MHD_YES
     121           0 :              : MHD_NO;
     122             :     /* For pre-v20 clients, we use the description given as the
     123             :        product name; remove once we make product_name mandatory. */
     124           8 :     if (NULL == pd.product_name)
     125           0 :       pd.product_name = pd.description;
     126             :   }
     127           8 :   if (total_stock < -1)
     128             :   {
     129           0 :     GNUNET_break_op (0);
     130           0 :     ret = TALER_MHD_reply_with_error (connection,
     131             :                                       MHD_HTTP_BAD_REQUEST,
     132             :                                       TALER_EC_GENERIC_PARAMETER_MALFORMED,
     133             :                                       "total_stock");
     134           0 :     goto cleanup;
     135             :   }
     136           8 :   if (-1 == total_stock)
     137           1 :     pd.total_stock = INT64_MAX;
     138             :   else
     139           7 :     pd.total_stock = (uint64_t) total_stock;
     140           8 :   if (NULL == pd.address)
     141           2 :     pd.address = json_object ();
     142             : 
     143           8 :   if (! TMH_location_object_valid (pd.address))
     144             :   {
     145           0 :     GNUNET_break_op (0);
     146           0 :     ret = TALER_MHD_reply_with_error (connection,
     147             :                                       MHD_HTTP_BAD_REQUEST,
     148             :                                       TALER_EC_GENERIC_PARAMETER_MALFORMED,
     149             :                                       "address");
     150           0 :     goto cleanup;
     151             :   }
     152           8 :   num_cats = json_array_size (categories);
     153           8 :   cats = GNUNET_new_array (num_cats,
     154             :                            uint64_t);
     155             :   {
     156             :     size_t idx;
     157             :     json_t *val;
     158             : 
     159           8 :     json_array_foreach (categories, idx, val)
     160             :     {
     161           0 :       if (! json_is_integer (val))
     162             :       {
     163           0 :         GNUNET_break_op (0);
     164           0 :         ret = TALER_MHD_reply_with_error (connection,
     165             :                                           MHD_HTTP_BAD_REQUEST,
     166             :                                           TALER_EC_GENERIC_PARAMETER_MALFORMED,
     167             :                                           "categories");
     168           0 :         goto cleanup;
     169             :       }
     170           0 :       cats[idx] = json_integer_value (val);
     171             :     }
     172             :   }
     173             : 
     174           8 :   if (NULL == pd.description_i18n)
     175           2 :     pd.description_i18n = json_object ();
     176             : 
     177           8 :   if (! TALER_JSON_check_i18n (pd.description_i18n))
     178             :   {
     179           0 :     GNUNET_break_op (0);
     180           0 :     ret = TALER_MHD_reply_with_error (connection,
     181             :                                       MHD_HTTP_BAD_REQUEST,
     182             :                                       TALER_EC_GENERIC_PARAMETER_MALFORMED,
     183             :                                       "description_i18n");
     184           0 :     goto cleanup;
     185             :   }
     186             : 
     187           8 :   if (NULL == pd.taxes)
     188           0 :     pd.taxes = json_array ();
     189             :   /* check taxes is well-formed */
     190           8 :   if (! TMH_taxes_array_valid (pd.taxes))
     191             :   {
     192           0 :     GNUNET_break_op (0);
     193           0 :     ret = TALER_MHD_reply_with_error (connection,
     194             :                                       MHD_HTTP_BAD_REQUEST,
     195             :                                       TALER_EC_GENERIC_PARAMETER_MALFORMED,
     196             :                                       "taxes");
     197           0 :     goto cleanup;
     198             :   }
     199             : 
     200           8 :   if (NULL == pd.image)
     201           0 :     pd.image = (char *) "";
     202           8 :   if (! TMH_image_data_url_valid (pd.image))
     203             :   {
     204           0 :     GNUNET_break_op (0);
     205           0 :     ret = TALER_MHD_reply_with_error (connection,
     206             :                                       MHD_HTTP_BAD_REQUEST,
     207             :                                       TALER_EC_GENERIC_PARAMETER_MALFORMED,
     208             :                                       "image");
     209           0 :     goto cleanup;
     210             :   }
     211             : 
     212           8 :   if ( (pd.total_stock < pd.total_sold + pd.total_lost) ||
     213           8 :        (pd.total_sold + pd.total_lost < pd.total_sold) /* integer overflow */)
     214             :   {
     215           0 :     GNUNET_break_op (0);
     216           0 :     ret = TALER_MHD_reply_with_error (
     217             :       connection,
     218             :       MHD_HTTP_BAD_REQUEST,
     219             :       TALER_EC_MERCHANT_PRIVATE_PATCH_PRODUCTS_TOTAL_LOST_EXCEEDS_STOCKS,
     220             :       NULL);
     221           0 :     goto cleanup;
     222             :   }
     223             : 
     224           8 :   qs = TMH_db->update_product (TMH_db->cls,
     225           8 :                                mi->settings.id,
     226             :                                product_id,
     227             :                                &pd,
     228             :                                num_cats,
     229             :                                cats,
     230             :                                &no_instance,
     231             :                                &no_cat,
     232             :                                &no_product,
     233             :                                &lost_reduced,
     234             :                                &sold_reduced,
     235             :                                &stock_reduced);
     236           8 :   switch (qs)
     237             :   {
     238           0 :   case GNUNET_DB_STATUS_HARD_ERROR:
     239           0 :     GNUNET_break (0);
     240           0 :     ret = TALER_MHD_reply_with_error (connection,
     241             :                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
     242             :                                       TALER_EC_GENERIC_DB_STORE_FAILED,
     243             :                                       NULL);
     244           0 :     goto cleanup;
     245           0 :   case GNUNET_DB_STATUS_SOFT_ERROR:
     246           0 :     GNUNET_break (0);
     247           0 :     ret = TALER_MHD_reply_with_error (connection,
     248             :                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
     249             :                                       TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
     250             :                                       "unexpected serialization problem");
     251           0 :     goto cleanup;
     252           0 :   case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
     253           0 :     GNUNET_break (0);
     254           0 :     ret = TALER_MHD_reply_with_error (connection,
     255             :                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
     256             :                                       TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
     257             :                                       "unexpected problem in stored procedure");
     258           0 :     goto cleanup;
     259           8 :   case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
     260           8 :     break;
     261             :   }
     262             : 
     263           8 :   if (no_instance)
     264             :   {
     265           0 :     ret = TALER_MHD_reply_with_error (connection,
     266             :                                       MHD_HTTP_NOT_FOUND,
     267             :                                       TALER_EC_MERCHANT_GENERIC_INSTANCE_UNKNOWN,
     268           0 :                                       mi->settings.id);
     269           0 :     goto cleanup;
     270             :   }
     271           8 :   if (-1 != no_cat)
     272             :   {
     273             :     char cat_str[24];
     274             : 
     275           0 :     GNUNET_snprintf (cat_str,
     276             :                      sizeof (cat_str),
     277             :                      "%llu",
     278             :                      (unsigned long long) no_cat);
     279           0 :     ret = TALER_MHD_reply_with_error (connection,
     280             :                                       MHD_HTTP_NOT_FOUND,
     281             :                                       TALER_EC_MERCHANT_GENERIC_CATEGORY_UNKNOWN,
     282             :                                       cat_str);
     283           0 :     goto cleanup;
     284             :   }
     285           8 :   if (no_product)
     286             :   {
     287           2 :     ret = TALER_MHD_reply_with_error (connection,
     288             :                                       MHD_HTTP_NOT_FOUND,
     289             :                                       TALER_EC_MERCHANT_GENERIC_PRODUCT_UNKNOWN,
     290             :                                       product_id);
     291           2 :     goto cleanup;
     292             :   }
     293           6 :   if (lost_reduced)
     294             :   {
     295           0 :     ret = TALER_MHD_reply_with_error (
     296             :       connection,
     297             :       MHD_HTTP_CONFLICT,
     298             :       TALER_EC_MERCHANT_PRIVATE_PATCH_PRODUCTS_TOTAL_LOST_REDUCED,
     299             :       NULL);
     300           0 :     goto cleanup;
     301             :   }
     302           6 :   if (sold_reduced)
     303             :   {
     304           0 :     ret = TALER_MHD_reply_with_error (
     305             :       connection,
     306             :       MHD_HTTP_CONFLICT,
     307             :       TALER_EC_MERCHANT_PRIVATE_PATCH_PRODUCTS_TOTAL_SOLD_REDUCED,
     308             :       NULL);
     309           0 :     goto cleanup;
     310             :   }
     311           6 :   if (stock_reduced)
     312             :   {
     313           0 :     ret = TALER_MHD_reply_with_error (
     314             :       connection,
     315             :       MHD_HTTP_CONFLICT,
     316             :       TALER_EC_MERCHANT_PRIVATE_PATCH_PRODUCTS_TOTAL_STOCKED_REDUCED,
     317             :       NULL);
     318           0 :     goto cleanup;
     319             :   }
     320             :   /* success! */
     321           6 :   ret = TALER_MHD_reply_static (connection,
     322             :                                 MHD_HTTP_NO_CONTENT,
     323             :                                 NULL,
     324             :                                 NULL,
     325             :                                 0);
     326           8 : cleanup:
     327           8 :   GNUNET_free (cats);
     328           8 :   GNUNET_JSON_parse_free (spec);
     329           8 :   return ret;
     330             : }
     331             : 
     332             : 
     333             : /* end of taler-merchant-httpd_private-patch-products-ID.c */

Generated by: LCOV version 1.16