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