Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2025-2026 Taler Systems SA
4 :
5 : TALER is free software; you can redistribute it and/or modify it under the
6 : terms of the GNU Lesser General Public License as published by the Free Software
7 : Foundation; either version 2.1, or (at your option) any later version.
8 :
9 : TALER is distributed in the hope that it will be useful, but WITHOUT ANY
10 : WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
11 : A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
12 :
13 : You should have received a copy of the GNU Lesser General Public License along with
14 : TALER; see the file COPYING.LGPL. If not, see
15 : <http://www.gnu.org/licenses/>
16 : */
17 : /**
18 : * @file merchant_api_get-private-statistics-amount-SLUG-new.c
19 : * @brief Implementation of the GET /private/statistics-amount/$SLUG request
20 : * @author Christian Grothoff
21 : */
22 : #include "taler/platform.h"
23 : #include <curl/curl.h>
24 : #include <jansson.h>
25 : #include <microhttpd.h> /* just for HTTP status codes */
26 : #include <gnunet/gnunet_util_lib.h>
27 : #include <gnunet/gnunet_curl_lib.h>
28 : #include <taler/merchant/get-private-statistics-amount-SLUG.h>
29 : #include "merchant_api_curl_defaults.h"
30 : #include <taler/taler_json_lib.h>
31 :
32 :
33 : /**
34 : * Maximum number of statistics entries we return.
35 : */
36 : #define MAX_STATISTICS 1024
37 :
38 :
39 : /**
40 : * Handle for a GET /private/statistics-amount/$SLUG operation.
41 : */
42 : struct TALER_MERCHANT_GetPrivateStatisticsAmountHandle
43 : {
44 : /**
45 : * Base URL of the merchant backend.
46 : */
47 : char *base_url;
48 :
49 : /**
50 : * The full URL for this request.
51 : */
52 : char *url;
53 :
54 : /**
55 : * Handle for the request.
56 : */
57 : struct GNUNET_CURL_Job *job;
58 :
59 : /**
60 : * Function to call with the result.
61 : */
62 : TALER_MERCHANT_GetPrivateStatisticsAmountCallback cb;
63 :
64 : /**
65 : * Closure for @a cb.
66 : */
67 : TALER_MERCHANT_GET_PRIVATE_STATISTICS_AMOUNT_RESULT_CLOSURE *cb_cls;
68 :
69 : /**
70 : * Reference to the execution context.
71 : */
72 : struct GNUNET_CURL_Context *ctx;
73 :
74 : /**
75 : * Statistics slug.
76 : */
77 : char *slug;
78 :
79 : /**
80 : * Aggregation mode.
81 : */
82 : enum TALER_MERCHANT_StatisticsType stype;
83 :
84 : /**
85 : * Whether stype was explicitly set.
86 : */
87 : bool have_stype;
88 : };
89 :
90 :
91 : /**
92 : * Parse interval and bucket data from the JSON response.
93 : *
94 : * @param json overall JSON reply
95 : * @param jbuckets JSON array with bucket data
96 : * @param buckets_description human-readable description for buckets
97 : * @param jintervals JSON array with interval data
98 : * @param intervals_description human-readable description for intervals
99 : * @param sah operation handle
100 : * @return #GNUNET_OK on success
101 : */
102 : static enum GNUNET_GenericReturnValue
103 0 : parse_intervals_and_buckets_amt (
104 : const json_t *json,
105 : const json_t *jbuckets,
106 : const char *buckets_description,
107 : const json_t *jintervals,
108 : const char *intervals_description,
109 : struct TALER_MERCHANT_GetPrivateStatisticsAmountHandle *sah)
110 : {
111 0 : unsigned int resp_buckets_len = json_array_size (jbuckets);
112 0 : unsigned int resp_intervals_len = json_array_size (jintervals);
113 :
114 0 : if ( (json_array_size (jbuckets) != (size_t) resp_buckets_len) ||
115 0 : (json_array_size (jintervals) != (size_t) resp_intervals_len) ||
116 0 : (resp_buckets_len > MAX_STATISTICS) ||
117 : (resp_intervals_len > MAX_STATISTICS) )
118 : {
119 0 : GNUNET_break (0);
120 0 : return GNUNET_SYSERR;
121 : }
122 0 : {
123 0 : struct TALER_MERCHANT_GetPrivateStatisticsAmountByBucket resp_buckets[
124 0 : GNUNET_NZL (resp_buckets_len)];
125 0 : struct TALER_MERCHANT_GetPrivateStatisticsAmountByInterval resp_intervals[
126 0 : GNUNET_NZL (resp_intervals_len)];
127 : size_t index;
128 : json_t *value;
129 : enum GNUNET_GenericReturnValue ret;
130 :
131 0 : ret = GNUNET_OK;
132 0 : json_array_foreach (jintervals, index, value) {
133 0 : struct TALER_MERCHANT_GetPrivateStatisticsAmountByInterval *jinterval
134 : = &resp_intervals[index];
135 : const json_t *amounts_arr;
136 : size_t amounts_len;
137 : struct GNUNET_JSON_Specification spec[] = {
138 0 : GNUNET_JSON_spec_timestamp ("start_time",
139 : &jinterval->start_time),
140 0 : GNUNET_JSON_spec_array_const ("cumulative_amounts",
141 : &amounts_arr),
142 0 : GNUNET_JSON_spec_end ()
143 : };
144 :
145 0 : if (GNUNET_OK !=
146 0 : GNUNET_JSON_parse (value,
147 : spec,
148 : NULL, NULL))
149 : {
150 0 : GNUNET_break_op (0);
151 0 : return GNUNET_SYSERR;
152 : }
153 0 : amounts_len = json_array_size (amounts_arr);
154 0 : {
155 0 : struct TALER_Amount amt_arr[GNUNET_NZL (amounts_len)];
156 : size_t aindex;
157 : json_t *avalue;
158 :
159 0 : jinterval->cumulative_amount_len = amounts_len;
160 0 : jinterval->cumulative_amounts = amt_arr;
161 0 : json_array_foreach (amounts_arr, aindex, avalue) {
162 0 : if (! json_is_string (avalue))
163 : {
164 0 : GNUNET_break_op (0);
165 0 : return GNUNET_SYSERR;
166 : }
167 0 : if (GNUNET_OK !=
168 0 : TALER_string_to_amount (json_string_value (avalue),
169 : &amt_arr[aindex]))
170 : {
171 0 : GNUNET_break_op (0);
172 0 : return GNUNET_SYSERR;
173 : }
174 : }
175 : }
176 : }
177 0 : json_array_foreach (jbuckets, index, value) {
178 0 : struct TALER_MERCHANT_GetPrivateStatisticsAmountByBucket *jbucket
179 : = &resp_buckets[index];
180 : const json_t *amounts_arr;
181 : size_t amounts_len;
182 : struct GNUNET_JSON_Specification spec[] = {
183 0 : GNUNET_JSON_spec_timestamp ("start_time",
184 : &jbucket->start_time),
185 0 : GNUNET_JSON_spec_timestamp ("end_time",
186 : &jbucket->end_time),
187 0 : GNUNET_JSON_spec_string ("range",
188 : &jbucket->range),
189 0 : GNUNET_JSON_spec_array_const ("cumulative_amounts",
190 : &amounts_arr),
191 0 : GNUNET_JSON_spec_end ()
192 : };
193 :
194 0 : if (GNUNET_OK !=
195 0 : GNUNET_JSON_parse (value,
196 : spec,
197 : NULL, NULL))
198 : {
199 0 : GNUNET_break_op (0);
200 0 : ret = GNUNET_SYSERR;
201 0 : break;
202 : }
203 0 : amounts_len = json_array_size (amounts_arr);
204 0 : {
205 0 : struct TALER_Amount amt_arr[GNUNET_NZL (amounts_len)];
206 : size_t aindex;
207 : json_t *avalue;
208 :
209 0 : jbucket->cumulative_amount_len = amounts_len;
210 0 : jbucket->cumulative_amounts = amt_arr;
211 0 : json_array_foreach (amounts_arr, aindex, avalue) {
212 0 : if (! json_is_string (avalue))
213 : {
214 0 : GNUNET_break_op (0);
215 0 : return GNUNET_SYSERR;
216 : }
217 0 : if (GNUNET_OK !=
218 0 : TALER_string_to_amount (json_string_value (avalue),
219 : &amt_arr[aindex]))
220 : {
221 0 : GNUNET_break_op (0);
222 0 : return GNUNET_SYSERR;
223 : }
224 : }
225 : }
226 : }
227 0 : if (GNUNET_OK == ret)
228 : {
229 0 : struct TALER_MERCHANT_GetPrivateStatisticsAmountResponse sagr = {
230 : .hr.http_status = MHD_HTTP_OK,
231 : .hr.reply = json,
232 : .details.ok.buckets_length = resp_buckets_len,
233 : .details.ok.buckets = resp_buckets,
234 : .details.ok.buckets_description = buckets_description,
235 : .details.ok.intervals_length = resp_intervals_len,
236 : .details.ok.intervals = resp_intervals,
237 : .details.ok.intervals_description = intervals_description,
238 : };
239 0 : sah->cb (sah->cb_cls,
240 : &sagr);
241 0 : sah->cb = NULL;
242 : }
243 0 : return ret;
244 : }
245 : }
246 :
247 :
248 : /**
249 : * Function called when we're done processing the
250 : * HTTP GET /private/statistics-amount/$SLUG request.
251 : *
252 : * @param cls the `struct TALER_MERCHANT_GetPrivateStatisticsAmountHandle`
253 : * @param response_code HTTP response code, 0 on error
254 : * @param response response body, NULL if not in JSON
255 : */
256 : static void
257 0 : handle_get_statistics_amount_finished (void *cls,
258 : long response_code,
259 : const void *response)
260 : {
261 0 : struct TALER_MERCHANT_GetPrivateStatisticsAmountHandle *sah = cls;
262 0 : const json_t *json = response;
263 0 : struct TALER_MERCHANT_GetPrivateStatisticsAmountResponse sagr = {
264 0 : .hr.http_status = (unsigned int) response_code,
265 : .hr.reply = json
266 : };
267 :
268 0 : sah->job = NULL;
269 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
270 : "Got /private/statistics-amount/$SLUG response with status code %u\n",
271 : (unsigned int) response_code);
272 0 : switch (response_code)
273 : {
274 0 : case MHD_HTTP_OK:
275 : {
276 : const json_t *buckets;
277 : const json_t *intervals;
278 0 : const char *buckets_description = NULL;
279 0 : const char *intervals_description = NULL;
280 : struct GNUNET_JSON_Specification spec[] = {
281 0 : GNUNET_JSON_spec_array_const ("buckets",
282 : &buckets),
283 0 : GNUNET_JSON_spec_mark_optional (
284 : GNUNET_JSON_spec_string ("buckets_description",
285 : &buckets_description),
286 : NULL),
287 0 : GNUNET_JSON_spec_array_const ("intervals",
288 : &intervals),
289 0 : GNUNET_JSON_spec_mark_optional (
290 : GNUNET_JSON_spec_string ("intervals_description",
291 : &intervals_description),
292 : NULL),
293 0 : GNUNET_JSON_spec_end ()
294 : };
295 :
296 0 : if (GNUNET_OK !=
297 0 : GNUNET_JSON_parse (json,
298 : spec,
299 : NULL, NULL))
300 : {
301 0 : sagr.hr.http_status = 0;
302 0 : sagr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
303 0 : break;
304 : }
305 0 : if (GNUNET_OK ==
306 0 : parse_intervals_and_buckets_amt (json,
307 : buckets,
308 : buckets_description,
309 : intervals,
310 : intervals_description,
311 : sah))
312 : {
313 0 : TALER_MERCHANT_get_private_statistics_amount_cancel (sah);
314 0 : return;
315 : }
316 0 : sagr.hr.http_status = 0;
317 0 : sagr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
318 0 : break;
319 : }
320 0 : case MHD_HTTP_UNAUTHORIZED:
321 0 : sagr.hr.ec = TALER_JSON_get_error_code (json);
322 0 : sagr.hr.hint = TALER_JSON_get_error_hint (json);
323 0 : break;
324 0 : case MHD_HTTP_NOT_FOUND:
325 0 : sagr.hr.ec = TALER_JSON_get_error_code (json);
326 0 : sagr.hr.hint = TALER_JSON_get_error_hint (json);
327 0 : break;
328 0 : default:
329 0 : sagr.hr.ec = TALER_JSON_get_error_code (json);
330 0 : sagr.hr.hint = TALER_JSON_get_error_hint (json);
331 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
332 : "Unexpected response code %u/%d\n",
333 : (unsigned int) response_code,
334 : (int) sagr.hr.ec);
335 0 : break;
336 : }
337 0 : sah->cb (sah->cb_cls,
338 : &sagr);
339 0 : TALER_MERCHANT_get_private_statistics_amount_cancel (sah);
340 : }
341 :
342 :
343 : struct TALER_MERCHANT_GetPrivateStatisticsAmountHandle *
344 0 : TALER_MERCHANT_get_private_statistics_amount_create (
345 : struct GNUNET_CURL_Context *ctx,
346 : const char *url,
347 : const char *slug)
348 : {
349 : struct TALER_MERCHANT_GetPrivateStatisticsAmountHandle *sah;
350 :
351 0 : sah = GNUNET_new (struct TALER_MERCHANT_GetPrivateStatisticsAmountHandle);
352 0 : sah->ctx = ctx;
353 0 : sah->base_url = GNUNET_strdup (url);
354 0 : sah->slug = GNUNET_strdup (slug);
355 0 : sah->stype = TALER_MERCHANT_STATISTICS_ALL;
356 0 : return sah;
357 : }
358 :
359 :
360 : enum GNUNET_GenericReturnValue
361 0 : TALER_MERCHANT_get_private_statistics_amount_set_options_ (
362 : struct TALER_MERCHANT_GetPrivateStatisticsAmountHandle *sah,
363 : unsigned int num_options,
364 : const struct TALER_MERCHANT_GetPrivateStatisticsAmountOptionValue *options)
365 : {
366 0 : for (unsigned int i = 0; i < num_options; i++)
367 : {
368 0 : const struct TALER_MERCHANT_GetPrivateStatisticsAmountOptionValue *opt =
369 0 : &options[i];
370 :
371 0 : switch (opt->option)
372 : {
373 0 : case TALER_MERCHANT_GET_PRIVATE_STATISTICS_AMOUNT_OPTION_END:
374 0 : return GNUNET_OK;
375 0 : case TALER_MERCHANT_GET_PRIVATE_STATISTICS_AMOUNT_OPTION_TYPE:
376 0 : sah->stype = opt->details.type;
377 0 : sah->have_stype = true;
378 0 : break;
379 0 : default:
380 0 : GNUNET_break (0);
381 0 : return GNUNET_NO;
382 : }
383 : }
384 0 : return GNUNET_OK;
385 : }
386 :
387 :
388 : enum TALER_ErrorCode
389 0 : TALER_MERCHANT_get_private_statistics_amount_start (
390 : struct TALER_MERCHANT_GetPrivateStatisticsAmountHandle *sah,
391 : TALER_MERCHANT_GetPrivateStatisticsAmountCallback cb,
392 : TALER_MERCHANT_GET_PRIVATE_STATISTICS_AMOUNT_RESULT_CLOSURE *cb_cls)
393 : {
394 : CURL *eh;
395 :
396 0 : sah->cb = cb;
397 0 : sah->cb_cls = cb_cls;
398 : {
399 0 : const char *filter = NULL;
400 : char *path;
401 :
402 0 : switch (sah->stype)
403 : {
404 0 : case TALER_MERCHANT_STATISTICS_BY_BUCKET:
405 0 : filter = "bucket";
406 0 : break;
407 0 : case TALER_MERCHANT_STATISTICS_BY_INTERVAL:
408 0 : filter = "interval";
409 0 : break;
410 0 : case TALER_MERCHANT_STATISTICS_ALL:
411 0 : filter = NULL;
412 0 : break;
413 : }
414 0 : GNUNET_asprintf (&path,
415 : "private/statistics-amount/%s",
416 : sah->slug);
417 0 : sah->url = TALER_url_join (sah->base_url,
418 : path,
419 : "by",
420 : filter,
421 : NULL);
422 0 : GNUNET_free (path);
423 : }
424 0 : if (NULL == sah->url)
425 0 : return TALER_EC_GENERIC_CONFIGURATION_INVALID;
426 0 : eh = TALER_MERCHANT_curl_easy_get_ (sah->url);
427 0 : if (NULL == eh)
428 0 : return TALER_EC_GENERIC_CONFIGURATION_INVALID;
429 0 : sah->job = GNUNET_CURL_job_add (sah->ctx,
430 : eh,
431 : &handle_get_statistics_amount_finished,
432 : sah);
433 0 : if (NULL == sah->job)
434 0 : return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
435 0 : return TALER_EC_NONE;
436 : }
437 :
438 :
439 : void
440 0 : TALER_MERCHANT_get_private_statistics_amount_cancel (
441 : struct TALER_MERCHANT_GetPrivateStatisticsAmountHandle *sah)
442 : {
443 0 : if (NULL != sah->job)
444 : {
445 0 : GNUNET_CURL_job_cancel (sah->job);
446 0 : sah->job = NULL;
447 : }
448 0 : GNUNET_free (sah->url);
449 0 : GNUNET_free (sah->base_url);
450 0 : GNUNET_free (sah->slug);
451 0 : GNUNET_free (sah);
452 0 : }
453 :
454 :
455 : /* end of merchant_api_get-private-statistics-amount-SLUG-new.c */
|