merchant_api_get-private-statistics-amount-SLUG.c (13825B)
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/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 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 unsigned int resp_buckets_len = json_array_size (jbuckets); 112 unsigned int resp_intervals_len = json_array_size (jintervals); 113 114 if ( (json_array_size (jbuckets) != (size_t) resp_buckets_len) || 115 (json_array_size (jintervals) != (size_t) resp_intervals_len) || 116 (resp_buckets_len > MAX_STATISTICS) || 117 (resp_intervals_len > MAX_STATISTICS) ) 118 { 119 GNUNET_break (0); 120 return GNUNET_SYSERR; 121 } 122 { 123 struct TALER_MERCHANT_GetPrivateStatisticsAmountByBucket resp_buckets[ 124 GNUNET_NZL (resp_buckets_len)]; 125 struct TALER_MERCHANT_GetPrivateStatisticsAmountByInterval resp_intervals[ 126 GNUNET_NZL (resp_intervals_len)]; 127 size_t index; 128 json_t *value; 129 enum GNUNET_GenericReturnValue ret; 130 131 ret = GNUNET_OK; 132 json_array_foreach (jintervals, index, value) { 133 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 GNUNET_JSON_spec_timestamp ("start_time", 139 &jinterval->start_time), 140 GNUNET_JSON_spec_array_const ("cumulative_amounts", 141 &amounts_arr), 142 GNUNET_JSON_spec_end () 143 }; 144 145 if (GNUNET_OK != 146 GNUNET_JSON_parse (value, 147 spec, 148 NULL, NULL)) 149 { 150 GNUNET_break_op (0); 151 return GNUNET_SYSERR; 152 } 153 amounts_len = json_array_size (amounts_arr); 154 { 155 struct TALER_Amount amt_arr[GNUNET_NZL (amounts_len)]; 156 size_t aindex; 157 json_t *avalue; 158 159 jinterval->cumulative_amount_len = amounts_len; 160 jinterval->cumulative_amounts = amt_arr; 161 json_array_foreach (amounts_arr, aindex, avalue) { 162 if (! json_is_string (avalue)) 163 { 164 GNUNET_break_op (0); 165 return GNUNET_SYSERR; 166 } 167 if (GNUNET_OK != 168 TALER_string_to_amount (json_string_value (avalue), 169 &amt_arr[aindex])) 170 { 171 GNUNET_break_op (0); 172 return GNUNET_SYSERR; 173 } 174 } 175 } 176 } 177 json_array_foreach (jbuckets, index, value) { 178 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 GNUNET_JSON_spec_timestamp ("start_time", 184 &jbucket->start_time), 185 GNUNET_JSON_spec_timestamp ("end_time", 186 &jbucket->end_time), 187 GNUNET_JSON_spec_string ("range", 188 &jbucket->range), 189 GNUNET_JSON_spec_array_const ("cumulative_amounts", 190 &amounts_arr), 191 GNUNET_JSON_spec_end () 192 }; 193 194 if (GNUNET_OK != 195 GNUNET_JSON_parse (value, 196 spec, 197 NULL, NULL)) 198 { 199 GNUNET_break_op (0); 200 ret = GNUNET_SYSERR; 201 break; 202 } 203 amounts_len = json_array_size (amounts_arr); 204 { 205 struct TALER_Amount amt_arr[GNUNET_NZL (amounts_len)]; 206 size_t aindex; 207 json_t *avalue; 208 209 jbucket->cumulative_amount_len = amounts_len; 210 jbucket->cumulative_amounts = amt_arr; 211 json_array_foreach (amounts_arr, aindex, avalue) { 212 if (! json_is_string (avalue)) 213 { 214 GNUNET_break_op (0); 215 return GNUNET_SYSERR; 216 } 217 if (GNUNET_OK != 218 TALER_string_to_amount (json_string_value (avalue), 219 &amt_arr[aindex])) 220 { 221 GNUNET_break_op (0); 222 return GNUNET_SYSERR; 223 } 224 } 225 } 226 } 227 if (GNUNET_OK == ret) 228 { 229 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 sah->cb (sah->cb_cls, 240 &sagr); 241 sah->cb = NULL; 242 } 243 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 handle_get_statistics_amount_finished (void *cls, 258 long response_code, 259 const void *response) 260 { 261 struct TALER_MERCHANT_GetPrivateStatisticsAmountHandle *sah = cls; 262 const json_t *json = response; 263 struct TALER_MERCHANT_GetPrivateStatisticsAmountResponse sagr = { 264 .hr.http_status = (unsigned int) response_code, 265 .hr.reply = json 266 }; 267 268 sah->job = NULL; 269 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 270 "Got /private/statistics-amount/$SLUG response with status code %u\n", 271 (unsigned int) response_code); 272 switch (response_code) 273 { 274 case MHD_HTTP_OK: 275 { 276 const json_t *buckets; 277 const json_t *intervals; 278 const char *buckets_description = NULL; 279 const char *intervals_description = NULL; 280 struct GNUNET_JSON_Specification spec[] = { 281 GNUNET_JSON_spec_array_const ("buckets", 282 &buckets), 283 GNUNET_JSON_spec_mark_optional ( 284 GNUNET_JSON_spec_string ("buckets_description", 285 &buckets_description), 286 NULL), 287 GNUNET_JSON_spec_array_const ("intervals", 288 &intervals), 289 GNUNET_JSON_spec_mark_optional ( 290 GNUNET_JSON_spec_string ("intervals_description", 291 &intervals_description), 292 NULL), 293 GNUNET_JSON_spec_end () 294 }; 295 296 if (GNUNET_OK != 297 GNUNET_JSON_parse (json, 298 spec, 299 NULL, NULL)) 300 { 301 sagr.hr.http_status = 0; 302 sagr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 303 break; 304 } 305 if (GNUNET_OK == 306 parse_intervals_and_buckets_amt (json, 307 buckets, 308 buckets_description, 309 intervals, 310 intervals_description, 311 sah)) 312 { 313 TALER_MERCHANT_get_private_statistics_amount_cancel (sah); 314 return; 315 } 316 sagr.hr.http_status = 0; 317 sagr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 318 break; 319 } 320 case MHD_HTTP_UNAUTHORIZED: 321 sagr.hr.ec = TALER_JSON_get_error_code (json); 322 sagr.hr.hint = TALER_JSON_get_error_hint (json); 323 break; 324 case MHD_HTTP_NOT_FOUND: 325 sagr.hr.ec = TALER_JSON_get_error_code (json); 326 sagr.hr.hint = TALER_JSON_get_error_hint (json); 327 break; 328 default: 329 sagr.hr.ec = TALER_JSON_get_error_code (json); 330 sagr.hr.hint = TALER_JSON_get_error_hint (json); 331 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 332 "Unexpected response code %u/%d\n", 333 (unsigned int) response_code, 334 (int) sagr.hr.ec); 335 break; 336 } 337 sah->cb (sah->cb_cls, 338 &sagr); 339 TALER_MERCHANT_get_private_statistics_amount_cancel (sah); 340 } 341 342 343 struct TALER_MERCHANT_GetPrivateStatisticsAmountHandle * 344 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 sah = GNUNET_new (struct TALER_MERCHANT_GetPrivateStatisticsAmountHandle); 352 sah->ctx = ctx; 353 sah->base_url = GNUNET_strdup (url); 354 sah->slug = GNUNET_strdup (slug); 355 sah->stype = TALER_MERCHANT_STATISTICS_ALL; 356 return sah; 357 } 358 359 360 enum GNUNET_GenericReturnValue 361 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 for (unsigned int i = 0; i < num_options; i++) 367 { 368 const struct TALER_MERCHANT_GetPrivateStatisticsAmountOptionValue *opt = 369 &options[i]; 370 371 switch (opt->option) 372 { 373 case TALER_MERCHANT_GET_PRIVATE_STATISTICS_AMOUNT_OPTION_END: 374 return GNUNET_OK; 375 case TALER_MERCHANT_GET_PRIVATE_STATISTICS_AMOUNT_OPTION_TYPE: 376 sah->stype = opt->details.type; 377 sah->have_stype = true; 378 break; 379 default: 380 GNUNET_break (0); 381 return GNUNET_NO; 382 } 383 } 384 return GNUNET_OK; 385 } 386 387 388 enum TALER_ErrorCode 389 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 sah->cb = cb; 397 sah->cb_cls = cb_cls; 398 { 399 const char *filter = NULL; 400 char *path; 401 402 switch (sah->stype) 403 { 404 case TALER_MERCHANT_STATISTICS_BY_BUCKET: 405 filter = "bucket"; 406 break; 407 case TALER_MERCHANT_STATISTICS_BY_INTERVAL: 408 filter = "interval"; 409 break; 410 case TALER_MERCHANT_STATISTICS_ALL: 411 filter = NULL; 412 break; 413 } 414 GNUNET_asprintf (&path, 415 "private/statistics-amount/%s", 416 sah->slug); 417 sah->url = TALER_url_join (sah->base_url, 418 path, 419 "by", 420 filter, 421 NULL); 422 GNUNET_free (path); 423 } 424 if (NULL == sah->url) 425 return TALER_EC_GENERIC_CONFIGURATION_INVALID; 426 eh = TALER_MERCHANT_curl_easy_get_ (sah->url); 427 if (NULL == eh) 428 return TALER_EC_GENERIC_CONFIGURATION_INVALID; 429 sah->job = GNUNET_CURL_job_add (sah->ctx, 430 eh, 431 &handle_get_statistics_amount_finished, 432 sah); 433 if (NULL == sah->job) 434 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 435 return TALER_EC_NONE; 436 } 437 438 439 void 440 TALER_MERCHANT_get_private_statistics_amount_cancel ( 441 struct TALER_MERCHANT_GetPrivateStatisticsAmountHandle *sah) 442 { 443 if (NULL != sah->job) 444 { 445 GNUNET_CURL_job_cancel (sah->job); 446 sah->job = NULL; 447 } 448 GNUNET_free (sah->url); 449 GNUNET_free (sah->base_url); 450 GNUNET_free (sah->slug); 451 GNUNET_free (sah); 452 } 453 454 455 /* end of merchant_api_get-private-statistics-amount-SLUG-new.c */