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.1 % 157 88
Test Date: 2025-12-29 21:26:17 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           14 : TMH_private_patch_products_ID (
      41              :   const struct TMH_RequestHandler *rh,
      42              :   struct MHD_Connection *connection,
      43              :   struct TMH_HandlerContext *hc)
      44              : {
      45           14 :   struct TMH_MerchantInstance *mi = hc->instance;
      46           14 :   const char *product_id = hc->infix;
      47           14 :   struct TALER_MERCHANTDB_ProductDetails pd = {0};
      48           14 :   const json_t *categories = NULL;
      49              :   int64_t total_stock;
      50           14 :   const char *unit_total_stock = NULL;
      51              :   bool unit_total_stock_missing;
      52              :   bool total_stock_missing;
      53              :   bool price_missing;
      54              :   bool unit_price_missing;
      55              :   bool unit_allow_fraction;
      56              :   bool unit_allow_fraction_missing;
      57              :   uint32_t unit_precision_level;
      58              :   bool unit_precision_missing;
      59              :   enum GNUNET_DB_QueryStatus qs;
      60              :   struct GNUNET_JSON_Specification spec[] = {
      61              :     /* new in protocol v20, thus optional for backwards-compatibility */
      62           14 :     GNUNET_JSON_spec_mark_optional (
      63              :       GNUNET_JSON_spec_string ("product_name",
      64              :                                (const char **) &pd.product_name),
      65              :       NULL),
      66           14 :     GNUNET_JSON_spec_string ("description",
      67              :                              (const char **) &pd.description),
      68           14 :     GNUNET_JSON_spec_mark_optional (
      69              :       GNUNET_JSON_spec_json ("description_i18n",
      70              :                              &pd.description_i18n),
      71              :       NULL),
      72           14 :     GNUNET_JSON_spec_string ("unit",
      73              :                              (const char **) &pd.unit),
      74           14 :     GNUNET_JSON_spec_mark_optional (
      75              :       TALER_JSON_spec_amount_any ("price",
      76              :                                   &pd.price),
      77              :       &price_missing),
      78           14 :     GNUNET_JSON_spec_mark_optional (
      79              :       GNUNET_JSON_spec_string ("image",
      80              :                                (const char **) &pd.image),
      81              :       NULL),
      82           14 :     GNUNET_JSON_spec_mark_optional (
      83              :       GNUNET_JSON_spec_json ("taxes",
      84              :                              &pd.taxes),
      85              :       NULL),
      86           14 :     GNUNET_JSON_spec_mark_optional (
      87              :       GNUNET_JSON_spec_array_const ("categories",
      88              :                                     &categories),
      89              :       NULL),
      90           14 :     GNUNET_JSON_spec_mark_optional (
      91              :       GNUNET_JSON_spec_string ("unit_total_stock",
      92              :                                &unit_total_stock),
      93              :       &unit_total_stock_missing),
      94           14 :     GNUNET_JSON_spec_mark_optional (
      95              :       GNUNET_JSON_spec_int64 ("total_stock",
      96              :                               &total_stock),
      97              :       &total_stock_missing),
      98           14 :     GNUNET_JSON_spec_mark_optional (
      99              :       GNUNET_JSON_spec_bool ("unit_allow_fraction",
     100              :                              &unit_allow_fraction),
     101              :       &unit_allow_fraction_missing),
     102           14 :     GNUNET_JSON_spec_mark_optional (
     103              :       GNUNET_JSON_spec_uint32 ("unit_precision_level",
     104              :                                &unit_precision_level),
     105              :       &unit_precision_missing),
     106           14 :     GNUNET_JSON_spec_mark_optional (
     107              :       TALER_JSON_spec_amount_any_array ("unit_price",
     108              :                                         &pd.price_array_length,
     109              :                                         &pd.price_array),
     110              :       &unit_price_missing),
     111           14 :     GNUNET_JSON_spec_mark_optional (
     112              :       GNUNET_JSON_spec_uint64 ("total_lost",
     113              :                                &pd.total_lost),
     114              :       NULL),
     115           14 :     GNUNET_JSON_spec_mark_optional (
     116              :       GNUNET_JSON_spec_json ("address",
     117              :                              &pd.address),
     118              :       NULL),
     119           14 :     GNUNET_JSON_spec_mark_optional (
     120              :       GNUNET_JSON_spec_timestamp ("next_restock",
     121              :                                   &pd.next_restock),
     122              :       NULL),
     123           14 :     GNUNET_JSON_spec_mark_optional (
     124              :       GNUNET_JSON_spec_uint32 ("minimum_age",
     125              :                                &pd.minimum_age),
     126              :       NULL),
     127           14 :     GNUNET_JSON_spec_end ()
     128              :   };
     129              :   MHD_RESULT ret;
     130           14 :   size_t num_cats = 0;
     131           14 :   uint64_t *cats = NULL;
     132              :   bool no_instance;
     133              :   ssize_t no_cat;
     134              :   bool no_product;
     135              :   bool lost_reduced;
     136              :   bool sold_reduced;
     137              :   bool stock_reduced;
     138              :   bool no_group;
     139              :   bool no_pot;
     140              : 
     141           14 :   pd.total_sold = 0; /* will be ignored anyway */
     142           14 :   GNUNET_assert (NULL != mi);
     143           14 :   GNUNET_assert (NULL != product_id);
     144              :   {
     145              :     enum GNUNET_GenericReturnValue res;
     146              : 
     147           14 :     res = TALER_MHD_parse_json_data (connection,
     148           14 :                                      hc->request_body,
     149              :                                      spec);
     150           14 :     if (GNUNET_OK != res)
     151              :       return (GNUNET_NO == res)
     152              :              ? MHD_YES
     153            0 :              : MHD_NO;
     154              :     /* For pre-v20 clients, we use the description given as the
     155              :        product name; remove once we make product_name mandatory. */
     156           14 :     if (NULL == pd.product_name)
     157            0 :       pd.product_name = pd.description;
     158              :   }
     159           14 :   if (! unit_price_missing)
     160              :   {
     161           14 :     if (! price_missing)
     162              :     {
     163            0 :       if (0 != TALER_amount_cmp (&pd.price,
     164            0 :                                  &pd.price_array[0]))
     165              :       {
     166            0 :         ret = TALER_MHD_reply_with_error (connection,
     167              :                                           MHD_HTTP_BAD_REQUEST,
     168              :                                           TALER_EC_GENERIC_PARAMETER_MALFORMED,
     169              :                                           "price,unit_price mismatch");
     170            0 :         goto cleanup;
     171              :       }
     172              :     }
     173              :     else
     174              :     {
     175           14 :       pd.price = pd.price_array[0];
     176           14 :       price_missing = false;
     177              :     }
     178              :   }
     179              :   else
     180              :   {
     181            0 :     if (price_missing)
     182              :     {
     183            0 :       ret = TALER_MHD_reply_with_error (connection,
     184              :                                         MHD_HTTP_BAD_REQUEST,
     185              :                                         TALER_EC_GENERIC_PARAMETER_MALFORMED,
     186              :                                         "price missing");
     187            0 :       goto cleanup;
     188              :     }
     189            0 :     pd.price_array = GNUNET_new_array (1,
     190              :                                        struct TALER_Amount);
     191            0 :     pd.price_array[0] = pd.price;
     192            0 :     pd.price_array_length = 1;
     193              :   }
     194           14 :   if (! unit_precision_missing)
     195              :   {
     196            4 :     if (unit_precision_level > TMH_MAX_FRACTIONAL_PRECISION_LEVEL)
     197              :     {
     198            0 :       ret = TALER_MHD_reply_with_error (connection,
     199              :                                         MHD_HTTP_BAD_REQUEST,
     200              :                                         TALER_EC_GENERIC_PARAMETER_MALFORMED,
     201              :                                         "unit_precision_level");
     202            0 :       goto cleanup;
     203              :     }
     204              :   }
     205              :   {
     206              :     bool default_allow_fractional;
     207              :     uint32_t default_precision_level;
     208              : 
     209           14 :     if (GNUNET_OK !=
     210           14 :         TMH_unit_defaults_for_instance (mi,
     211           14 :                                         pd.unit,
     212              :                                         &default_allow_fractional,
     213              :                                         &default_precision_level))
     214              :     {
     215            0 :       GNUNET_break (0);
     216            0 :       ret = TALER_MHD_reply_with_error (connection,
     217              :                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
     218              :                                         TALER_EC_GENERIC_DB_FETCH_FAILED,
     219              :                                         "unit defaults");
     220            0 :       goto cleanup;
     221              :     }
     222           14 :     if (unit_allow_fraction_missing)
     223           10 :       unit_allow_fraction = default_allow_fractional;
     224           14 :     if (unit_precision_missing)
     225           10 :       unit_precision_level = default_precision_level;
     226              : 
     227           14 :     if (! unit_allow_fraction)
     228           10 :       unit_precision_level = 0;
     229           14 :     pd.fractional_precision_level = unit_precision_level;
     230              :   }
     231              :   {
     232              :     const char *eparam;
     233           14 :     if (GNUNET_OK !=
     234           14 :         TMH_process_quantity_inputs (TMH_VK_STOCK,
     235              :                                      unit_allow_fraction,
     236              :                                      total_stock_missing,
     237              :                                      total_stock,
     238              :                                      unit_total_stock_missing,
     239              :                                      unit_total_stock,
     240              :                                      &pd.total_stock,
     241              :                                      &pd.total_stock_frac,
     242              :                                      &eparam))
     243              :     {
     244            0 :       ret = TALER_MHD_reply_with_error (
     245              :         connection,
     246              :         MHD_HTTP_BAD_REQUEST,
     247              :         TALER_EC_GENERIC_PARAMETER_MALFORMED,
     248              :         eparam);
     249            0 :       goto cleanup;
     250              :     }
     251           14 :     pd.allow_fractional_quantity = unit_allow_fraction;
     252              :   }
     253           14 :   if (NULL == pd.address)
     254            2 :     pd.address = json_object ();
     255              : 
     256           14 :   if (! TMH_location_object_valid (pd.address))
     257              :   {
     258            0 :     GNUNET_break_op (0);
     259            0 :     ret = TALER_MHD_reply_with_error (connection,
     260              :                                       MHD_HTTP_BAD_REQUEST,
     261              :                                       TALER_EC_GENERIC_PARAMETER_MALFORMED,
     262              :                                       "address");
     263            0 :     goto cleanup;
     264              :   }
     265           14 :   num_cats = json_array_size (categories);
     266           14 :   cats = GNUNET_new_array (num_cats,
     267              :                            uint64_t);
     268              :   {
     269              :     size_t idx;
     270              :     json_t *val;
     271              : 
     272           14 :     json_array_foreach (categories, idx, val)
     273              :     {
     274            0 :       if (! json_is_integer (val))
     275              :       {
     276            0 :         GNUNET_break_op (0);
     277            0 :         ret = TALER_MHD_reply_with_error (connection,
     278              :                                           MHD_HTTP_BAD_REQUEST,
     279              :                                           TALER_EC_GENERIC_PARAMETER_MALFORMED,
     280              :                                           "categories");
     281            0 :         goto cleanup;
     282              :       }
     283            0 :       cats[idx] = json_integer_value (val);
     284              :     }
     285              :   }
     286              : 
     287           14 :   if (NULL == pd.description_i18n)
     288            2 :     pd.description_i18n = json_object ();
     289              : 
     290           14 :   if (! TALER_JSON_check_i18n (pd.description_i18n))
     291              :   {
     292            0 :     GNUNET_break_op (0);
     293            0 :     ret = TALER_MHD_reply_with_error (connection,
     294              :                                       MHD_HTTP_BAD_REQUEST,
     295              :                                       TALER_EC_GENERIC_PARAMETER_MALFORMED,
     296              :                                       "description_i18n");
     297            0 :     goto cleanup;
     298              :   }
     299              : 
     300           14 :   if (NULL == pd.taxes)
     301            0 :     pd.taxes = json_array ();
     302              :   /* check taxes is well-formed */
     303           14 :   if (! TMH_taxes_array_valid (pd.taxes))
     304              :   {
     305            0 :     GNUNET_break_op (0);
     306            0 :     ret = TALER_MHD_reply_with_error (connection,
     307              :                                       MHD_HTTP_BAD_REQUEST,
     308              :                                       TALER_EC_GENERIC_PARAMETER_MALFORMED,
     309              :                                       "taxes");
     310            0 :     goto cleanup;
     311              :   }
     312              : 
     313           14 :   if (NULL == pd.image)
     314            0 :     pd.image = (char *) "";
     315           14 :   if (! TMH_image_data_url_valid (pd.image))
     316              :   {
     317            0 :     GNUNET_break_op (0);
     318            0 :     ret = TALER_MHD_reply_with_error (connection,
     319              :                                       MHD_HTTP_BAD_REQUEST,
     320              :                                       TALER_EC_GENERIC_PARAMETER_MALFORMED,
     321              :                                       "image");
     322            0 :     goto cleanup;
     323              :   }
     324              : 
     325           14 :   if ( (pd.total_stock < pd.total_sold + pd.total_lost) ||
     326           14 :        (pd.total_sold + pd.total_lost < pd.total_sold) /* integer overflow */)
     327              :   {
     328            0 :     GNUNET_break_op (0);
     329            0 :     ret = TALER_MHD_reply_with_error (
     330              :       connection,
     331              :       MHD_HTTP_BAD_REQUEST,
     332              :       TALER_EC_MERCHANT_PRIVATE_PATCH_PRODUCTS_TOTAL_LOST_EXCEEDS_STOCKS,
     333              :       NULL);
     334            0 :     goto cleanup;
     335              :   }
     336              : 
     337           14 :   qs = TMH_db->update_product (TMH_db->cls,
     338           14 :                                mi->settings.id,
     339              :                                product_id,
     340              :                                &pd,
     341              :                                num_cats,
     342              :                                cats,
     343              :                                &no_instance,
     344              :                                &no_cat,
     345              :                                &no_product,
     346              :                                &lost_reduced,
     347              :                                &sold_reduced,
     348              :                                &stock_reduced,
     349              :                                &no_group,
     350              :                                &no_pot);
     351           14 :   switch (qs)
     352              :   {
     353            0 :   case GNUNET_DB_STATUS_HARD_ERROR:
     354            0 :     GNUNET_break (0);
     355            0 :     ret = TALER_MHD_reply_with_error (connection,
     356              :                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
     357              :                                       TALER_EC_GENERIC_DB_STORE_FAILED,
     358              :                                       NULL);
     359            0 :     goto cleanup;
     360            0 :   case GNUNET_DB_STATUS_SOFT_ERROR:
     361            0 :     GNUNET_break (0);
     362            0 :     ret = TALER_MHD_reply_with_error (connection,
     363              :                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
     364              :                                       TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
     365              :                                       "unexpected serialization problem");
     366            0 :     goto cleanup;
     367            0 :   case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
     368            0 :     GNUNET_break (0);
     369            0 :     ret = TALER_MHD_reply_with_error (connection,
     370              :                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
     371              :                                       TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
     372              :                                       "unexpected problem in stored procedure");
     373            0 :     goto cleanup;
     374           14 :   case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
     375           14 :     break;
     376              :   }
     377              : 
     378           14 :   if (no_instance)
     379              :   {
     380            0 :     ret = TALER_MHD_reply_with_error (connection,
     381              :                                       MHD_HTTP_NOT_FOUND,
     382              :                                       TALER_EC_MERCHANT_GENERIC_INSTANCE_UNKNOWN,
     383            0 :                                       mi->settings.id);
     384            0 :     goto cleanup;
     385              :   }
     386           14 :   if (-1 != no_cat)
     387              :   {
     388              :     char cat_str[24];
     389              : 
     390            0 :     GNUNET_snprintf (cat_str,
     391              :                      sizeof (cat_str),
     392              :                      "%llu",
     393              :                      (unsigned long long) no_cat);
     394            0 :     ret = TALER_MHD_reply_with_error (connection,
     395              :                                       MHD_HTTP_NOT_FOUND,
     396              :                                       TALER_EC_MERCHANT_GENERIC_CATEGORY_UNKNOWN,
     397              :                                       cat_str);
     398            0 :     goto cleanup;
     399              :   }
     400           14 :   if (no_product)
     401              :   {
     402            2 :     ret = TALER_MHD_reply_with_error (connection,
     403              :                                       MHD_HTTP_NOT_FOUND,
     404              :                                       TALER_EC_MERCHANT_GENERIC_PRODUCT_UNKNOWN,
     405              :                                       product_id);
     406            2 :     goto cleanup;
     407              :   }
     408           12 :   if (no_group)
     409              :   {
     410            0 :     ret = TALER_MHD_reply_with_error (
     411              :       connection,
     412              :       MHD_HTTP_NOT_FOUND,
     413              :       TALER_EC_MERCHANT_GENERIC_PRODUCT_GROUP_UNKNOWN,
     414              :       NULL);
     415            0 :     goto cleanup;
     416              :   }
     417           12 :   if (no_pot)
     418              :   {
     419            0 :     ret = TALER_MHD_reply_with_error (
     420              :       connection,
     421              :       MHD_HTTP_NOT_FOUND,
     422              :       TALER_EC_MERCHANT_GENERIC_MONEY_POT_UNKNOWN,
     423              :       NULL);
     424            0 :     goto cleanup;
     425              :   }
     426           12 :   if (lost_reduced)
     427              :   {
     428            0 :     ret = TALER_MHD_reply_with_error (
     429              :       connection,
     430              :       MHD_HTTP_CONFLICT,
     431              :       TALER_EC_MERCHANT_PRIVATE_PATCH_PRODUCTS_TOTAL_LOST_REDUCED,
     432              :       NULL);
     433            0 :     goto cleanup;
     434              :   }
     435           12 :   if (sold_reduced)
     436              :   {
     437            0 :     ret = TALER_MHD_reply_with_error (
     438              :       connection,
     439              :       MHD_HTTP_CONFLICT,
     440              :       TALER_EC_MERCHANT_PRIVATE_PATCH_PRODUCTS_TOTAL_SOLD_REDUCED,
     441              :       NULL);
     442            0 :     goto cleanup;
     443              :   }
     444           12 :   if (stock_reduced)
     445              :   {
     446            0 :     ret = TALER_MHD_reply_with_error (
     447              :       connection,
     448              :       MHD_HTTP_CONFLICT,
     449              :       TALER_EC_MERCHANT_PRIVATE_PATCH_PRODUCTS_TOTAL_STOCKED_REDUCED,
     450              :       NULL);
     451            0 :     goto cleanup;
     452              :   }
     453              :   /* success! */
     454           12 :   ret = TALER_MHD_reply_static (connection,
     455              :                                 MHD_HTTP_NO_CONTENT,
     456              :                                 NULL,
     457              :                                 NULL,
     458              :                                 0);
     459           14 : cleanup:
     460           14 :   GNUNET_free (cats);
     461           14 :   GNUNET_free (pd.price_array);
     462           14 :   GNUNET_JSON_parse_free (spec);
     463           14 :   return ret;
     464              : }
     465              : 
     466              : 
     467              : /* end of taler-merchant-httpd_private-patch-products-ID.c */
        

Generated by: LCOV version 2.0-1