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

            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 2.0-1