LCOV - code coverage report
Current view: top level - backend - taler-merchant-httpd_get-private-statistics-report-transactions.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 47.5 % 223 106
Test Date: 2026-09-04 23:42:01 Functions: 75.0 % 8 6

            Line data    Source code
       1              : /*
       2              :   This file is part of TALER
       3              :   (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 Affero General Public License as published by the Free Software
       7              :   Foundation; either version 3, 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 General Public License for more details.
      12              : 
      13              :   You should have received a copy of the GNU General Public License along with
      14              :   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
      15              : */
      16              : /**
      17              :  * @file src/backend/taler-merchant-httpd_get-private-statistics-report-transactions.c
      18              :  * @brief implement GET /statistics-report/transactions
      19              :  * @author Christian Grothoff
      20              :  */
      21              : #include "platform.h"
      22              : #include "taler-merchant-httpd_get-private-statistics-report-transactions.h"
      23              : #include <gnunet/gnunet_json_lib.h>
      24              : #include <taler/taler_json_lib.h>
      25              : #include <taler/taler_mhd_lib.h>
      26              : #include "taler/taler_merchant_util.h"
      27              : #include "merchant-database/iterate_statistic_bucket_amounts_by_range.h"
      28              : #include "merchant-database/iterate_statistic_bucket_counters_by_range.h"
      29              : 
      30              : 
      31              : /**
      32              :  * Sensible bound on the number of buckets a client may request.
      33              :  */
      34              : #define MAX_DELTA 1024
      35              : 
      36              : 
      37              : /**
      38              :  * Closure for the detail_cb().
      39              :  */
      40              : struct ResponseContext
      41              : {
      42              :   /**
      43              :    * Format of the response we are to generate.
      44              :    */
      45              :   enum
      46              :   {
      47              :     RCF_JSON,
      48              :     RCF_PDF
      49              :   } format;
      50              : 
      51              :   /**
      52              :    * Stored in a DLL while suspended.
      53              :    */
      54              :   struct ResponseContext *next;
      55              : 
      56              :   /**
      57              :    * Stored in a DLL while suspended.
      58              :    */
      59              :   struct ResponseContext *prev;
      60              : 
      61              :   /**
      62              :    * Context for this request.
      63              :    */
      64              :   struct TMH_HandlerContext *hc;
      65              : 
      66              :   /**
      67              :    * Async context used to run Typst.
      68              :    */
      69              :   struct TALER_MHD_TypstContext *tc;
      70              : 
      71              :   /**
      72              :    * Response to return.
      73              :    */
      74              :   struct MHD_Response *response;
      75              : 
      76              :   /**
      77              :    * Time when we started processing the request.
      78              :    */
      79              :   struct GNUNET_TIME_Timestamp now;
      80              : 
      81              :   /**
      82              :    * Period of each bucket.
      83              :    */
      84              :   struct GNUNET_TIME_Relative period;
      85              : 
      86              :   /**
      87              :    * Granularity of the buckets. Matches @e period.
      88              :    */
      89              :   const char *granularity;
      90              : 
      91              :   /**
      92              :    * Number of buckets to return.
      93              :    */
      94              :   uint64_t count;
      95              : 
      96              :   /**
      97              :    * HTTP status to use with @e response.
      98              :    */
      99              :   unsigned int http_status;
     100              : 
     101              :   /**
     102              :    * Length of the @e labels array.
     103              :    */
     104              :   unsigned int labels_cnt;
     105              : 
     106              :   /**
     107              :    * Array of labels for the chart.
     108              :    */
     109              :   char **labels;
     110              : 
     111              :   /**
     112              :    * Data groups for the chart.
     113              :    */
     114              :   json_t *data_groups;
     115              : 
     116              :   /**
     117              :    * #GNUNET_YES if connection was suspended,
     118              :    * #GNUNET_SYSERR if we were resumed on shutdown.
     119              :    */
     120              :   enum GNUNET_GenericReturnValue suspended;
     121              : 
     122              : };
     123              : 
     124              : 
     125              : /**
     126              :  * DLL of requests awaiting Typst.
     127              :  */
     128              : static struct ResponseContext *rctx_head;
     129              : 
     130              : /**
     131              :  * DLL of requests awaiting Typst.
     132              :  */
     133              : static struct ResponseContext *rctx_tail;
     134              : 
     135              : 
     136              : void
     137           20 : TMH_handler_statistic_report_transactions_cleanup ()
     138              : {
     139              :   struct ResponseContext *rctx;
     140              : 
     141           20 :   while (NULL != (rctx = rctx_head))
     142              :   {
     143            0 :     GNUNET_CONTAINER_DLL_remove (rctx_head,
     144              :                                  rctx_tail,
     145              :                                  rctx);
     146            0 :     rctx->suspended = GNUNET_SYSERR;
     147            0 :     MHD_resume_connection (rctx->hc->connection);
     148              :   }
     149           20 : }
     150              : 
     151              : 
     152              : /**
     153              :  * Free resources from @a ctx
     154              :  *
     155              :  * @param[in] ctx the `struct ResponseContext` to clean up
     156              :  */
     157              : static void
     158            1 : free_rc (void *ctx)
     159              : {
     160            1 :   struct ResponseContext *rctx = ctx;
     161              : 
     162            1 :   if (NULL != rctx->tc)
     163              :   {
     164            0 :     TALER_MHD_typst_cancel (rctx->tc);
     165            0 :     rctx->tc = NULL;
     166              :   }
     167            1 :   if (NULL != rctx->response)
     168              :   {
     169            0 :     MHD_destroy_response (rctx->response);
     170            0 :     rctx->response = NULL;
     171              :   }
     172            1 :   for (unsigned int i = 0; i<rctx->labels_cnt; i++)
     173            0 :     GNUNET_free (rctx->labels[i]);
     174            1 :   GNUNET_array_grow (rctx->labels,
     175              :                      rctx->labels_cnt,
     176              :                      0);
     177            1 :   json_decref (rctx->data_groups);
     178            1 :   GNUNET_free (rctx);
     179            1 : }
     180              : 
     181              : 
     182              : /**
     183              :  * Function called with the result of a #TALER_MHD_typst() operation.
     184              :  *
     185              :  * @param cls closure
     186              :  * @param tr result of the operation
     187              :  */
     188              : static void
     189            0 : pdf_cb (void *cls,
     190              :         const struct TALER_MHD_TypstResponse *tr)
     191              : {
     192            0 :   struct ResponseContext *rctx = cls;
     193              : 
     194            0 :   rctx->tc = NULL;
     195            0 :   GNUNET_CONTAINER_DLL_remove (rctx_head,
     196              :                                rctx_tail,
     197              :                                rctx);
     198            0 :   rctx->suspended = GNUNET_NO;
     199            0 :   MHD_resume_connection (rctx->hc->connection);
     200            0 :   TALER_MHD_daemon_trigger ();
     201            0 :   if (TALER_EC_NONE != tr->ec)
     202              :   {
     203              :     rctx->http_status
     204            0 :       = TALER_ErrorCode_get_http_status (tr->ec);
     205              :     rctx->response
     206            0 :       = TALER_MHD_make_error (tr->ec,
     207            0 :                               tr->details.hint);
     208            0 :     return;
     209              :   }
     210              :   rctx->http_status
     211            0 :     = MHD_HTTP_OK;
     212              :   rctx->response
     213            0 :     = TALER_MHD_response_from_pdf_file (tr->details.filename);
     214              : }
     215              : 
     216              : 
     217              : /**
     218              :  * Typically called by `iterate_statistic_bucket_amounts_by_range`.
     219              :  *
     220              :  * @param[in,out] cls our `struct ResponseContext` to update
     221              :  * @param bucket_start start time of the bucket
     222              :  * @param amounts_len the length of @a amounts array
     223              :  * @param amounts the cumulative amounts in the bucket
     224              :  */
     225              : static void
     226            1 : amount_by_bucket (void *cls,
     227              :                   struct GNUNET_TIME_Timestamp bucket_start,
     228              :                   unsigned int amounts_len,
     229              :                   const struct TALER_Amount amounts[static amounts_len])
     230            1 : {
     231            1 :   struct ResponseContext *rctx = cls;
     232              :   json_t *values;
     233              : 
     234            2 :   for (unsigned int i = 0; i<amounts_len; i++)
     235              :   {
     236            1 :     bool found = false;
     237              : 
     238            1 :     for (unsigned int j = 0; j<rctx->labels_cnt; j++)
     239              :     {
     240            0 :       if (0 == strcasecmp (amounts[i].currency,
     241            0 :                            rctx->labels[j]))
     242              :       {
     243            0 :         found = true;
     244            0 :         break;
     245              :       }
     246              :     }
     247            1 :     if (! found)
     248              :     {
     249            1 :       GNUNET_array_append (rctx->labels,
     250              :                            rctx->labels_cnt,
     251              :                            GNUNET_strdup (amounts[i].currency));
     252              :     }
     253              :   }
     254              : 
     255            1 :   values = json_array ();
     256            1 :   GNUNET_assert (NULL != values);
     257            2 :   for (unsigned int i = 0; i<rctx->labels_cnt; i++)
     258              :   {
     259            1 :     const char *label = rctx->labels[i];
     260            1 :     double d = 0.0;
     261              : 
     262            1 :     for (unsigned int j = 0; j<amounts_len; j++)
     263              :     {
     264            1 :       const struct TALER_Amount *a = &amounts[j];
     265              : 
     266            1 :       if (0 != strcasecmp (amounts[j].currency,
     267              :                            label))
     268            0 :         continue;
     269            1 :       d = a->value * 1.0
     270            1 :           + (a->fraction * 1.0 / TALER_AMOUNT_FRAC_BASE);
     271            1 :       break;
     272              :     } /* for all amounts */
     273            1 :     GNUNET_assert (0 ==
     274              :                    json_array_append_new (values,
     275              :                                           json_real (d)));
     276              :   } /* for all labels */
     277              : 
     278              :   {
     279              :     json_t *dg;
     280              : 
     281            1 :     dg = GNUNET_JSON_PACK (
     282              :       GNUNET_JSON_pack_timestamp ("start_date",
     283              :                                   bucket_start),
     284              :       GNUNET_JSON_pack_array_steal ("values",
     285              :                                     values));
     286            1 :     GNUNET_assert (0 ==
     287              :                    json_array_append_new (rctx->data_groups,
     288              :                                           dg));
     289              : 
     290              :   }
     291            1 : }
     292              : 
     293              : 
     294              : /**
     295              :  * Create the transaction volume report.
     296              :  *
     297              :  * @param[in,out] rctx request context to use
     298              :  * @param[in,out] charts JSON chart array to expand
     299              :  * @return #GNUNET_OK on success,
     300              :  *         #GNUNET_NO to end with #MHD_YES,
     301              :  *         #GNUNET_NO to end with #MHD_NO.
     302              :  */
     303              : static enum GNUNET_GenericReturnValue
     304            1 : make_transaction_volume_report (struct ResponseContext *rctx,
     305              :                                 json_t *charts)
     306              : {
     307            1 :   const char *bucket_name = "deposits-received";
     308              :   enum GNUNET_DB_QueryStatus qs;
     309              :   json_t *chart;
     310              :   json_t *labels;
     311              : 
     312            1 :   rctx->data_groups = json_array ();
     313            1 :   GNUNET_assert (NULL != rctx->data_groups);
     314            1 :   qs = TALER_MERCHANTDB_iterate_statistic_bucket_amounts_by_range (
     315              :     TMH_db,
     316            1 :     rctx->hc->instance->settings.id,
     317              :     bucket_name,
     318              :     rctx->granularity,
     319              :     rctx->count,
     320              :     &amount_by_bucket,
     321              :     rctx);
     322            1 :   if (0 > qs)
     323              :   {
     324            0 :     GNUNET_break (0);
     325              :     return (MHD_YES ==
     326            0 :             TALER_MHD_reply_with_error (
     327            0 :               rctx->hc->connection,
     328              :               MHD_HTTP_INTERNAL_SERVER_ERROR,
     329              :               TALER_EC_GENERIC_DB_FETCH_FAILED,
     330              :               "iterate_statistic_bucket_amounts_by_range"))
     331            0 :         ? GNUNET_NO : GNUNET_SYSERR;
     332              :   }
     333            1 :   if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
     334              :   {
     335            0 :     json_decref (rctx->data_groups);
     336            0 :     rctx->data_groups = NULL;
     337            0 :     return GNUNET_OK;
     338              :   }
     339              : 
     340            1 :   labels = json_array ();
     341            1 :   GNUNET_assert (NULL != labels);
     342            2 :   for (unsigned int i=0; i<rctx->labels_cnt; i++)
     343              :   {
     344            1 :     GNUNET_assert (0 ==
     345              :                    json_array_append_new (labels,
     346              :                                           json_string (rctx->labels[i])));
     347            1 :     GNUNET_free (rctx->labels[i]);
     348              :   }
     349            1 :   GNUNET_array_grow (rctx->labels,
     350              :                      rctx->labels_cnt,
     351              :                      0);
     352            1 :   chart = GNUNET_JSON_PACK (
     353              :     GNUNET_JSON_pack_string ("chart_name",
     354              :                              "Sales volume"),
     355              :     GNUNET_JSON_pack_string ("y_label",
     356              :                              "Sales"),
     357              :     GNUNET_JSON_pack_array_steal ("data_groups",
     358              :                                   rctx->data_groups),
     359              :     GNUNET_JSON_pack_array_steal ("labels",
     360              :                                   labels),
     361              :     GNUNET_JSON_pack_bool ("cumulative",
     362              :                            false));
     363            1 :   rctx->data_groups = NULL;
     364            1 :   GNUNET_assert (0 ==
     365              :                  json_array_append_new (charts,
     366              :                                         chart));
     367            1 :   return GNUNET_OK;
     368              : }
     369              : 
     370              : 
     371              : /**
     372              :  * Typically called by `iterate_statistic_bucket_counters_by_range`.
     373              :  *
     374              :  * @param[in,out] cls our `struct ResponseContext` to update
     375              :  * @param bucket_start start time of the bucket
     376              :  * @param counters_len the length of @a cumulative_amounts
     377              :  * @param descriptions description for the counter in the bucket
     378              :  * @param counters the counters in the bucket
     379              :  */
     380              : static void
     381            0 : count_by_bucket (void *cls,
     382              :                  struct GNUNET_TIME_Timestamp bucket_start,
     383              :                  unsigned int counters_len,
     384              :                  const char *descriptions[static counters_len],
     385              :                  uint64_t counters[static counters_len])
     386            0 : {
     387            0 :   struct ResponseContext *rctx = cls;
     388              :   json_t *values;
     389              : 
     390            0 :   for (unsigned int i = 0; i<counters_len; i++)
     391              :   {
     392            0 :     bool found = false;
     393              : 
     394            0 :     for (unsigned int j = 0; j<rctx->labels_cnt; j++)
     395              :     {
     396            0 :       if (0 == strcmp (descriptions[i],
     397            0 :                        rctx->labels[j]))
     398              :       {
     399            0 :         found = true;
     400            0 :         break;
     401              :       }
     402              :     }
     403            0 :     if (! found)
     404              :     {
     405            0 :       GNUNET_array_append (rctx->labels,
     406              :                            rctx->labels_cnt,
     407              :                            GNUNET_strdup (descriptions[i]));
     408              :     }
     409              :   }
     410              : 
     411            0 :   values = json_array ();
     412            0 :   GNUNET_assert (NULL != values);
     413            0 :   for (unsigned int i = 0; i<rctx->labels_cnt; i++)
     414              :   {
     415            0 :     const char *label = rctx->labels[i];
     416            0 :     uint64_t v = 0;
     417              : 
     418            0 :     for (unsigned int j = 0; j<counters_len; j++)
     419              :     {
     420            0 :       if (0 != strcmp (descriptions[j],
     421              :                        label))
     422            0 :         continue;
     423            0 :       v = counters[j];
     424            0 :       break;
     425              :     } /* for all amounts */
     426            0 :     GNUNET_assert (0 ==
     427              :                    json_array_append_new (values,
     428              :                                           json_integer (v)));
     429              :   } /* for all labels */
     430              : 
     431              :   {
     432              :     json_t *dg;
     433              : 
     434            0 :     dg = GNUNET_JSON_PACK (
     435              :       GNUNET_JSON_pack_timestamp ("start_date",
     436              :                                   bucket_start),
     437              :       GNUNET_JSON_pack_array_steal ("values",
     438              :                                     values));
     439            0 :     GNUNET_assert (0 ==
     440              :                    json_array_append_new (rctx->data_groups,
     441              :                                           dg));
     442              : 
     443              :   }
     444            0 : }
     445              : 
     446              : 
     447              : /**
     448              :  * Create the transaction count report.
     449              :  *
     450              :  * @param[in,out] rctx request context to use
     451              :  * @param[in,out] charts JSON chart array to expand
     452              :  * @return #GNUNET_OK on success,
     453              :  *         #GNUNET_NO to end with #MHD_YES,
     454              :  *         #GNUNET_NO to end with #MHD_NO.
     455              :  */
     456              : static enum GNUNET_GenericReturnValue
     457            1 : make_transaction_count_report (struct ResponseContext *rctx,
     458              :                                json_t *charts)
     459              : {
     460            1 :   const char *prefix = "orders-paid";
     461              :   enum GNUNET_DB_QueryStatus qs;
     462              :   json_t *chart;
     463              :   json_t *labels;
     464              : 
     465            1 :   rctx->data_groups = json_array ();
     466            1 :   GNUNET_assert (NULL != rctx->data_groups);
     467            1 :   qs = TALER_MERCHANTDB_iterate_statistic_bucket_counters_by_range (
     468              :     TMH_db,
     469            1 :     rctx->hc->instance->settings.id,
     470              :     prefix,   /* prefix to match against bucket name */
     471              :     rctx->granularity,
     472              :     rctx->count,
     473              :     &count_by_bucket,
     474              :     rctx);
     475            1 :   if (0 > qs)
     476              :   {
     477            0 :     GNUNET_break (0);
     478              :     return (MHD_YES ==
     479            0 :             TALER_MHD_reply_with_error (
     480            0 :               rctx->hc->connection,
     481              :               MHD_HTTP_INTERNAL_SERVER_ERROR,
     482              :               TALER_EC_GENERIC_DB_FETCH_FAILED,
     483              :               "iterate_statistic_bucket_counters_by_range"))
     484            0 :         ? GNUNET_NO : GNUNET_SYSERR;
     485              :   }
     486            1 :   if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
     487              :   {
     488            1 :     json_decref (rctx->data_groups);
     489            1 :     rctx->data_groups = NULL;
     490            1 :     return GNUNET_OK;
     491              :   }
     492            0 :   labels = json_array ();
     493            0 :   GNUNET_assert (NULL != labels);
     494            0 :   for (unsigned int i=0; i<rctx->labels_cnt; i++)
     495              :   {
     496            0 :     const char *label = rctx->labels[i];
     497              : 
     498              :     /* This condition should always hold. */
     499            0 :     if (0 ==
     500            0 :         strncmp (prefix,
     501              :                  label,
     502              :                  strlen (prefix)))
     503            0 :       label += strlen (prefix);
     504            0 :     GNUNET_assert (0 ==
     505              :                    json_array_append_new (labels,
     506              :                                           json_string (label)));
     507            0 :     GNUNET_free (rctx->labels[i]);
     508              :   }
     509            0 :   GNUNET_array_grow (rctx->labels,
     510              :                      rctx->labels_cnt,
     511              :                      0);
     512            0 :   chart = GNUNET_JSON_PACK (
     513              :     GNUNET_JSON_pack_string ("chart_name",
     514              :                              "Transaction counts"),
     515              :     GNUNET_JSON_pack_string ("y_label",
     516              :                              "Number of transactions"),
     517              :     GNUNET_JSON_pack_array_steal ("data_groups",
     518              :                                   rctx->data_groups),
     519              :     GNUNET_JSON_pack_array_steal ("labels",
     520              :                                   labels),
     521              :     GNUNET_JSON_pack_bool ("cumulative",
     522              :                            false));
     523            0 :   rctx->data_groups = NULL;
     524            0 :   GNUNET_assert (0 ==
     525              :                  json_array_append_new (charts,
     526              :                                         chart));
     527            0 :   return GNUNET_OK;
     528              : }
     529              : 
     530              : 
     531              : enum MHD_Result
     532            1 : TMH_private_get_statistics_report_transactions (
     533              :   const struct TMH_RequestHandler *rh,
     534              :   struct MHD_Connection *connection,
     535              :   struct TMH_HandlerContext *hc)
     536              : {
     537            1 :   struct ResponseContext *rctx = hc->ctx;
     538            1 :   struct TMH_MerchantInstance *mi = hc->instance;
     539              :   json_t *charts;
     540              : 
     541            1 :   if (NULL != rctx)
     542              :   {
     543            0 :     GNUNET_assert (GNUNET_YES != rctx->suspended);
     544            0 :     if (GNUNET_SYSERR == rctx->suspended)
     545            0 :       return MHD_NO;
     546            0 :     if (NULL == rctx->response)
     547              :     {
     548            0 :       GNUNET_break (0);
     549            0 :       return MHD_NO;
     550              :     }
     551            0 :     return MHD_queue_response (connection,
     552              :                                rctx->http_status,
     553              :                                rctx->response);
     554              :   }
     555            1 :   rctx = GNUNET_new (struct ResponseContext);
     556            1 :   rctx->hc = hc;
     557            1 :   rctx->now = GNUNET_TIME_timestamp_get ();
     558            1 :   hc->ctx = rctx;
     559            1 :   hc->cc = &free_rc;
     560            1 :   GNUNET_assert (NULL != mi);
     561              : 
     562            1 :   rctx->granularity = MHD_lookup_connection_value (connection,
     563              :                                                    MHD_GET_ARGUMENT_KIND,
     564              :                                                    "granularity");
     565            1 :   if (NULL == rctx->granularity)
     566              :   {
     567            1 :     rctx->granularity = "day";
     568            1 :     rctx->period = GNUNET_TIME_UNIT_DAYS;
     569            1 :     rctx->count = 95;
     570              :   }
     571              :   else
     572              :   {
     573              :     const struct
     574              :     {
     575              :       const char *name;
     576              :       struct GNUNET_TIME_Relative period;
     577              :       uint64_t default_counter;
     578            0 :     } map[] = {
     579              :       {
     580              :         .name = "second",
     581            0 :         .period = GNUNET_TIME_UNIT_SECONDS,
     582              :         .default_counter = 120,
     583              :       },
     584              :       {
     585              :         .name = "minute",
     586            0 :         .period = GNUNET_TIME_UNIT_MINUTES,
     587              :         .default_counter = 120,
     588              :       },
     589              :       {
     590              :         .name = "hour",
     591            0 :         .period = GNUNET_TIME_UNIT_HOURS,
     592              :         .default_counter = 48,
     593              :       },
     594              :       {
     595              :         .name = "day",
     596            0 :         .period = GNUNET_TIME_UNIT_DAYS,
     597              :         .default_counter = 95,
     598              :       },
     599              :       {
     600              :         .name = "week",
     601            0 :         .period = GNUNET_TIME_UNIT_WEEKS,
     602              :         .default_counter = 12,
     603              :       },
     604              :       {
     605              :         .name = "month",
     606            0 :         .period = GNUNET_TIME_UNIT_MONTHS,
     607              :         .default_counter = 36,
     608              :       },
     609              :       {
     610              :         .name = "quarter",
     611            0 :         .period = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MONTHS,
     612              :                                                  3),
     613              :         .default_counter = 40,
     614              :       },
     615              :       {
     616              :         .name = "year",
     617            0 :         .period = GNUNET_TIME_UNIT_YEARS,
     618              :         .default_counter = 10
     619              :       },
     620              :       {
     621              :         .name = NULL
     622              :       }
     623              :     };
     624              : 
     625            0 :     rctx->count = 0;
     626            0 :     for (unsigned int i = 0; map[i].name != NULL; i++)
     627              :     {
     628            0 :       if (0 == strcasecmp (map[i].name,
     629              :                            rctx->granularity))
     630              :       {
     631            0 :         rctx->count = map[i].default_counter;
     632            0 :         rctx->period = map[i].period;
     633            0 :         break;
     634              :       }
     635              :     }
     636            0 :     if (0 == rctx->count)
     637              :     {
     638            0 :       GNUNET_break_op (0);
     639            0 :       return TALER_MHD_reply_with_error (
     640              :         connection,
     641              :         MHD_HTTP_GONE,
     642              :         TALER_EC_MERCHANT_PRIVATE_GET_STATISTICS_REPORT_GRANULARITY_UNAVAILABLE,
     643              :         rctx->granularity);
     644              :     }
     645              :   } /* end handling granularity */
     646              : 
     647              :   /* Figure out desired output format */
     648              :   {
     649              :     const char *mime;
     650              : 
     651            1 :     mime = MHD_lookup_connection_value (connection,
     652              :                                         MHD_HEADER_KIND,
     653              :                                         MHD_HTTP_HEADER_ACCEPT);
     654            1 :     if (NULL == mime)
     655            0 :       mime = "application/json";
     656            1 :     if (0 == strcmp (mime,
     657              :                      "application/json"))
     658              :     {
     659            0 :       rctx->format = RCF_JSON;
     660              :     }
     661            1 :     else if (0 == strcmp (mime,
     662              :                           "application/pdf"))
     663              :     {
     664              : 
     665            1 :       rctx->format = RCF_PDF;
     666              :     }
     667              :     else
     668              :     {
     669            0 :       GNUNET_break_op (0);
     670            0 :       return TALER_MHD_REPLY_JSON_PACK (
     671              :         connection,
     672              :         MHD_HTTP_NOT_ACCEPTABLE,
     673              :         GNUNET_JSON_pack_string ("hint",
     674              :                                  mime));
     675              :     }
     676              :   } /* end of determine output format */
     677              : 
     678            1 :   TALER_MHD_parse_request_number (connection,
     679              :                                   "count",
     680              :                                   &rctx->count);
     681              :   /* Cap the client-supplied count to a sane maximum to prevent
     682              :      resource amplification (used both as DB bucket count and in
     683              :      GNUNET_TIME_relative_multiply() for the start_date). */
     684            1 :   if (rctx->count > MAX_DELTA)
     685            0 :     rctx->count = MAX_DELTA;
     686              : 
     687              :   /* create charts */
     688            1 :   charts = json_array ();
     689            1 :   GNUNET_assert (NULL != charts);
     690              :   {
     691              :     enum GNUNET_GenericReturnValue ret;
     692              : 
     693            1 :     ret = make_transaction_volume_report (rctx,
     694              :                                           charts);
     695            1 :     if (GNUNET_OK != ret)
     696            0 :       return (GNUNET_NO == ret) ? MHD_YES : MHD_NO;
     697            1 :     ret = make_transaction_count_report (rctx,
     698              :                                          charts);
     699            1 :     if (GNUNET_OK != ret)
     700            0 :       return (GNUNET_NO == ret) ? MHD_YES : MHD_NO;
     701              :   }
     702              : 
     703              :   /* generate response */
     704              :   {
     705              :     struct GNUNET_TIME_Timestamp start_date;
     706              :     struct GNUNET_TIME_Timestamp end_date;
     707              :     json_t *root;
     708              : 
     709            1 :     end_date = rctx->now;
     710              :     start_date
     711            1 :       = GNUNET_TIME_absolute_to_timestamp (
     712              :           GNUNET_TIME_absolute_subtract (
     713              :             end_date.abs_time,
     714              :             GNUNET_TIME_relative_multiply (rctx->period,
     715            1 :                                            rctx->count)));
     716            1 :     if (GNUNET_TIME_absolute_is_zero (start_date.abs_time))
     717              :     {
     718              :       /* Zero is special and not allowed, use 1s to avoid it
     719              :          if the above underflows 1970-01-01... */
     720            0 :       start_date.abs_time.abs_value_us = 1000LLU * 1000LLU;
     721              :     }
     722            1 :     root = GNUNET_JSON_PACK (
     723              :       GNUNET_JSON_pack_string ("business_name",
     724              :                                mi->settings.name),
     725              :       GNUNET_JSON_pack_timestamp ("start_date",
     726              :                                   start_date),
     727              :       GNUNET_JSON_pack_timestamp ("end_date",
     728              :                                   end_date),
     729              :       GNUNET_JSON_pack_time_rel ("bucket_period",
     730              :                                  rctx->period),
     731              :       GNUNET_JSON_pack_array_steal ("charts",
     732              :                                     charts));
     733              : 
     734            1 :     switch (rctx->format)
     735              :     {
     736            0 :     case RCF_JSON:
     737            1 :       return TALER_MHD_reply_json (connection,
     738              :                                    root,
     739              :                                    MHD_HTTP_OK);
     740            1 :     case RCF_PDF:
     741              :       {
     742            1 :         struct TALER_MHD_TypstDocument doc = {
     743              :           .form_name = "transactions",
     744              :           .form_version = "0.0.0",
     745              :           .data = root
     746              :         };
     747              : 
     748            1 :         rctx->tc = TALER_MHD_typst (TALER_MERCHANT_project_data (),
     749              :                                     TMH_cfg,
     750              :                                     false, /* remove on exit */
     751              :                                     "merchant",
     752              :                                     1, /* one document, length of "array"! */
     753              :                                     &doc,
     754              :                                     &pdf_cb,
     755              :                                     rctx);
     756            1 :         json_decref (root);
     757            1 :         if (NULL == rctx->tc)
     758              :         {
     759            1 :           GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
     760              :                       "Client requested PDF, but Typst is unavailable\n");
     761            1 :           return TALER_MHD_reply_with_error (
     762              :             connection,
     763              :             MHD_HTTP_NOT_IMPLEMENTED,
     764              :             TALER_EC_MERCHANT_GENERIC_NO_TYPST_OR_PDFTK,
     765              :             NULL);
     766              :         }
     767            0 :         GNUNET_CONTAINER_DLL_insert (rctx_head,
     768              :                                      rctx_tail,
     769              :                                      rctx);
     770            0 :         rctx->suspended = GNUNET_YES;
     771            0 :         MHD_suspend_connection (connection);
     772            0 :         return MHD_YES;
     773              :       }
     774              :     } /* end switch */
     775              :   }
     776            0 :   GNUNET_assert (0);
     777              :   return MHD_NO;
     778              : }
     779              : 
     780              : 
     781              : /* end of taler-merchant-httpd_get-private-statistics-report-transactions.c */
        

Generated by: LCOV version 2.0-1