taler-merchant-httpd_private-post-products.c (12791B)
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 TMH_private_post_products (const struct TMH_RequestHandler *rh, 32 struct MHD_Connection *connection, 33 struct TMH_HandlerContext *hc) 34 { 35 struct TMH_MerchantInstance *mi = hc->instance; 36 struct TALER_MERCHANTDB_ProductDetails pd = { 0 }; 37 const json_t *categories = NULL; 38 const char *product_id; 39 int64_t total_stock; 40 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 GNUNET_JSON_spec_string ("product_id", 51 &product_id), 52 /* new in protocol v20, thus optional for backwards-compatibility */ 53 GNUNET_JSON_spec_mark_optional ( 54 GNUNET_JSON_spec_string ("product_name", 55 (const char **) &pd.product_name), 56 NULL), 57 GNUNET_JSON_spec_string ("description", 58 (const char **) &pd.description), 59 GNUNET_JSON_spec_mark_optional ( 60 GNUNET_JSON_spec_json ("description_i18n", 61 &pd.description_i18n), 62 NULL), 63 GNUNET_JSON_spec_string ("unit", 64 (const char **) &pd.unit), 65 GNUNET_JSON_spec_mark_optional ( 66 TALER_JSON_spec_amount_any ("price", 67 &pd.price), 68 &price_missing), 69 GNUNET_JSON_spec_mark_optional ( 70 GNUNET_JSON_spec_string ("image", 71 (const char **) &pd.image), 72 NULL), 73 GNUNET_JSON_spec_mark_optional ( 74 GNUNET_JSON_spec_json ("taxes", 75 &pd.taxes), 76 NULL), 77 GNUNET_JSON_spec_mark_optional ( 78 GNUNET_JSON_spec_array_const ("categories", 79 &categories), 80 NULL), 81 GNUNET_JSON_spec_mark_optional ( 82 GNUNET_JSON_spec_string ("unit_total_stock", 83 &unit_total_stock), 84 &unit_total_stock_missing), 85 GNUNET_JSON_spec_mark_optional ( 86 GNUNET_JSON_spec_int64 ("total_stock", 87 &total_stock), 88 &total_stock_missing), 89 GNUNET_JSON_spec_mark_optional ( 90 GNUNET_JSON_spec_bool ("unit_allow_fraction", 91 &unit_allow_fraction), 92 &unit_allow_fraction_missing), 93 GNUNET_JSON_spec_mark_optional ( 94 GNUNET_JSON_spec_uint32 ("unit_precision_level", 95 &unit_precision_level), 96 &unit_precision_missing), 97 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 GNUNET_JSON_spec_mark_optional ( 103 GNUNET_JSON_spec_json ("address", 104 &pd.address), 105 NULL), 106 GNUNET_JSON_spec_mark_optional ( 107 GNUNET_JSON_spec_timestamp ("next_restock", 108 &pd.next_restock), 109 NULL), 110 GNUNET_JSON_spec_mark_optional ( 111 GNUNET_JSON_spec_uint32 ("minimum_age", 112 &pd.minimum_age), 113 NULL), 114 GNUNET_JSON_spec_end () 115 }; 116 size_t num_cats = 0; 117 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 GNUNET_assert (NULL != mi); 125 { 126 enum GNUNET_GenericReturnValue res; 127 128 res = TALER_MHD_parse_json_data (connection, 129 hc->request_body, 130 spec); 131 if (GNUNET_OK != res) 132 { 133 GNUNET_break_op (0); 134 return (GNUNET_NO == res) 135 ? MHD_YES 136 : 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 if (NULL == pd.product_name) 141 pd.product_name = pd.description; 142 143 if (! unit_price_missing) 144 { 145 if (! price_missing) 146 { 147 if (0 != TALER_amount_cmp (&pd.price, 148 &pd.price_array[0])) 149 { 150 ret = TALER_MHD_reply_with_error (connection, 151 MHD_HTTP_BAD_REQUEST, 152 TALER_EC_GENERIC_PARAMETER_MALFORMED, 153 "price,unit_price mismatch"); 154 goto cleanup; 155 } 156 } 157 else 158 { 159 pd.price = pd.price_array[0]; 160 price_missing = false; 161 } 162 } 163 else 164 { 165 if (price_missing) 166 { 167 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 goto cleanup; 172 } 173 pd.price_array = GNUNET_new_array (1, 174 struct TALER_Amount); 175 pd.price_array[0] = pd.price; 176 pd.price_array_length = 1; 177 } 178 } 179 if (! unit_precision_missing) 180 { 181 if (unit_precision_level > TMH_MAX_FRACTIONAL_PRECISION_LEVEL) 182 { 183 ret = TALER_MHD_reply_with_error (connection, 184 MHD_HTTP_BAD_REQUEST, 185 TALER_EC_GENERIC_PARAMETER_MALFORMED, 186 "unit_precision_level"); 187 goto cleanup; 188 } 189 } 190 { 191 bool default_allow_fractional; 192 uint32_t default_precision_level; 193 194 if (GNUNET_OK != 195 TMH_unit_defaults_for_instance (mi, 196 pd.unit, 197 &default_allow_fractional, 198 &default_precision_level)) 199 { 200 GNUNET_break (0); 201 ret = TALER_MHD_reply_with_error (connection, 202 MHD_HTTP_INTERNAL_SERVER_ERROR, 203 TALER_EC_GENERIC_DB_FETCH_FAILED, 204 "unit defaults"); 205 goto cleanup; 206 } 207 if (unit_allow_fraction_missing) 208 unit_allow_fraction = default_allow_fractional; 209 if (unit_precision_missing) 210 unit_precision_level = default_precision_level; 211 } 212 if (! unit_allow_fraction) 213 unit_precision_level = 0; 214 pd.fractional_precision_level = unit_precision_level; 215 { 216 const char *eparam; 217 if (GNUNET_OK != 218 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 ret = TALER_MHD_reply_with_error ( 229 connection, 230 MHD_HTTP_BAD_REQUEST, 231 TALER_EC_GENERIC_PARAMETER_MALFORMED, 232 eparam); 233 goto cleanup; 234 } 235 pd.allow_fractional_quantity = unit_allow_fraction; 236 } 237 num_cats = json_array_size (categories); 238 cats = GNUNET_new_array (num_cats, 239 uint64_t); 240 { 241 size_t idx; 242 json_t *val; 243 244 json_array_foreach (categories, idx, val) 245 { 246 if (! json_is_integer (val)) 247 { 248 GNUNET_break_op (0); 249 ret = TALER_MHD_reply_with_error (connection, 250 MHD_HTTP_BAD_REQUEST, 251 TALER_EC_GENERIC_PARAMETER_MALFORMED, 252 "categories"); 253 goto cleanup; 254 } 255 cats[idx] = json_integer_value (val); 256 } 257 } 258 259 if (NULL == pd.address) 260 pd.address = json_object (); 261 if (NULL == pd.description_i18n) 262 pd.description_i18n = json_object (); 263 if (NULL == pd.taxes) 264 pd.taxes = json_array (); 265 266 /* check taxes is well-formed */ 267 if (! TMH_taxes_array_valid (pd.taxes)) 268 { 269 GNUNET_break_op (0); 270 ret = TALER_MHD_reply_with_error (connection, 271 MHD_HTTP_BAD_REQUEST, 272 TALER_EC_GENERIC_PARAMETER_MALFORMED, 273 "taxes"); 274 goto cleanup; 275 } 276 277 if (! TMH_location_object_valid (pd.address)) 278 { 279 GNUNET_break_op (0); 280 ret = TALER_MHD_reply_with_error (connection, 281 MHD_HTTP_BAD_REQUEST, 282 TALER_EC_GENERIC_PARAMETER_MALFORMED, 283 "address"); 284 goto cleanup; 285 } 286 287 if (! TALER_JSON_check_i18n (pd.description_i18n)) 288 { 289 GNUNET_break_op (0); 290 ret = TALER_MHD_reply_with_error (connection, 291 MHD_HTTP_BAD_REQUEST, 292 TALER_EC_GENERIC_PARAMETER_MALFORMED, 293 "description_i18n"); 294 goto cleanup; 295 } 296 297 if (NULL == pd.image) 298 pd.image = (char *) ""; 299 if (! TMH_image_data_url_valid (pd.image)) 300 { 301 GNUNET_break_op (0); 302 ret = TALER_MHD_reply_with_error (connection, 303 MHD_HTTP_BAD_REQUEST, 304 TALER_EC_GENERIC_PARAMETER_MALFORMED, 305 "image"); 306 goto cleanup; 307 } 308 309 qs = TMH_db->insert_product (TMH_db->cls, 310 mi->settings.id, 311 product_id, 312 &pd, 313 num_cats, 314 cats, 315 &no_instance, 316 &conflict, 317 &no_cat); 318 switch (qs) 319 { 320 case GNUNET_DB_STATUS_HARD_ERROR: 321 case GNUNET_DB_STATUS_SOFT_ERROR: 322 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 goto cleanup; 330 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 331 ret = TALER_MHD_reply_with_error ( 332 connection, 333 MHD_HTTP_INTERNAL_SERVER_ERROR, 334 TALER_EC_GENERIC_DB_INVARIANT_FAILURE, 335 NULL); 336 goto cleanup; 337 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 338 break; 339 } 340 if (no_instance) 341 { 342 ret = TALER_MHD_reply_with_error ( 343 connection, 344 MHD_HTTP_NOT_FOUND, 345 TALER_EC_MERCHANT_GENERIC_INSTANCE_UNKNOWN, 346 mi->settings.id); 347 goto cleanup; 348 } 349 if (conflict) 350 { 351 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 goto cleanup; 357 } 358 if (-1 != no_cat) 359 { 360 char nocats[24]; 361 362 GNUNET_break_op (0); 363 TMH_db->rollback (TMH_db->cls); 364 GNUNET_snprintf (nocats, 365 sizeof (nocats), 366 "%llu", 367 (unsigned long long) no_cat); 368 ret = TALER_MHD_reply_with_error ( 369 connection, 370 MHD_HTTP_NOT_FOUND, 371 TALER_EC_MERCHANT_GENERIC_CATEGORY_UNKNOWN, 372 nocats); 373 goto cleanup; 374 } 375 ret = TALER_MHD_reply_static (connection, 376 MHD_HTTP_NO_CONTENT, 377 NULL, 378 NULL, 379 0); 380 cleanup: 381 GNUNET_JSON_parse_free (spec); 382 GNUNET_free (pd.price_array); 383 GNUNET_free (cats); 384 return ret; 385 } 386 387 388 /* end of taler-merchant-httpd_private-post-products.c */