merchant

Merchant backend to process payments, run by merchants
Log | Files | Refs | Submodules | README | LICENSE

merchant_api_get-private-statistics-counter-SLUG.c (12159B)


      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/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 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   unsigned int resp_buckets_len = (unsigned int) json_array_size (jbuckets);
    111   unsigned int resp_intervals_len = (unsigned int) json_array_size (jintervals);
    112 
    113   if ( (json_array_size (jbuckets) != (size_t) resp_buckets_len) ||
    114        (json_array_size (jintervals) != (size_t) resp_intervals_len) ||
    115        (resp_buckets_len > MAX_STATISTICS) ||
    116        (resp_intervals_len > MAX_STATISTICS) )
    117   {
    118     GNUNET_break (0);
    119     return GNUNET_SYSERR;
    120   }
    121   {
    122     struct TALER_MERCHANT_GetPrivateStatisticsCounterByBucket resp_buckets[
    123       GNUNET_NZL (resp_buckets_len)];
    124     struct TALER_MERCHANT_GetPrivateStatisticsCounterByInterval resp_intervals[
    125       GNUNET_NZL (resp_intervals_len)];
    126     size_t index;
    127     json_t *value;
    128 
    129     json_array_foreach (jintervals, index, value) {
    130       struct TALER_MERCHANT_GetPrivateStatisticsCounterByInterval *ji
    131         = &resp_intervals[index];
    132       struct GNUNET_JSON_Specification spec[] = {
    133         GNUNET_JSON_spec_timestamp ("start_time",
    134                                     &ji->start_time),
    135         GNUNET_JSON_spec_uint64 ("cumulative_counter",
    136                                  &ji->cumulative_counter),
    137         GNUNET_JSON_spec_end ()
    138       };
    139 
    140       if (GNUNET_OK !=
    141           GNUNET_JSON_parse (value,
    142                              spec,
    143                              NULL, NULL))
    144       {
    145         GNUNET_break_op (0);
    146         return GNUNET_SYSERR;
    147       }
    148     }
    149     json_array_foreach (jbuckets, index, value) {
    150       struct TALER_MERCHANT_GetPrivateStatisticsCounterByBucket *jb
    151         = &resp_buckets[index];
    152       struct GNUNET_JSON_Specification spec[] = {
    153         GNUNET_JSON_spec_timestamp ("start_time",
    154                                     &jb->start_time),
    155         GNUNET_JSON_spec_timestamp ("end_time",
    156                                     &jb->end_time),
    157         GNUNET_JSON_spec_string ("range",
    158                                  &jb->range),
    159         GNUNET_JSON_spec_uint64 ("cumulative_counter",
    160                                  &jb->cumulative_counter),
    161         GNUNET_JSON_spec_end ()
    162       };
    163 
    164       if (GNUNET_OK !=
    165           GNUNET_JSON_parse (value,
    166                              spec,
    167                              NULL, NULL))
    168       {
    169         GNUNET_break_op (0);
    170         return GNUNET_SYSERR;
    171       }
    172     }
    173     {
    174       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       sch->cb (sch->cb_cls,
    185                &scgr);
    186       sch->cb = NULL;
    187     }
    188     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 handle_get_statistics_counter_finished (void *cls,
    203                                         long response_code,
    204                                         const void *response)
    205 {
    206   struct TALER_MERCHANT_GetPrivateStatisticsCounterHandle *sch = cls;
    207   const json_t *json = response;
    208   struct TALER_MERCHANT_GetPrivateStatisticsCounterResponse res = {
    209     .hr.http_status = (unsigned int) response_code,
    210     .hr.reply = json
    211   };
    212 
    213   sch->job = NULL;
    214   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    215               "Got /private/statistics-counter/$SLUG response with status code %u\n",
    216               (unsigned int) response_code);
    217   switch (response_code)
    218   {
    219   case MHD_HTTP_OK:
    220     {
    221       const json_t *buckets;
    222       const json_t *intervals;
    223       const char *buckets_description = NULL;
    224       const char *intervals_description = NULL;
    225       struct GNUNET_JSON_Specification spec[] = {
    226         GNUNET_JSON_spec_array_const ("buckets",
    227                                       &buckets),
    228         GNUNET_JSON_spec_mark_optional (
    229           GNUNET_JSON_spec_string ("buckets_description",
    230                                    &buckets_description),
    231           NULL),
    232         GNUNET_JSON_spec_array_const ("intervals",
    233                                       &intervals),
    234         GNUNET_JSON_spec_mark_optional (
    235           GNUNET_JSON_spec_string ("intervals_description",
    236                                    &intervals_description),
    237           NULL),
    238         GNUNET_JSON_spec_end ()
    239       };
    240 
    241       if (GNUNET_OK !=
    242           GNUNET_JSON_parse (json,
    243                              spec,
    244                              NULL, NULL))
    245       {
    246         res.hr.http_status = 0;
    247         res.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    248         break;
    249       }
    250       if (GNUNET_OK ==
    251           parse_intervals_and_buckets (json,
    252                                        buckets,
    253                                        buckets_description,
    254                                        intervals,
    255                                        intervals_description,
    256                                        sch))
    257       {
    258         TALER_MERCHANT_get_private_statistics_counter_cancel (sch);
    259         return;
    260       }
    261       res.hr.http_status = 0;
    262       res.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    263       break;
    264     }
    265   case MHD_HTTP_UNAUTHORIZED:
    266     res.hr.ec = TALER_JSON_get_error_code (json);
    267     res.hr.hint = TALER_JSON_get_error_hint (json);
    268     break;
    269   case MHD_HTTP_NOT_FOUND:
    270     res.hr.ec = TALER_JSON_get_error_code (json);
    271     res.hr.hint = TALER_JSON_get_error_hint (json);
    272     break;
    273   default:
    274     res.hr.ec = TALER_JSON_get_error_code (json);
    275     res.hr.hint = TALER_JSON_get_error_hint (json);
    276     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    277                 "Unexpected response code %u/%d\n",
    278                 (unsigned int) response_code,
    279                 (int) res.hr.ec);
    280     break;
    281   }
    282   sch->cb (sch->cb_cls,
    283            &res);
    284   TALER_MERCHANT_get_private_statistics_counter_cancel (sch);
    285 }
    286 
    287 
    288 struct TALER_MERCHANT_GetPrivateStatisticsCounterHandle *
    289 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   sch = GNUNET_new (struct TALER_MERCHANT_GetPrivateStatisticsCounterHandle);
    297   sch->ctx = ctx;
    298   sch->base_url = GNUNET_strdup (url);
    299   sch->slug = GNUNET_strdup (slug);
    300   sch->type_set = false;
    301   return sch;
    302 }
    303 
    304 
    305 enum GNUNET_GenericReturnValue
    306 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   for (unsigned int i = 0; i < num_options; i++)
    312   {
    313     switch (options[i].option)
    314     {
    315     case TALER_MERCHANT_GET_PRIVATE_STATISTICS_COUNTER_OPTION_END:
    316       return GNUNET_OK;
    317     case TALER_MERCHANT_GET_PRIVATE_STATISTICS_COUNTER_OPTION_TYPE:
    318       sch->stype = options[i].details.type;
    319       sch->type_set = true;
    320       break;
    321     default:
    322       GNUNET_break (0);
    323       return GNUNET_SYSERR;
    324     }
    325   }
    326   return GNUNET_OK;
    327 }
    328 
    329 
    330 enum TALER_ErrorCode
    331 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   sch->cb = cb;
    339   sch->cb_cls = cb_cls;
    340   {
    341     const char *filter = NULL;
    342     char *path;
    343 
    344     if (sch->type_set)
    345     {
    346       switch (sch->stype)
    347       {
    348       case TALER_MERCHANT_STATISTICS_BY_BUCKET:
    349         filter = "bucket";
    350         break;
    351       case TALER_MERCHANT_STATISTICS_BY_INTERVAL:
    352         filter = "interval";
    353         break;
    354       case TALER_MERCHANT_STATISTICS_ALL:
    355         filter = NULL;
    356         break;
    357       }
    358     }
    359     GNUNET_asprintf (&path,
    360                      "private/statistics-counter/%s",
    361                      sch->slug);
    362     sch->url = TALER_url_join (sch->base_url,
    363                                path,
    364                                "by",
    365                                filter,
    366                                NULL);
    367     GNUNET_free (path);
    368   }
    369   if (NULL == sch->url)
    370     return TALER_EC_GENERIC_CONFIGURATION_INVALID;
    371   eh = TALER_MERCHANT_curl_easy_get_ (sch->url);
    372   if (NULL == eh)
    373     return TALER_EC_GENERIC_CONFIGURATION_INVALID;
    374   sch->job = GNUNET_CURL_job_add (sch->ctx,
    375                                   eh,
    376                                   &handle_get_statistics_counter_finished,
    377                                   sch);
    378   if (NULL == sch->job)
    379     return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
    380   return TALER_EC_NONE;
    381 }
    382 
    383 
    384 void
    385 TALER_MERCHANT_get_private_statistics_counter_cancel (
    386   struct TALER_MERCHANT_GetPrivateStatisticsCounterHandle *sch)
    387 {
    388   if (NULL != sch->job)
    389   {
    390     GNUNET_CURL_job_cancel (sch->job);
    391     sch->job = NULL;
    392   }
    393   GNUNET_free (sch->slug);
    394   GNUNET_free (sch->url);
    395   GNUNET_free (sch->base_url);
    396   GNUNET_free (sch);
    397 }
    398 
    399 
    400 /* end of merchant_api_get-private-statistics-counter-SLUG-new.c */