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-counter-SLUG-new.c
19 : * @brief Implementation of the GET /private/statistics-counter/$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-counter-SLUG.h>
29 : #include "merchant_api_curl_defaults.h"
30 : #include <taler/taler_json_lib.h>
31 :
32 : /**
33 : * Maximum number of statistics we return.
34 : */
35 : #define MAX_STATISTICS 1024
36 :
37 :
38 : /**
39 : * Handle for a GET /private/statistics-counter/$SLUG operation.
40 : */
41 : struct TALER_MERCHANT_GetPrivateStatisticsCounterHandle
42 : {
43 : /**
44 : * Base URL of the merchant backend.
45 : */
46 : char *base_url;
47 :
48 : /**
49 : * The full URL for this request.
50 : */
51 : char *url;
52 :
53 : /**
54 : * Handle for the request.
55 : */
56 : struct GNUNET_CURL_Job *job;
57 :
58 : /**
59 : * Function to call with the result.
60 : */
61 : TALER_MERCHANT_GetPrivateStatisticsCounterCallback cb;
62 :
63 : /**
64 : * Closure for @a cb.
65 : */
66 : TALER_MERCHANT_GET_PRIVATE_STATISTICS_COUNTER_RESULT_CLOSURE *cb_cls;
67 :
68 : /**
69 : * Reference to the execution context.
70 : */
71 : struct GNUNET_CURL_Context *ctx;
72 :
73 : /**
74 : * Statistics slug to retrieve.
75 : */
76 : char *slug;
77 :
78 : /**
79 : * Aggregation filter type, or -1 if not set.
80 : */
81 : enum TALER_MERCHANT_StatisticsType stype;
82 :
83 : /**
84 : * Whether the type option was set.
85 : */
86 : bool type_set;
87 : };
88 :
89 :
90 : /**
91 : * Parse interval and bucket information from the JSON response.
92 : *
93 : * @param json overall JSON reply
94 : * @param jbuckets JSON array with bucket data
95 : * @param buckets_description human-readable description for the buckets
96 : * @param jintervals JSON array with interval data
97 : * @param intervals_description human-readable description for the intervals
98 : * @param sch operation handle
99 : * @return #GNUNET_OK on success
100 : */
101 : static enum GNUNET_GenericReturnValue
102 0 : parse_intervals_and_buckets (
103 : const json_t *json,
104 : const json_t *jbuckets,
105 : const char *buckets_description,
106 : const json_t *jintervals,
107 : const char *intervals_description,
108 : struct TALER_MERCHANT_GetPrivateStatisticsCounterHandle *sch)
109 : {
110 0 : unsigned int resp_buckets_len = (unsigned int) json_array_size (jbuckets);
111 0 : unsigned int resp_intervals_len = (unsigned int) json_array_size (jintervals);
112 :
113 0 : if ( (json_array_size (jbuckets) != (size_t) resp_buckets_len) ||
114 0 : (json_array_size (jintervals) != (size_t) resp_intervals_len) ||
115 0 : (resp_buckets_len > MAX_STATISTICS) ||
116 : (resp_intervals_len > MAX_STATISTICS) )
117 : {
118 0 : GNUNET_break (0);
119 0 : return GNUNET_SYSERR;
120 : }
121 0 : {
122 0 : struct TALER_MERCHANT_GetPrivateStatisticsCounterByBucket resp_buckets[
123 0 : GNUNET_NZL (resp_buckets_len)];
124 0 : struct TALER_MERCHANT_GetPrivateStatisticsCounterByInterval resp_intervals[
125 0 : GNUNET_NZL (resp_intervals_len)];
126 : size_t index;
127 : json_t *value;
128 :
129 0 : json_array_foreach (jintervals, index, value) {
130 0 : struct TALER_MERCHANT_GetPrivateStatisticsCounterByInterval *ji
131 : = &resp_intervals[index];
132 : struct GNUNET_JSON_Specification spec[] = {
133 0 : GNUNET_JSON_spec_timestamp ("start_time",
134 : &ji->start_time),
135 0 : GNUNET_JSON_spec_uint64 ("cumulative_counter",
136 : &ji->cumulative_counter),
137 0 : GNUNET_JSON_spec_end ()
138 : };
139 :
140 0 : if (GNUNET_OK !=
141 0 : GNUNET_JSON_parse (value,
142 : spec,
143 : NULL, NULL))
144 : {
145 0 : GNUNET_break_op (0);
146 0 : return GNUNET_SYSERR;
147 : }
148 : }
149 0 : json_array_foreach (jbuckets, index, value) {
150 0 : struct TALER_MERCHANT_GetPrivateStatisticsCounterByBucket *jb
151 : = &resp_buckets[index];
152 : struct GNUNET_JSON_Specification spec[] = {
153 0 : GNUNET_JSON_spec_timestamp ("start_time",
154 : &jb->start_time),
155 0 : GNUNET_JSON_spec_timestamp ("end_time",
156 : &jb->end_time),
157 0 : GNUNET_JSON_spec_string ("range",
158 : &jb->range),
159 0 : GNUNET_JSON_spec_uint64 ("cumulative_counter",
160 : &jb->cumulative_counter),
161 0 : GNUNET_JSON_spec_end ()
162 : };
163 :
164 0 : if (GNUNET_OK !=
165 0 : GNUNET_JSON_parse (value,
166 : spec,
167 : NULL, NULL))
168 : {
169 0 : GNUNET_break_op (0);
170 0 : return GNUNET_SYSERR;
171 : }
172 : }
173 : {
174 0 : struct TALER_MERCHANT_GetPrivateStatisticsCounterResponse scgr = {
175 : .hr.http_status = MHD_HTTP_OK,
176 : .hr.reply = json,
177 : .details.ok.buckets_length = resp_buckets_len,
178 : .details.ok.buckets = resp_buckets,
179 : .details.ok.buckets_description = buckets_description,
180 : .details.ok.intervals_length = resp_intervals_len,
181 : .details.ok.intervals = resp_intervals,
182 : .details.ok.intervals_description = intervals_description,
183 : };
184 0 : sch->cb (sch->cb_cls,
185 : &scgr);
186 0 : sch->cb = NULL;
187 : }
188 0 : return GNUNET_OK;
189 : }
190 : }
191 :
192 :
193 : /**
194 : * Function called when we're done processing the
195 : * HTTP GET /private/statistics-counter/$SLUG request.
196 : *
197 : * @param cls the `struct TALER_MERCHANT_GetPrivateStatisticsCounterHandle`
198 : * @param response_code HTTP response code, 0 on error
199 : * @param response response body, NULL if not in JSON
200 : */
201 : static void
202 0 : handle_get_statistics_counter_finished (void *cls,
203 : long response_code,
204 : const void *response)
205 : {
206 0 : struct TALER_MERCHANT_GetPrivateStatisticsCounterHandle *sch = cls;
207 0 : const json_t *json = response;
208 0 : struct TALER_MERCHANT_GetPrivateStatisticsCounterResponse res = {
209 0 : .hr.http_status = (unsigned int) response_code,
210 : .hr.reply = json
211 : };
212 :
213 0 : sch->job = NULL;
214 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
215 : "Got /private/statistics-counter/$SLUG response with status code %u\n",
216 : (unsigned int) response_code);
217 0 : switch (response_code)
218 : {
219 0 : case MHD_HTTP_OK:
220 : {
221 : const json_t *buckets;
222 : const json_t *intervals;
223 0 : const char *buckets_description = NULL;
224 0 : const char *intervals_description = NULL;
225 : struct GNUNET_JSON_Specification spec[] = {
226 0 : GNUNET_JSON_spec_array_const ("buckets",
227 : &buckets),
228 0 : GNUNET_JSON_spec_mark_optional (
229 : GNUNET_JSON_spec_string ("buckets_description",
230 : &buckets_description),
231 : NULL),
232 0 : GNUNET_JSON_spec_array_const ("intervals",
233 : &intervals),
234 0 : GNUNET_JSON_spec_mark_optional (
235 : GNUNET_JSON_spec_string ("intervals_description",
236 : &intervals_description),
237 : NULL),
238 0 : GNUNET_JSON_spec_end ()
239 : };
240 :
241 0 : if (GNUNET_OK !=
242 0 : GNUNET_JSON_parse (json,
243 : spec,
244 : NULL, NULL))
245 : {
246 0 : res.hr.http_status = 0;
247 0 : res.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
248 0 : break;
249 : }
250 0 : if (GNUNET_OK ==
251 0 : parse_intervals_and_buckets (json,
252 : buckets,
253 : buckets_description,
254 : intervals,
255 : intervals_description,
256 : sch))
257 : {
258 0 : TALER_MERCHANT_get_private_statistics_counter_cancel (sch);
259 0 : return;
260 : }
261 0 : res.hr.http_status = 0;
262 0 : res.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
263 0 : break;
264 : }
265 0 : case MHD_HTTP_UNAUTHORIZED:
266 0 : res.hr.ec = TALER_JSON_get_error_code (json);
267 0 : res.hr.hint = TALER_JSON_get_error_hint (json);
268 0 : break;
269 0 : case MHD_HTTP_NOT_FOUND:
270 0 : res.hr.ec = TALER_JSON_get_error_code (json);
271 0 : res.hr.hint = TALER_JSON_get_error_hint (json);
272 0 : break;
273 0 : default:
274 0 : res.hr.ec = TALER_JSON_get_error_code (json);
275 0 : res.hr.hint = TALER_JSON_get_error_hint (json);
276 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
277 : "Unexpected response code %u/%d\n",
278 : (unsigned int) response_code,
279 : (int) res.hr.ec);
280 0 : break;
281 : }
282 0 : sch->cb (sch->cb_cls,
283 : &res);
284 0 : TALER_MERCHANT_get_private_statistics_counter_cancel (sch);
285 : }
286 :
287 :
288 : struct TALER_MERCHANT_GetPrivateStatisticsCounterHandle *
289 0 : TALER_MERCHANT_get_private_statistics_counter_create (
290 : struct GNUNET_CURL_Context *ctx,
291 : const char *url,
292 : const char *slug)
293 : {
294 : struct TALER_MERCHANT_GetPrivateStatisticsCounterHandle *sch;
295 :
296 0 : sch = GNUNET_new (struct TALER_MERCHANT_GetPrivateStatisticsCounterHandle);
297 0 : sch->ctx = ctx;
298 0 : sch->base_url = GNUNET_strdup (url);
299 0 : sch->slug = GNUNET_strdup (slug);
300 0 : sch->type_set = false;
301 0 : return sch;
302 : }
303 :
304 :
305 : enum GNUNET_GenericReturnValue
306 0 : TALER_MERCHANT_get_private_statistics_counter_set_options_ (
307 : struct TALER_MERCHANT_GetPrivateStatisticsCounterHandle *sch,
308 : unsigned int num_options,
309 : const struct TALER_MERCHANT_GetPrivateStatisticsCounterOptionValue *options)
310 : {
311 0 : for (unsigned int i = 0; i < num_options; i++)
312 : {
313 0 : switch (options[i].option)
314 : {
315 0 : case TALER_MERCHANT_GET_PRIVATE_STATISTICS_COUNTER_OPTION_END:
316 0 : return GNUNET_OK;
317 0 : case TALER_MERCHANT_GET_PRIVATE_STATISTICS_COUNTER_OPTION_TYPE:
318 0 : sch->stype = options[i].details.type;
319 0 : sch->type_set = true;
320 0 : break;
321 0 : default:
322 0 : GNUNET_break (0);
323 0 : return GNUNET_SYSERR;
324 : }
325 : }
326 0 : return GNUNET_OK;
327 : }
328 :
329 :
330 : enum TALER_ErrorCode
331 0 : TALER_MERCHANT_get_private_statistics_counter_start (
332 : struct TALER_MERCHANT_GetPrivateStatisticsCounterHandle *sch,
333 : TALER_MERCHANT_GetPrivateStatisticsCounterCallback cb,
334 : TALER_MERCHANT_GET_PRIVATE_STATISTICS_COUNTER_RESULT_CLOSURE *cb_cls)
335 : {
336 : CURL *eh;
337 :
338 0 : sch->cb = cb;
339 0 : sch->cb_cls = cb_cls;
340 : {
341 0 : const char *filter = NULL;
342 : char *path;
343 :
344 0 : if (sch->type_set)
345 : {
346 0 : switch (sch->stype)
347 : {
348 0 : case TALER_MERCHANT_STATISTICS_BY_BUCKET:
349 0 : filter = "bucket";
350 0 : break;
351 0 : case TALER_MERCHANT_STATISTICS_BY_INTERVAL:
352 0 : filter = "interval";
353 0 : break;
354 0 : case TALER_MERCHANT_STATISTICS_ALL:
355 0 : filter = NULL;
356 0 : break;
357 : }
358 : }
359 0 : GNUNET_asprintf (&path,
360 : "private/statistics-counter/%s",
361 : sch->slug);
362 0 : sch->url = TALER_url_join (sch->base_url,
363 : path,
364 : "by",
365 : filter,
366 : NULL);
367 0 : GNUNET_free (path);
368 : }
369 0 : if (NULL == sch->url)
370 0 : return TALER_EC_GENERIC_CONFIGURATION_INVALID;
371 0 : eh = TALER_MERCHANT_curl_easy_get_ (sch->url);
372 0 : if (NULL == eh)
373 0 : return TALER_EC_GENERIC_CONFIGURATION_INVALID;
374 0 : sch->job = GNUNET_CURL_job_add (sch->ctx,
375 : eh,
376 : &handle_get_statistics_counter_finished,
377 : sch);
378 0 : if (NULL == sch->job)
379 0 : return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
380 0 : return TALER_EC_NONE;
381 : }
382 :
383 :
384 : void
385 0 : TALER_MERCHANT_get_private_statistics_counter_cancel (
386 : struct TALER_MERCHANT_GetPrivateStatisticsCounterHandle *sch)
387 : {
388 0 : if (NULL != sch->job)
389 : {
390 0 : GNUNET_CURL_job_cancel (sch->job);
391 0 : sch->job = NULL;
392 : }
393 0 : GNUNET_free (sch->slug);
394 0 : GNUNET_free (sch->url);
395 0 : GNUNET_free (sch->base_url);
396 0 : GNUNET_free (sch);
397 0 : }
398 :
399 :
400 : /* end of merchant_api_get-private-statistics-counter-SLUG-new.c */
|