LCOV - code coverage report
Current view: top level - backend - taler-merchant-httpd_private-post-products.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 61.1 % 131 80
Test Date: 2025-11-28 21:09:21 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-post-products.c
      22              :  * @brief implementing POST /products request handling
      23              :  * @author Christian Grothoff
      24              :  */
      25              : #include "platform.h"
      26              : #include "taler-merchant-httpd_private-post-products.h"
      27              : #include "taler-merchant-httpd_helper.h"
      28              : #include <taler/taler_json_lib.h>
      29              : 
      30              : MHD_RESULT
      31           22 : TMH_private_post_products (const struct TMH_RequestHandler *rh,
      32              :                            struct MHD_Connection *connection,
      33              :                            struct TMH_HandlerContext *hc)
      34              : {
      35           22 :   struct TMH_MerchantInstance *mi = hc->instance;
      36           22 :   struct TALER_MERCHANTDB_ProductDetails pd = { 0 };
      37           22 :   const json_t *categories = NULL;
      38              :   const char *product_id;
      39              :   int64_t total_stock;
      40           22 :   const char *unit_total_stock = NULL;
      41              :   bool unit_total_stock_missing;
      42              :   bool total_stock_missing;
      43              :   bool unit_price_missing;
      44              :   bool unit_allow_fraction;
      45              :   bool unit_allow_fraction_missing;
      46              :   uint32_t unit_precision_level;
      47              :   bool unit_precision_missing;
      48              :   bool price_missing;
      49              :   struct GNUNET_JSON_Specification spec[] = {
      50           22 :     GNUNET_JSON_spec_string ("product_id",
      51              :                              &product_id),
      52              :     /* new in protocol v20, thus optional for backwards-compatibility */
      53           22 :     GNUNET_JSON_spec_mark_optional (
      54              :       GNUNET_JSON_spec_string ("product_name",
      55              :                                (const char **) &pd.product_name),
      56              :       NULL),
      57           22 :     GNUNET_JSON_spec_string ("description",
      58              :                              (const char **) &pd.description),
      59           22 :     GNUNET_JSON_spec_mark_optional (
      60              :       GNUNET_JSON_spec_json ("description_i18n",
      61              :                              &pd.description_i18n),
      62              :       NULL),
      63           22 :     GNUNET_JSON_spec_string ("unit",
      64              :                              (const char **) &pd.unit),
      65           22 :     GNUNET_JSON_spec_mark_optional (
      66              :       TALER_JSON_spec_amount_any ("price",
      67              :                                   &pd.price),
      68              :       &price_missing),
      69           22 :     GNUNET_JSON_spec_mark_optional (
      70              :       GNUNET_JSON_spec_string ("image",
      71              :                                (const char **) &pd.image),
      72              :       NULL),
      73           22 :     GNUNET_JSON_spec_mark_optional (
      74              :       GNUNET_JSON_spec_json ("taxes",
      75              :                              &pd.taxes),
      76              :       NULL),
      77           22 :     GNUNET_JSON_spec_mark_optional (
      78              :       GNUNET_JSON_spec_array_const ("categories",
      79              :                                     &categories),
      80              :       NULL),
      81           22 :     GNUNET_JSON_spec_mark_optional (
      82              :       GNUNET_JSON_spec_string ("unit_total_stock",
      83              :                                &unit_total_stock),
      84              :       &unit_total_stock_missing),
      85           22 :     GNUNET_JSON_spec_mark_optional (
      86              :       GNUNET_JSON_spec_int64 ("total_stock",
      87              :                               &total_stock),
      88              :       &total_stock_missing),
      89           22 :     GNUNET_JSON_spec_mark_optional (
      90              :       GNUNET_JSON_spec_bool ("unit_allow_fraction",
      91              :                              &unit_allow_fraction),
      92              :       &unit_allow_fraction_missing),
      93           22 :     GNUNET_JSON_spec_mark_optional (
      94              :       GNUNET_JSON_spec_uint32 ("unit_precision_level",
      95              :                                &unit_precision_level),
      96              :       &unit_precision_missing),
      97           22 :     GNUNET_JSON_spec_mark_optional (
      98              :       TALER_JSON_spec_amount_any_array ("unit_price",
      99              :                                         &pd.price_array_length,
     100              :                                         &pd.price_array),
     101              :       &unit_price_missing),
     102           22 :     GNUNET_JSON_spec_mark_optional (
     103              :       GNUNET_JSON_spec_json ("address",
     104              :                              &pd.address),
     105              :       NULL),
     106           22 :     GNUNET_JSON_spec_mark_optional (
     107              :       GNUNET_JSON_spec_timestamp ("next_restock",
     108              :                                   &pd.next_restock),
     109              :       NULL),
     110           22 :     GNUNET_JSON_spec_mark_optional (
     111              :       GNUNET_JSON_spec_uint32 ("minimum_age",
     112              :                                &pd.minimum_age),
     113              :       NULL),
     114           22 :     GNUNET_JSON_spec_end ()
     115              :   };
     116           22 :   size_t num_cats = 0;
     117           22 :   uint64_t *cats = NULL;
     118              :   bool conflict;
     119              :   bool no_instance;
     120              :   ssize_t no_cat;
     121              :   enum GNUNET_DB_QueryStatus qs;
     122              :   MHD_RESULT ret;
     123              : 
     124           22 :   GNUNET_assert (NULL != mi);
     125              :   {
     126              :     enum GNUNET_GenericReturnValue res;
     127              : 
     128           22 :     res = TALER_MHD_parse_json_data (connection,
     129           22 :                                      hc->request_body,
     130              :                                      spec);
     131           22 :     if (GNUNET_OK != res)
     132              :     {
     133            0 :       GNUNET_break_op (0);
     134              :       return (GNUNET_NO == res)
     135              :              ? MHD_YES
     136            0 :              : MHD_NO;
     137              :     }
     138              :     /* For pre-v20 clients, we use the description given as the
     139              :        product name; remove once we make product_name mandatory. */
     140           22 :     if (NULL == pd.product_name)
     141            2 :       pd.product_name = pd.description;
     142              : 
     143           22 :     if (! unit_price_missing)
     144              :     {
     145           22 :       if (! price_missing)
     146              :       {
     147            0 :         if (0 != TALER_amount_cmp (&pd.price,
     148            0 :                                    &pd.price_array[0]))
     149              :         {
     150            0 :           ret = TALER_MHD_reply_with_error (connection,
     151              :                                             MHD_HTTP_BAD_REQUEST,
     152              :                                             TALER_EC_GENERIC_PARAMETER_MALFORMED,
     153              :                                             "price,unit_price mismatch");
     154            0 :           goto cleanup;
     155              :         }
     156              :       }
     157              :       else
     158              :       {
     159           22 :         pd.price = pd.price_array[0];
     160           22 :         price_missing = false;
     161              :       }
     162              :     }
     163              :     else
     164              :     {
     165            0 :       if (price_missing)
     166              :       {
     167            0 :         ret = TALER_MHD_reply_with_error (connection,
     168              :                                           MHD_HTTP_BAD_REQUEST,
     169              :                                           TALER_EC_GENERIC_PARAMETER_MALFORMED,
     170              :                                           "price and unit_price missing");
     171            0 :         goto cleanup;
     172              :       }
     173            0 :       pd.price_array = GNUNET_new_array (1,
     174              :                                          struct TALER_Amount);
     175            0 :       pd.price_array[0] = pd.price;
     176            0 :       pd.price_array_length = 1;
     177              :     }
     178              :   }
     179           22 :   if (! unit_precision_missing)
     180              :   {
     181            2 :     if (unit_precision_level > TMH_MAX_FRACTIONAL_PRECISION_LEVEL)
     182              :     {
     183            0 :       ret = TALER_MHD_reply_with_error (connection,
     184              :                                         MHD_HTTP_BAD_REQUEST,
     185              :                                         TALER_EC_GENERIC_PARAMETER_MALFORMED,
     186              :                                         "unit_precision_level");
     187            0 :       goto cleanup;
     188              :     }
     189              :   }
     190              :   {
     191              :     bool default_allow_fractional;
     192              :     uint32_t default_precision_level;
     193              : 
     194           22 :     if (GNUNET_OK !=
     195           22 :         TMH_unit_defaults_for_instance (mi,
     196           22 :                                         pd.unit,
     197              :                                         &default_allow_fractional,
     198              :                                         &default_precision_level))
     199              :     {
     200            0 :       GNUNET_break (0);
     201            0 :       ret = TALER_MHD_reply_with_error (connection,
     202              :                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
     203              :                                         TALER_EC_GENERIC_DB_FETCH_FAILED,
     204              :                                         "unit defaults");
     205            0 :       goto cleanup;
     206              :     }
     207           22 :     if (unit_allow_fraction_missing)
     208           20 :       unit_allow_fraction = default_allow_fractional;
     209           22 :     if (unit_precision_missing)
     210           20 :       unit_precision_level = default_precision_level;
     211              :   }
     212           22 :   if (! unit_allow_fraction)
     213           18 :     unit_precision_level = 0;
     214           22 :   pd.fractional_precision_level = unit_precision_level;
     215              :   {
     216              :     const char *eparam;
     217           22 :     if (GNUNET_OK !=
     218           22 :         TMH_process_quantity_inputs (TMH_VK_STOCK,
     219              :                                      unit_allow_fraction,
     220              :                                      total_stock_missing,
     221              :                                      total_stock,
     222              :                                      unit_total_stock_missing,
     223              :                                      unit_total_stock,
     224              :                                      &pd.total_stock,
     225              :                                      &pd.total_stock_frac,
     226              :                                      &eparam))
     227              :     {
     228            0 :       ret = TALER_MHD_reply_with_error (
     229              :         connection,
     230              :         MHD_HTTP_BAD_REQUEST,
     231              :         TALER_EC_GENERIC_PARAMETER_MALFORMED,
     232              :         eparam);
     233            0 :       goto cleanup;
     234              :     }
     235           22 :     pd.allow_fractional_quantity = unit_allow_fraction;
     236              :   }
     237           22 :   num_cats = json_array_size (categories);
     238           22 :   cats = GNUNET_new_array (num_cats,
     239              :                            uint64_t);
     240              :   {
     241              :     size_t idx;
     242              :     json_t *val;
     243              : 
     244           22 :     json_array_foreach (categories, idx, val)
     245              :     {
     246            0 :       if (! json_is_integer (val))
     247              :       {
     248            0 :         GNUNET_break_op (0);
     249            0 :         ret = TALER_MHD_reply_with_error (connection,
     250              :                                           MHD_HTTP_BAD_REQUEST,
     251              :                                           TALER_EC_GENERIC_PARAMETER_MALFORMED,
     252              :                                           "categories");
     253            0 :         goto cleanup;
     254              :       }
     255            0 :       cats[idx] = json_integer_value (val);
     256              :     }
     257              :   }
     258              : 
     259           22 :   if (NULL == pd.address)
     260            6 :     pd.address = json_object ();
     261           22 :   if (NULL == pd.description_i18n)
     262            4 :     pd.description_i18n = json_object ();
     263           22 :   if (NULL == pd.taxes)
     264            2 :     pd.taxes = json_array ();
     265              : 
     266              :   /* check taxes is well-formed */
     267           22 :   if (! TMH_taxes_array_valid (pd.taxes))
     268              :   {
     269            0 :     GNUNET_break_op (0);
     270            0 :     ret = TALER_MHD_reply_with_error (connection,
     271              :                                       MHD_HTTP_BAD_REQUEST,
     272              :                                       TALER_EC_GENERIC_PARAMETER_MALFORMED,
     273              :                                       "taxes");
     274            0 :     goto cleanup;
     275              :   }
     276              : 
     277           22 :   if (! TMH_location_object_valid (pd.address))
     278              :   {
     279            0 :     GNUNET_break_op (0);
     280            0 :     ret = TALER_MHD_reply_with_error (connection,
     281              :                                       MHD_HTTP_BAD_REQUEST,
     282              :                                       TALER_EC_GENERIC_PARAMETER_MALFORMED,
     283              :                                       "address");
     284            0 :     goto cleanup;
     285              :   }
     286              : 
     287           22 :   if (! TALER_JSON_check_i18n (pd.description_i18n))
     288              :   {
     289            0 :     GNUNET_break_op (0);
     290            0 :     ret = TALER_MHD_reply_with_error (connection,
     291              :                                       MHD_HTTP_BAD_REQUEST,
     292              :                                       TALER_EC_GENERIC_PARAMETER_MALFORMED,
     293              :                                       "description_i18n");
     294            0 :     goto cleanup;
     295              :   }
     296              : 
     297           22 :   if (NULL == pd.image)
     298            0 :     pd.image = (char *) "";
     299           22 :   if (! TMH_image_data_url_valid (pd.image))
     300              :   {
     301            0 :     GNUNET_break_op (0);
     302            0 :     ret = TALER_MHD_reply_with_error (connection,
     303              :                                       MHD_HTTP_BAD_REQUEST,
     304              :                                       TALER_EC_GENERIC_PARAMETER_MALFORMED,
     305              :                                       "image");
     306            0 :     goto cleanup;
     307              :   }
     308              : 
     309           22 :   qs = TMH_db->insert_product (TMH_db->cls,
     310           22 :                                mi->settings.id,
     311              :                                product_id,
     312              :                                &pd,
     313              :                                num_cats,
     314              :                                cats,
     315              :                                &no_instance,
     316              :                                &conflict,
     317              :                                &no_cat);
     318           22 :   switch (qs)
     319              :   {
     320            0 :   case GNUNET_DB_STATUS_HARD_ERROR:
     321              :   case GNUNET_DB_STATUS_SOFT_ERROR:
     322            0 :     ret = TALER_MHD_reply_with_error (
     323              :       connection,
     324              :       MHD_HTTP_INTERNAL_SERVER_ERROR,
     325              :       (GNUNET_DB_STATUS_SOFT_ERROR == qs)
     326              :       ? TALER_EC_GENERIC_DB_SOFT_FAILURE
     327              :       : TALER_EC_GENERIC_DB_COMMIT_FAILED,
     328              :       NULL);
     329            0 :     goto cleanup;
     330            0 :   case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
     331            0 :     ret = TALER_MHD_reply_with_error (
     332              :       connection,
     333              :       MHD_HTTP_INTERNAL_SERVER_ERROR,
     334              :       TALER_EC_GENERIC_DB_INVARIANT_FAILURE,
     335              :       NULL);
     336            0 :     goto cleanup;
     337           22 :   case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
     338           22 :     break;
     339              :   }
     340           22 :   if (no_instance)
     341              :   {
     342            0 :     ret = TALER_MHD_reply_with_error (
     343              :       connection,
     344              :       MHD_HTTP_NOT_FOUND,
     345              :       TALER_EC_MERCHANT_GENERIC_INSTANCE_UNKNOWN,
     346            0 :       mi->settings.id);
     347            0 :     goto cleanup;
     348              :   }
     349           22 :   if (conflict)
     350              :   {
     351            2 :     ret = TALER_MHD_reply_with_error (
     352              :       connection,
     353              :       MHD_HTTP_CONFLICT,
     354              :       TALER_EC_MERCHANT_PRIVATE_POST_PRODUCTS_CONFLICT_PRODUCT_EXISTS,
     355              :       product_id);
     356            2 :     goto cleanup;
     357              :   }
     358           20 :   if (-1 != no_cat)
     359              :   {
     360              :     char nocats[24];
     361              : 
     362            0 :     GNUNET_break_op (0);
     363            0 :     TMH_db->rollback (TMH_db->cls);
     364            0 :     GNUNET_snprintf (nocats,
     365              :                      sizeof (nocats),
     366              :                      "%llu",
     367              :                      (unsigned long long) no_cat);
     368            0 :     ret = TALER_MHD_reply_with_error (
     369              :       connection,
     370              :       MHD_HTTP_NOT_FOUND,
     371              :       TALER_EC_MERCHANT_GENERIC_CATEGORY_UNKNOWN,
     372              :       nocats);
     373            0 :     goto cleanup;
     374              :   }
     375           20 :   ret = TALER_MHD_reply_static (connection,
     376              :                                 MHD_HTTP_NO_CONTENT,
     377              :                                 NULL,
     378              :                                 NULL,
     379              :                                 0);
     380           22 : cleanup:
     381           22 :   GNUNET_JSON_parse_free (spec);
     382           22 :   GNUNET_free (pd.price_array);
     383           22 :   GNUNET_free (cats);
     384           22 :   return ret;
     385              : }
     386              : 
     387              : 
     388              : /* end of taler-merchant-httpd_private-post-products.c */
        

Generated by: LCOV version 2.0-1