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