merchant_api_get-private-statistics-counter-SLUG.c (11041B)
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-counter-SLUG.c 19 * @brief Implementation of the GET /private/statistics-counter/$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-counter/$SLUG operation. 42 */ 43 struct TALER_MERCHANT_StatisticsCounterGetHandle 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_StatisticsCounterGetCallback 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 @a ia. 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 scgh operation handle 82 * @return #GNUNET_OK on success 83 */ 84 static enum GNUNET_GenericReturnValue 85 parse_intervals_and_buckets ( 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_StatisticsCounterGetHandle *scgh) 92 { 93 unsigned int resp_buckets_len = json_array_size (jbuckets); 94 unsigned int resp_intervals_len = json_array_size (jintervals); 95 96 if ( (json_array_size (jbuckets) != (size_t) resp_buckets_len) || 97 (json_array_size (jintervals) != (size_t) resp_intervals_len) || 98 (resp_intervals_len = resp_buckets_len > MAX_STATISTICS) ) 99 { 100 GNUNET_break (0); 101 return GNUNET_SYSERR; 102 } 103 { 104 struct TALER_MERCHANT_StatisticCounterByBucket resp_buckets[ 105 GNUNET_NZL (resp_buckets_len)]; 106 struct TALER_MERCHANT_StatisticCounterByInterval resp_intervals[ 107 GNUNET_NZL (resp_intervals_len)]; 108 size_t index; 109 json_t *value; 110 enum GNUNET_GenericReturnValue ret; 111 112 ret = GNUNET_OK; 113 json_array_foreach (jintervals, index, value) { 114 struct TALER_MERCHANT_StatisticCounterByInterval *jinterval 115 = &resp_intervals[index]; 116 struct GNUNET_JSON_Specification spec[] = { 117 GNUNET_JSON_spec_timestamp ("start_time", 118 &jinterval->start_time), 119 GNUNET_JSON_spec_uint64 ("cumulative_counter", 120 &jinterval->cumulative_counter), 121 GNUNET_JSON_spec_end () 122 }; 123 124 if (GNUNET_OK != 125 GNUNET_JSON_parse (value, 126 spec, 127 NULL, NULL)) 128 { 129 GNUNET_break_op (0); 130 ret = GNUNET_SYSERR; 131 continue; 132 } 133 if (GNUNET_SYSERR == ret) 134 break; 135 } 136 ret = GNUNET_OK; 137 json_array_foreach (jbuckets, index, value) { 138 struct TALER_MERCHANT_StatisticCounterByBucket *jbucket = &resp_buckets[ 139 index]; 140 struct GNUNET_JSON_Specification spec[] = { 141 GNUNET_JSON_spec_timestamp ("start_time", 142 &jbucket->start_time), 143 GNUNET_JSON_spec_timestamp ("end_time", 144 &jbucket->end_time), 145 GNUNET_JSON_spec_string ("range", 146 &jbucket->range), 147 GNUNET_JSON_spec_uint64 ("cumulative_counter", 148 &jbucket->cumulative_counter), 149 GNUNET_JSON_spec_end () 150 }; 151 152 if (GNUNET_OK != 153 GNUNET_JSON_parse (value, 154 spec, 155 NULL, NULL)) 156 { 157 GNUNET_break_op (0); 158 ret = GNUNET_SYSERR; 159 continue; 160 } 161 if (GNUNET_SYSERR == ret) 162 break; 163 } 164 if (GNUNET_OK == ret) 165 { 166 struct TALER_MERCHANT_StatisticsCounterGetResponse gsr = { 167 .hr.http_status = MHD_HTTP_OK, 168 .hr.reply = json, 169 .details.ok.buckets_length = resp_buckets_len, 170 .details.ok.buckets = resp_buckets, 171 .details.ok.buckets_description = buckets_description, 172 .details.ok.intervals_length = resp_intervals_len, 173 .details.ok.intervals = resp_intervals, 174 .details.ok.intervals_description = intervals_description, 175 }; 176 scgh->cb (scgh->cb_cls, 177 &gsr); 178 scgh->cb = NULL; /* just to be sure */ 179 } 180 return ret; 181 } 182 } 183 184 185 /** 186 * Function called when we're done processing the 187 * HTTP GET /statistics-counter/$SLUG request. 188 * 189 * @param cls the `struct TALER_MERCHANT_StatisticsCounterGetHandle` 190 * @param response_code HTTP response code, 0 on error 191 * @param response response body, NULL if not in JSON 192 */ 193 static void 194 handle_get_statistics_counter_finished (void *cls, 195 long response_code, 196 const void *response) 197 { 198 struct TALER_MERCHANT_StatisticsCounterGetHandle *handle = cls; 199 const json_t *json = response; 200 struct TALER_MERCHANT_StatisticsCounterGetResponse res = { 201 .hr.http_status = (unsigned int) response_code, 202 .hr.reply = json 203 }; 204 205 handle->job = NULL; 206 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 207 "Got /statistics-counter/$SLUG response with status code %u\n", 208 (unsigned int) response_code); 209 switch (response_code) 210 { 211 case MHD_HTTP_OK: 212 { 213 const json_t *buckets; 214 const json_t *intervals; 215 const char *buckets_description; 216 const char *intervals_description; 217 struct GNUNET_JSON_Specification spec[] = { 218 GNUNET_JSON_spec_array_const ("buckets", 219 &buckets), 220 GNUNET_JSON_spec_mark_optional ( 221 GNUNET_JSON_spec_string ("buckets_description", 222 &buckets_description), 223 NULL), 224 GNUNET_JSON_spec_array_const ("intervals", 225 &intervals), 226 GNUNET_JSON_spec_mark_optional ( 227 GNUNET_JSON_spec_string ("intervals_description", 228 &intervals_description), 229 NULL), 230 GNUNET_JSON_spec_end () 231 }; 232 233 if (GNUNET_OK != 234 GNUNET_JSON_parse (json, 235 spec, 236 NULL, NULL)) 237 { 238 res.hr.http_status = 0; 239 res.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 240 break; 241 } 242 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 243 "%s\n", json_dumps (json, JSON_INDENT (1))); 244 if (GNUNET_OK == 245 parse_intervals_and_buckets (json, 246 buckets, 247 buckets_description, 248 intervals, 249 intervals_description, 250 handle)) 251 { 252 TALER_MERCHANT_statistic_counter_get_cancel (handle); 253 return; 254 } 255 res.hr.http_status = 0; 256 res.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 257 break; 258 } 259 case MHD_HTTP_UNAUTHORIZED: 260 res.hr.ec = TALER_JSON_get_error_code (json); 261 res.hr.hint = TALER_JSON_get_error_hint (json); 262 /* Nothing really to verify, merchant says we need to authenticate. */ 263 break; 264 case MHD_HTTP_NOT_FOUND: 265 res.hr.ec = TALER_JSON_get_error_code (json); 266 res.hr.hint = TALER_JSON_get_error_hint (json); 267 break; 268 default: 269 /* unexpected response code */ 270 res.hr.ec = TALER_JSON_get_error_code (json); 271 res.hr.hint = TALER_JSON_get_error_hint (json); 272 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 273 "Unexpected response code %u/%d\n", 274 (unsigned int) response_code, 275 (int) res.hr.ec); 276 break; 277 } 278 } 279 280 281 struct TALER_MERCHANT_StatisticsCounterGetHandle * 282 TALER_MERCHANT_statistic_counter_get ( 283 struct GNUNET_CURL_Context *ctx, 284 const char *backend_url, 285 const char *slug, 286 enum TALER_MERCHANT_StatisticsType stype, 287 TALER_MERCHANT_StatisticsCounterGetCallback cb, 288 void *cb_cls) 289 { 290 struct TALER_MERCHANT_StatisticsCounterGetHandle *handle; 291 CURL *eh; 292 293 handle = GNUNET_new (struct TALER_MERCHANT_StatisticsCounterGetHandle); 294 handle->ctx = ctx; 295 handle->cb = cb; 296 handle->cb_cls = cb_cls; 297 { 298 const char *filter = NULL; 299 char *path; 300 301 switch (stype) 302 { 303 case TALER_MERCHANT_STATISTICS_BY_BUCKET: 304 filter = "bucket"; 305 break; 306 case TALER_MERCHANT_STATISTICS_BY_INTERVAL: 307 filter = "interval"; 308 break; 309 case TALER_MERCHANT_STATISTICS_ALL: 310 filter = NULL; 311 break; 312 } 313 GNUNET_asprintf (&path, 314 "private/statistics-counter/%s", 315 slug); 316 handle->url = TALER_url_join (backend_url, 317 path, 318 "by", 319 filter, 320 NULL); 321 GNUNET_free (path); 322 } 323 if (NULL == handle->url) 324 { 325 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 326 "Could not construct request URL.\n"); 327 GNUNET_free (handle); 328 return NULL; 329 } 330 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 331 "Requesting URL '%s'\n", 332 handle->url); 333 eh = TALER_MERCHANT_curl_easy_get_ (handle->url); 334 handle->job = GNUNET_CURL_job_add (ctx, 335 eh, 336 &handle_get_statistics_counter_finished, 337 handle); 338 return handle; 339 } 340 341 342 void 343 TALER_MERCHANT_statistic_counter_get_cancel ( 344 struct TALER_MERCHANT_StatisticsCounterGetHandle *handle) 345 { 346 if (NULL != handle->job) 347 GNUNET_CURL_job_cancel (handle->job); 348 GNUNET_free (handle->url); 349 GNUNET_free (handle); 350 } 351 352 353 /* end of merchant_api_get-private-statistics-counter-SLUG.c */