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 :
31 : MHD_RESULT
32 16 : TMH_private_post_products (const struct TMH_RequestHandler *rh,
33 : struct MHD_Connection *connection,
34 : struct TMH_HandlerContext *hc)
35 : {
36 16 : struct TMH_MerchantInstance *mi = hc->instance;
37 16 : struct TALER_MERCHANTDB_ProductDetails pd = { 0 };
38 16 : const json_t *categories = NULL;
39 : const char *product_id;
40 : int64_t total_stock;
41 : struct GNUNET_JSON_Specification spec[] = {
42 16 : GNUNET_JSON_spec_string ("product_id",
43 : &product_id),
44 : /* new in protocol v20, thus optional for backwards-compatibility */
45 16 : GNUNET_JSON_spec_mark_optional (
46 : GNUNET_JSON_spec_string ("product_name",
47 : (const char **) &pd.product_name),
48 : NULL),
49 16 : GNUNET_JSON_spec_string ("description",
50 : (const char **) &pd.description),
51 16 : GNUNET_JSON_spec_mark_optional (
52 : GNUNET_JSON_spec_json ("description_i18n",
53 : &pd.description_i18n),
54 : NULL),
55 16 : GNUNET_JSON_spec_string ("unit",
56 : (const char **) &pd.unit),
57 16 : TALER_JSON_spec_amount_any ("price",
58 : &pd.price),
59 16 : GNUNET_JSON_spec_mark_optional (
60 : GNUNET_JSON_spec_string ("image",
61 : (const char **) &pd.image),
62 : NULL),
63 16 : GNUNET_JSON_spec_mark_optional (
64 : GNUNET_JSON_spec_json ("taxes",
65 : &pd.taxes),
66 : NULL),
67 16 : GNUNET_JSON_spec_mark_optional (
68 : GNUNET_JSON_spec_array_const ("categories",
69 : &categories),
70 : NULL),
71 16 : GNUNET_JSON_spec_int64 ("total_stock",
72 : &total_stock),
73 16 : GNUNET_JSON_spec_mark_optional (
74 : GNUNET_JSON_spec_json ("address",
75 : &pd.address),
76 : NULL),
77 16 : GNUNET_JSON_spec_mark_optional (
78 : GNUNET_JSON_spec_timestamp ("next_restock",
79 : &pd.next_restock),
80 : NULL),
81 16 : GNUNET_JSON_spec_mark_optional (
82 : GNUNET_JSON_spec_uint32 ("minimum_age",
83 : &pd.minimum_age),
84 : NULL),
85 16 : GNUNET_JSON_spec_end ()
86 : };
87 16 : size_t num_cats = 0;
88 16 : uint64_t *cats = NULL;
89 : bool conflict;
90 : bool no_instance;
91 : ssize_t no_cat;
92 : enum GNUNET_DB_QueryStatus qs;
93 : MHD_RESULT ret;
94 :
95 16 : GNUNET_assert (NULL != mi);
96 : {
97 : enum GNUNET_GenericReturnValue res;
98 :
99 16 : res = TALER_MHD_parse_json_data (connection,
100 16 : hc->request_body,
101 : spec);
102 16 : if (GNUNET_OK != res)
103 : {
104 0 : GNUNET_break_op (0);
105 : return (GNUNET_NO == res)
106 : ? MHD_YES
107 0 : : MHD_NO;
108 : }
109 : /* For pre-v20 clients, we use the description given as the
110 : product name; remove once we make product_name mandatory. */
111 16 : if (NULL == pd.product_name)
112 2 : pd.product_name = pd.description;
113 : }
114 16 : if (total_stock < -1)
115 : {
116 0 : GNUNET_break_op (0);
117 0 : ret = TALER_MHD_reply_with_error (connection,
118 : MHD_HTTP_BAD_REQUEST,
119 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
120 : "total_stock");
121 0 : goto cleanup;
122 : }
123 16 : num_cats = json_array_size (categories);
124 16 : cats = GNUNET_new_array (num_cats,
125 : uint64_t);
126 : {
127 : size_t idx;
128 : json_t *val;
129 :
130 16 : json_array_foreach (categories, idx, val)
131 : {
132 0 : if (! json_is_integer (val))
133 : {
134 0 : GNUNET_break_op (0);
135 0 : ret = TALER_MHD_reply_with_error (connection,
136 : MHD_HTTP_BAD_REQUEST,
137 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
138 : "categories");
139 0 : goto cleanup;
140 : }
141 0 : cats[idx] = json_integer_value (val);
142 : }
143 : }
144 :
145 16 : if (-1 == total_stock)
146 1 : pd.total_stock = INT64_MAX;
147 : else
148 15 : pd.total_stock = (uint64_t) total_stock;
149 :
150 16 : if (NULL == pd.address)
151 4 : pd.address = json_object ();
152 16 : if (NULL == pd.description_i18n)
153 4 : pd.description_i18n = json_object ();
154 16 : if (NULL == pd.taxes)
155 2 : pd.taxes = json_array ();
156 :
157 : /* check taxes is well-formed */
158 16 : if (! TMH_taxes_array_valid (pd.taxes))
159 : {
160 0 : GNUNET_break_op (0);
161 0 : ret = TALER_MHD_reply_with_error (connection,
162 : MHD_HTTP_BAD_REQUEST,
163 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
164 : "taxes");
165 0 : goto cleanup;
166 : }
167 :
168 16 : if (! TMH_location_object_valid (pd.address))
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 : "address");
175 0 : goto cleanup;
176 : }
177 :
178 16 : if (! TALER_JSON_check_i18n (pd.description_i18n))
179 : {
180 0 : GNUNET_break_op (0);
181 0 : ret = TALER_MHD_reply_with_error (connection,
182 : MHD_HTTP_BAD_REQUEST,
183 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
184 : "description_i18n");
185 0 : goto cleanup;
186 : }
187 :
188 16 : if (NULL == pd.image)
189 0 : pd.image = (char *) "";
190 16 : if (! TMH_image_data_url_valid (pd.image))
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 : "image");
197 0 : goto cleanup;
198 : }
199 :
200 16 : qs = TMH_db->insert_product (TMH_db->cls,
201 16 : mi->settings.id,
202 : product_id,
203 : &pd,
204 : num_cats,
205 : cats,
206 : &no_instance,
207 : &conflict,
208 : &no_cat);
209 16 : switch (qs)
210 : {
211 0 : case GNUNET_DB_STATUS_HARD_ERROR:
212 : case GNUNET_DB_STATUS_SOFT_ERROR:
213 0 : ret = TALER_MHD_reply_with_error (
214 : connection,
215 : MHD_HTTP_INTERNAL_SERVER_ERROR,
216 : (GNUNET_DB_STATUS_SOFT_ERROR == qs)
217 : ? TALER_EC_GENERIC_DB_SOFT_FAILURE
218 : : TALER_EC_GENERIC_DB_COMMIT_FAILED,
219 : NULL);
220 0 : goto cleanup;
221 0 : case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
222 0 : ret = TALER_MHD_reply_with_error (
223 : connection,
224 : MHD_HTTP_INTERNAL_SERVER_ERROR,
225 : TALER_EC_GENERIC_DB_INVARIANT_FAILURE,
226 : NULL);
227 0 : goto cleanup;
228 16 : case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
229 16 : break;
230 : }
231 16 : if (no_instance)
232 : {
233 0 : ret = TALER_MHD_reply_with_error (
234 : connection,
235 : MHD_HTTP_NOT_FOUND,
236 : TALER_EC_MERCHANT_GENERIC_INSTANCE_UNKNOWN,
237 0 : mi->settings.id);
238 0 : goto cleanup;
239 : }
240 16 : if (conflict)
241 : {
242 2 : ret = TALER_MHD_reply_with_error (
243 : connection,
244 : MHD_HTTP_CONFLICT,
245 : TALER_EC_MERCHANT_PRIVATE_POST_PRODUCTS_CONFLICT_PRODUCT_EXISTS,
246 : product_id);
247 2 : goto cleanup;
248 : }
249 14 : if (-1 != no_cat)
250 : {
251 : char nocats[24];
252 :
253 0 : GNUNET_break_op (0);
254 0 : TMH_db->rollback (TMH_db->cls);
255 0 : GNUNET_snprintf (nocats,
256 : sizeof (nocats),
257 : "%llu",
258 : (unsigned long long) no_cat);
259 0 : ret = TALER_MHD_reply_with_error (
260 : connection,
261 : MHD_HTTP_NOT_FOUND,
262 : TALER_EC_MERCHANT_GENERIC_CATEGORY_UNKNOWN,
263 : nocats);
264 0 : goto cleanup;
265 : }
266 14 : ret = TALER_MHD_reply_static (connection,
267 : MHD_HTTP_NO_CONTENT,
268 : NULL,
269 : NULL,
270 : 0);
271 16 : cleanup:
272 16 : GNUNET_JSON_parse_free (spec);
273 16 : GNUNET_free (cats);
274 16 : return ret;
275 : }
276 :
277 :
278 : /* end of taler-merchant-httpd_private-post-products.c */
|