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: 63 113 55.8 %
Date: 2025-06-23 16:22:09 Functions: 1 1 100.0 %

          Line data    Source code
       1             : /*
       2             :   This file is part of TALER
       3             :   (C) 2020-2024 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           8 :     GNUNET_JSON_spec_string ("description",
      53             :                              (const char **) &pd.description),
      54           8 :     GNUNET_JSON_spec_mark_optional (
      55             :       GNUNET_JSON_spec_json ("description_i18n",
      56             :                              &pd.description_i18n),
      57             :       NULL),
      58           8 :     GNUNET_JSON_spec_string ("unit",
      59             :                              (const char **) &pd.unit),
      60           8 :     TALER_JSON_spec_amount_any ("price",
      61             :                                 &pd.price),
      62           8 :     GNUNET_JSON_spec_mark_optional (
      63             :       GNUNET_JSON_spec_string ("image",
      64             :                                (const char **) &pd.image),
      65             :       NULL),
      66           8 :     GNUNET_JSON_spec_mark_optional (
      67             :       GNUNET_JSON_spec_json ("taxes",
      68             :                              &pd.taxes),
      69             :       NULL),
      70           8 :     GNUNET_JSON_spec_mark_optional (
      71             :       GNUNET_JSON_spec_array_const ("categories",
      72             :                                     &categories),
      73             :       NULL),
      74           8 :     GNUNET_JSON_spec_int64 ("total_stock",
      75             :                             &total_stock),
      76           8 :     GNUNET_JSON_spec_mark_optional (
      77             :       GNUNET_JSON_spec_uint64 ("total_lost",
      78             :                                &pd.total_lost),
      79             :       NULL),
      80           8 :     GNUNET_JSON_spec_mark_optional (
      81             :       GNUNET_JSON_spec_json ("address",
      82             :                              &pd.address),
      83             :       NULL),
      84           8 :     GNUNET_JSON_spec_mark_optional (
      85             :       GNUNET_JSON_spec_timestamp ("next_restock",
      86             :                                   &pd.next_restock),
      87             :       NULL),
      88           8 :     GNUNET_JSON_spec_mark_optional (
      89             :       GNUNET_JSON_spec_uint32 ("minimum_age",
      90             :                                &pd.minimum_age),
      91             :       NULL),
      92           8 :     GNUNET_JSON_spec_end ()
      93             :   };
      94             :   MHD_RESULT ret;
      95           8 :   size_t num_cats = 0;
      96           8 :   uint64_t *cats = NULL;
      97             :   bool no_instance;
      98             :   ssize_t no_cat;
      99             :   bool no_product;
     100             :   bool lost_reduced;
     101             :   bool sold_reduced;
     102             :   bool stock_reduced;
     103             : 
     104           8 :   pd.total_sold = 0; /* will be ignored anyway */
     105           8 :   GNUNET_assert (NULL != mi);
     106           8 :   GNUNET_assert (NULL != product_id);
     107             :   {
     108             :     enum GNUNET_GenericReturnValue res;
     109             : 
     110           8 :     res = TALER_MHD_parse_json_data (connection,
     111           8 :                                      hc->request_body,
     112             :                                      spec);
     113           8 :     if (GNUNET_OK != res)
     114             :       return (GNUNET_NO == res)
     115             :              ? MHD_YES
     116           0 :              : MHD_NO;
     117             :   }
     118           8 :   if (total_stock < -1)
     119             :   {
     120           0 :     GNUNET_break_op (0);
     121           0 :     ret = TALER_MHD_reply_with_error (connection,
     122             :                                       MHD_HTTP_BAD_REQUEST,
     123             :                                       TALER_EC_GENERIC_PARAMETER_MALFORMED,
     124             :                                       "total_stock");
     125           0 :     goto cleanup;
     126             :   }
     127           8 :   if (-1 == total_stock)
     128           1 :     pd.total_stock = INT64_MAX;
     129             :   else
     130           7 :     pd.total_stock = (uint64_t) total_stock;
     131           8 :   if (NULL == pd.address)
     132           2 :     pd.address = json_object ();
     133             : 
     134           8 :   if (! TMH_location_object_valid (pd.address))
     135             :   {
     136           0 :     GNUNET_break_op (0);
     137           0 :     ret = TALER_MHD_reply_with_error (connection,
     138             :                                       MHD_HTTP_BAD_REQUEST,
     139             :                                       TALER_EC_GENERIC_PARAMETER_MALFORMED,
     140             :                                       "address");
     141           0 :     goto cleanup;
     142             :   }
     143           8 :   num_cats = json_array_size (categories);
     144           8 :   cats = GNUNET_new_array (num_cats,
     145             :                            uint64_t);
     146             :   {
     147             :     size_t idx;
     148             :     json_t *val;
     149             : 
     150           8 :     json_array_foreach (categories, idx, val)
     151             :     {
     152           0 :       if (! json_is_integer (val))
     153             :       {
     154           0 :         GNUNET_break_op (0);
     155           0 :         ret = TALER_MHD_reply_with_error (connection,
     156             :                                           MHD_HTTP_BAD_REQUEST,
     157             :                                           TALER_EC_GENERIC_PARAMETER_MALFORMED,
     158             :                                           "categories");
     159           0 :         goto cleanup;
     160             :       }
     161           0 :       cats[idx] = json_integer_value (val);
     162             :     }
     163             :   }
     164             : 
     165           8 :   if (NULL == pd.description_i18n)
     166           2 :     pd.description_i18n = json_object ();
     167             : 
     168           8 :   if (! TALER_JSON_check_i18n (pd.description_i18n))
     169             :   {
     170           0 :     GNUNET_break_op (0);
     171           0 :     ret = TALER_MHD_reply_with_error (connection,
     172             :                                       MHD_HTTP_BAD_REQUEST,
     173             :                                       TALER_EC_GENERIC_PARAMETER_MALFORMED,
     174             :                                       "description_i18n");
     175           0 :     goto cleanup;
     176             :   }
     177             : 
     178           8 :   if (NULL == pd.taxes)
     179           0 :     pd.taxes = json_array ();
     180             :   /* check taxes is well-formed */
     181           8 :   if (! TMH_taxes_array_valid (pd.taxes))
     182             :   {
     183           0 :     GNUNET_break_op (0);
     184           0 :     ret = TALER_MHD_reply_with_error (connection,
     185             :                                       MHD_HTTP_BAD_REQUEST,
     186             :                                       TALER_EC_GENERIC_PARAMETER_MALFORMED,
     187             :                                       "taxes");
     188           0 :     goto cleanup;
     189             :   }
     190             : 
     191           8 :   if (NULL == pd.image)
     192           0 :     pd.image = (char *) "";
     193           8 :   if (! TMH_image_data_url_valid (pd.image))
     194             :   {
     195           0 :     GNUNET_break_op (0);
     196           0 :     ret = TALER_MHD_reply_with_error (connection,
     197             :                                       MHD_HTTP_BAD_REQUEST,
     198             :                                       TALER_EC_GENERIC_PARAMETER_MALFORMED,
     199             :                                       "image");
     200           0 :     goto cleanup;
     201             :   }
     202             : 
     203           8 :   if ( (pd.total_stock < pd.total_sold + pd.total_lost) ||
     204           8 :        (pd.total_sold + pd.total_lost < pd.total_sold) /* integer overflow */)
     205             :   {
     206           0 :     GNUNET_break_op (0);
     207           0 :     ret = TALER_MHD_reply_with_error (
     208             :       connection,
     209             :       MHD_HTTP_BAD_REQUEST,
     210             :       TALER_EC_MERCHANT_PRIVATE_PATCH_PRODUCTS_TOTAL_LOST_EXCEEDS_STOCKS,
     211             :       NULL);
     212           0 :     goto cleanup;
     213             :   }
     214             : 
     215           8 :   qs = TMH_db->update_product (TMH_db->cls,
     216           8 :                                mi->settings.id,
     217             :                                product_id,
     218             :                                &pd,
     219             :                                num_cats,
     220             :                                cats,
     221             :                                &no_instance,
     222             :                                &no_cat,
     223             :                                &no_product,
     224             :                                &lost_reduced,
     225             :                                &sold_reduced,
     226             :                                &stock_reduced);
     227           8 :   switch (qs)
     228             :   {
     229           0 :   case GNUNET_DB_STATUS_HARD_ERROR:
     230           0 :     GNUNET_break (0);
     231           0 :     ret = TALER_MHD_reply_with_error (connection,
     232             :                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
     233             :                                       TALER_EC_GENERIC_DB_STORE_FAILED,
     234             :                                       NULL);
     235           0 :     goto cleanup;
     236           0 :   case GNUNET_DB_STATUS_SOFT_ERROR:
     237           0 :     GNUNET_break (0);
     238           0 :     ret = TALER_MHD_reply_with_error (connection,
     239             :                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
     240             :                                       TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
     241             :                                       "unexpected serialization problem");
     242           0 :     goto cleanup;
     243           0 :   case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
     244           0 :     GNUNET_break (0);
     245           0 :     ret = TALER_MHD_reply_with_error (connection,
     246             :                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
     247             :                                       TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
     248             :                                       "unexpected problem in stored procedure");
     249           0 :     goto cleanup;
     250           8 :   case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
     251           8 :     break;
     252             :   }
     253             : 
     254           8 :   if (no_instance)
     255             :   {
     256           0 :     ret = TALER_MHD_reply_with_error (connection,
     257             :                                       MHD_HTTP_NOT_FOUND,
     258             :                                       TALER_EC_MERCHANT_GENERIC_INSTANCE_UNKNOWN,
     259           0 :                                       mi->settings.id);
     260           0 :     goto cleanup;
     261             :   }
     262           8 :   if (-1 != no_cat)
     263             :   {
     264             :     char cat_str[24];
     265             : 
     266           0 :     GNUNET_snprintf (cat_str,
     267             :                      sizeof (cat_str),
     268             :                      "%llu",
     269             :                      (unsigned long long) no_cat);
     270           0 :     ret = TALER_MHD_reply_with_error (connection,
     271             :                                       MHD_HTTP_NOT_FOUND,
     272             :                                       TALER_EC_MERCHANT_GENERIC_CATEGORY_UNKNOWN,
     273             :                                       cat_str);
     274           0 :     goto cleanup;
     275             :   }
     276           8 :   if (no_product)
     277             :   {
     278           2 :     ret = TALER_MHD_reply_with_error (connection,
     279             :                                       MHD_HTTP_NOT_FOUND,
     280             :                                       TALER_EC_MERCHANT_GENERIC_PRODUCT_UNKNOWN,
     281             :                                       product_id);
     282           2 :     goto cleanup;
     283             :   }
     284           6 :   if (lost_reduced)
     285             :   {
     286           0 :     ret = TALER_MHD_reply_with_error (
     287             :       connection,
     288             :       MHD_HTTP_CONFLICT,
     289             :       TALER_EC_MERCHANT_PRIVATE_PATCH_PRODUCTS_TOTAL_LOST_REDUCED,
     290             :       NULL);
     291           0 :     goto cleanup;
     292             :   }
     293           6 :   if (sold_reduced)
     294             :   {
     295           0 :     ret = TALER_MHD_reply_with_error (
     296             :       connection,
     297             :       MHD_HTTP_CONFLICT,
     298             :       TALER_EC_MERCHANT_PRIVATE_PATCH_PRODUCTS_TOTAL_SOLD_REDUCED,
     299             :       NULL);
     300           0 :     goto cleanup;
     301             :   }
     302           6 :   if (stock_reduced)
     303             :   {
     304           0 :     ret = TALER_MHD_reply_with_error (
     305             :       connection,
     306             :       MHD_HTTP_CONFLICT,
     307             :       TALER_EC_MERCHANT_PRIVATE_PATCH_PRODUCTS_TOTAL_STOCKED_REDUCED,
     308             :       NULL);
     309           0 :     goto cleanup;
     310             :   }
     311             :   /* success! */
     312           6 :   ret = TALER_MHD_reply_static (connection,
     313             :                                 MHD_HTTP_NO_CONTENT,
     314             :                                 NULL,
     315             :                                 NULL,
     316             :                                 0);
     317           8 : cleanup:
     318           8 :   GNUNET_free (cats);
     319           8 :   GNUNET_JSON_parse_free (spec);
     320           8 :   return ret;
     321             : }
     322             : 
     323             : 
     324             : /* end of taler-merchant-httpd_private-patch-products-ID.c */

Generated by: LCOV version 1.16