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 */
|