Line data Source code
1 : /*
2 : This file is part of TALER
3 : (C) 2020 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 : * How often do we retry the simple INSERT database transaction?
33 : */
34 : #define MAX_RETRIES 3
35 :
36 :
37 : /**
38 : * Determine the cause of the PATCH failure in more detail and report.
39 : *
40 : * @param connection connection to report on
41 : * @param instance_id instance we are processing
42 : * @param product_id ID of the product to patch
43 : * @param pd product details we failed to set
44 : */
45 : static MHD_RESULT
46 0 : determine_cause (struct MHD_Connection *connection,
47 : const char *instance_id,
48 : const char *product_id,
49 : const struct TALER_MERCHANTDB_ProductDetails *pd)
50 : {
51 : struct TALER_MERCHANTDB_ProductDetails pdx;
52 : enum GNUNET_DB_QueryStatus qs;
53 :
54 0 : qs = TMH_db->lookup_product (TMH_db->cls,
55 : instance_id,
56 : product_id,
57 : &pdx);
58 0 : switch (qs)
59 : {
60 0 : case GNUNET_DB_STATUS_HARD_ERROR:
61 0 : GNUNET_break (0);
62 0 : return TALER_MHD_reply_with_error (connection,
63 : MHD_HTTP_INTERNAL_SERVER_ERROR,
64 : TALER_EC_GENERIC_DB_FETCH_FAILED,
65 : NULL);
66 0 : case GNUNET_DB_STATUS_SOFT_ERROR:
67 0 : GNUNET_break (0);
68 0 : return TALER_MHD_reply_with_error (connection,
69 : MHD_HTTP_INTERNAL_SERVER_ERROR,
70 : TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
71 : "unexpected serialization problem");
72 0 : case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
73 0 : return TALER_MHD_reply_with_error (connection,
74 : MHD_HTTP_NOT_FOUND,
75 : TALER_EC_MERCHANT_GENERIC_PRODUCT_UNKNOWN,
76 : product_id);
77 0 : case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
78 0 : break; /* do below */
79 : }
80 :
81 0 : {
82 : enum TALER_ErrorCode ec;
83 :
84 0 : ec = TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
85 0 : if (pdx.total_lost > pd->total_lost)
86 0 : ec = TALER_EC_MERCHANT_PRIVATE_PATCH_PRODUCTS_TOTAL_LOST_REDUCED;
87 0 : if (pdx.total_sold > pd->total_sold)
88 0 : ec = TALER_EC_MERCHANT_PRIVATE_PATCH_PRODUCTS_TOTAL_SOLD_REDUCED;
89 0 : if (pdx.total_stock > pd->total_stock)
90 0 : ec = TALER_EC_MERCHANT_PRIVATE_PATCH_PRODUCTS_TOTAL_STOCKED_REDUCED;
91 0 : TALER_MERCHANTDB_product_details_free (&pdx);
92 0 : GNUNET_break (TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE != ec);
93 0 : return TALER_MHD_reply_with_error (connection,
94 : MHD_HTTP_CONFLICT,
95 : ec,
96 : NULL);
97 : }
98 : }
99 :
100 :
101 : /**
102 : * PATCH configuration of an existing instance, given its configuration.
103 : *
104 : * @param rh context of the handler
105 : * @param connection the MHD connection to handle
106 : * @param[in,out] hc context with further information about the request
107 : * @return MHD result code
108 : */
109 : MHD_RESULT
110 0 : TMH_private_patch_products_ID (const struct TMH_RequestHandler *rh,
111 : struct MHD_Connection *connection,
112 : struct TMH_HandlerContext *hc)
113 : {
114 0 : struct TMH_MerchantInstance *mi = hc->instance;
115 0 : const char *product_id = hc->infix;
116 0 : struct TALER_MERCHANTDB_ProductDetails pd = {0};
117 : int64_t total_stock;
118 : enum GNUNET_DB_QueryStatus qs;
119 : struct GNUNET_JSON_Specification spec[] = {
120 0 : GNUNET_JSON_spec_string ("description",
121 : (const char **) &pd.description),
122 0 : GNUNET_JSON_spec_mark_optional (
123 : GNUNET_JSON_spec_json ("description_i18n",
124 : &pd.description_i18n),
125 : NULL),
126 0 : GNUNET_JSON_spec_string ("unit",
127 : (const char **) &pd.unit),
128 0 : TALER_JSON_spec_amount ("price",
129 : TMH_currency,
130 : &pd.price),
131 0 : GNUNET_JSON_spec_mark_optional (
132 : GNUNET_JSON_spec_string ("image",
133 : (const char **) &pd.image),
134 : NULL),
135 0 : GNUNET_JSON_spec_mark_optional (
136 : GNUNET_JSON_spec_json ("taxes",
137 : &pd.taxes),
138 : NULL),
139 0 : GNUNET_JSON_spec_int64 ("total_stock",
140 : &total_stock),
141 0 : GNUNET_JSON_spec_mark_optional (
142 : GNUNET_JSON_spec_uint64 ("total_lost",
143 : &pd.total_lost),
144 : NULL),
145 0 : GNUNET_JSON_spec_mark_optional (
146 : GNUNET_JSON_spec_json ("address",
147 : &pd.address),
148 : NULL),
149 0 : GNUNET_JSON_spec_mark_optional (
150 : GNUNET_JSON_spec_timestamp ("next_restock",
151 : &pd.next_restock),
152 : NULL),
153 0 : GNUNET_JSON_spec_mark_optional (
154 : GNUNET_JSON_spec_uint32 ("minimum_age",
155 : &pd.minimum_age),
156 : NULL),
157 0 : GNUNET_JSON_spec_end ()
158 : };
159 :
160 0 : pd.total_sold = 0; /* will be ignored anyway */
161 0 : GNUNET_assert (NULL != mi);
162 0 : GNUNET_assert (NULL != product_id);
163 : {
164 : enum GNUNET_GenericReturnValue res;
165 :
166 0 : res = TALER_MHD_parse_json_data (connection,
167 0 : hc->request_body,
168 : spec);
169 0 : if (GNUNET_OK != res)
170 : return (GNUNET_NO == res)
171 : ? MHD_YES
172 0 : : MHD_NO;
173 : }
174 0 : if (total_stock < -1)
175 : {
176 0 : GNUNET_break_op (0);
177 0 : GNUNET_JSON_parse_free (spec);
178 0 : return TALER_MHD_reply_with_error (connection,
179 : MHD_HTTP_BAD_REQUEST,
180 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
181 : "total_stock");
182 : }
183 0 : if (-1 == total_stock)
184 0 : pd.total_stock = INT64_MAX;
185 : else
186 0 : pd.total_stock = (uint64_t) total_stock;
187 0 : if (NULL == pd.address)
188 0 : pd.address = json_object ();
189 :
190 0 : if (! TMH_location_object_valid (pd.address))
191 : {
192 0 : GNUNET_break_op (0);
193 0 : GNUNET_JSON_parse_free (spec);
194 0 : return TALER_MHD_reply_with_error (connection,
195 : MHD_HTTP_BAD_REQUEST,
196 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
197 : "address");
198 : }
199 0 : if (NULL == pd.description_i18n)
200 0 : pd.description_i18n = json_object ();
201 :
202 0 : if (! TALER_JSON_check_i18n (pd.description_i18n))
203 : {
204 0 : GNUNET_break_op (0);
205 0 : GNUNET_JSON_parse_free (spec);
206 0 : return TALER_MHD_reply_with_error (connection,
207 : MHD_HTTP_BAD_REQUEST,
208 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
209 : "description_i18n");
210 : }
211 :
212 0 : if (NULL == pd.taxes)
213 0 : pd.taxes = json_array ();
214 : /* check taxes is well-formed */
215 0 : if (! TMH_taxes_array_valid (pd.taxes))
216 : {
217 0 : GNUNET_break_op (0);
218 0 : GNUNET_JSON_parse_free (spec);
219 0 : return TALER_MHD_reply_with_error (connection,
220 : MHD_HTTP_BAD_REQUEST,
221 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
222 : "taxes");
223 : }
224 0 : if (NULL == pd.image)
225 0 : pd.image = "";
226 0 : if (! TMH_image_data_url_valid (pd.image))
227 : {
228 0 : GNUNET_break_op (0);
229 0 : GNUNET_JSON_parse_free (spec);
230 0 : return TALER_MHD_reply_with_error (connection,
231 : MHD_HTTP_BAD_REQUEST,
232 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
233 : "image");
234 : }
235 0 : if ( (pd.total_stock < pd.total_sold + pd.total_lost) ||
236 0 : (pd.total_sold + pd.total_lost < pd.total_sold) /* integer overflow */)
237 : {
238 0 : GNUNET_break_op (0);
239 0 : GNUNET_JSON_parse_free (spec);
240 0 : return TALER_MHD_reply_with_error (
241 : connection,
242 : MHD_HTTP_BAD_REQUEST,
243 : TALER_EC_MERCHANT_PRIVATE_PATCH_PRODUCTS_TOTAL_LOST_EXCEEDS_STOCKS,
244 : NULL);
245 : }
246 0 : qs = TMH_db->update_product (TMH_db->cls,
247 0 : mi->settings.id,
248 : product_id,
249 : &pd);
250 : {
251 0 : MHD_RESULT ret = MHD_NO;
252 :
253 0 : switch (qs)
254 : {
255 0 : case GNUNET_DB_STATUS_HARD_ERROR:
256 0 : GNUNET_break (0);
257 0 : ret = TALER_MHD_reply_with_error (connection,
258 : MHD_HTTP_INTERNAL_SERVER_ERROR,
259 : TALER_EC_GENERIC_DB_STORE_FAILED,
260 : NULL);
261 0 : break;
262 0 : case GNUNET_DB_STATUS_SOFT_ERROR:
263 0 : GNUNET_break (0);
264 0 : ret = TALER_MHD_reply_with_error (connection,
265 : MHD_HTTP_INTERNAL_SERVER_ERROR,
266 : TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
267 : "unexpected serialization problem");
268 0 : break;
269 0 : case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
270 0 : ret = determine_cause (connection,
271 0 : mi->settings.id,
272 : product_id,
273 : &pd);
274 0 : break;
275 0 : case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
276 0 : ret = TALER_MHD_reply_static (connection,
277 : MHD_HTTP_NO_CONTENT,
278 : NULL,
279 : NULL,
280 : 0);
281 0 : break;
282 : }
283 0 : GNUNET_JSON_parse_free (spec);
284 0 : return ret;
285 : }
286 : }
287 :
288 :
289 : /* end of taler-merchant-httpd_private-patch-products-ID.c */
|