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

Generated by: LCOV version 2.0-1