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 */
|