LCOV - code coverage report
Current view: top level - backend - taler-merchant-httpd_get-templates-TEMPLATE_ID.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 75.0 % 312 234
Test Date: 2026-09-04 23:42:01 Functions: 93.8 % 16 15

            Line data    Source code
       1              : /*
       2              :   This file is part of TALER
       3              :   (C) 2022-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-templates-TEMPLATE_ID.c
      18              :  * @brief implement GET /templates/$ID
      19              :  * @author Priscilla HUANG
      20              :  */
      21              : #include "platform.h"
      22              : #include "taler-merchant-httpd_get-templates-TEMPLATE_ID.h"
      23              : #include "taler-merchant-httpd_exchanges.h"
      24              : #include "taler-merchant-httpd_helper.h"
      25              : #include <taler/taler_json_lib.h>
      26              : #include "merchant-database/iterate_categories_by_ids.h"
      27              : #include "merchant-database/iterate_custom_units_by_names.h"
      28              : #include "merchant-database/iterate_inventory_products.h"
      29              : #include "merchant-database/iterate_inventory_products_filtered.h"
      30              : #include "merchant-database/get_template.h"
      31              : 
      32              : 
      33              : /**
      34              :  * How long do we wait at most for exchange /keys data?
      35              :  */
      36              : #define MAX_KEYS_WAIT \
      37              :         GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS, 2500)
      38              : 
      39              : 
      40              : struct GetTemplateContext;
      41              : 
      42              : 
      43              : /**
      44              :  * Pending lookup of one exchange's keys.
      45              :  */
      46              : struct ExchangeLookup
      47              : {
      48              :   /**
      49              :    * Kept in a DLL.
      50              :    */
      51              :   struct ExchangeLookup *next;
      52              : 
      53              :   /**
      54              :    * Kept in a DLL.
      55              :    */
      56              :   struct ExchangeLookup *prev;
      57              : 
      58              :   /**
      59              :    * Parent request.
      60              :    */
      61              :   struct GetTemplateContext *gtc;
      62              : 
      63              :   /**
      64              :    * Exchange base URL.
      65              :    */
      66              :   char *url;
      67              : 
      68              :   /**
      69              :    * Pending keys operation.
      70              :    */
      71              :   struct TMH_EXCHANGES_KeysOperation *ko;
      72              : };
      73              : 
      74              : 
      75              : /**
      76              :  * Context for a wallet-facing template lookup.
      77              :  */
      78              : struct GetTemplateContext
      79              : {
      80              :   /**
      81              :    * Kept in a DLL while suspended.
      82              :    */
      83              :   struct GetTemplateContext *next;
      84              : 
      85              :   /**
      86              :    * Kept in a DLL while suspended.
      87              :    */
      88              :   struct GetTemplateContext *prev;
      89              : 
      90              :   /**
      91              :    * Request handler context.
      92              :    */
      93              :   struct TMH_HandlerContext *hc;
      94              : 
      95              :   /**
      96              :    * Connection handling this request.
      97              :    */
      98              :   struct MHD_Connection *connection;
      99              : 
     100              :   /**
     101              :    * Pending exchange lookups.
     102              :    */
     103              :   struct ExchangeLookup *lookup_head;
     104              : 
     105              :   /**
     106              :    * Pending exchange lookups.
     107              :    */
     108              :   struct ExchangeLookup *lookup_tail;
     109              : 
     110              :   /**
     111              :    * Task that limits exchange lookup latency.
     112              :    */
     113              :   struct GNUNET_SCHEDULER_Task *timeout_task;
     114              : 
     115              :   /**
     116              :    * Wallet-facing template contract.
     117              :    */
     118              :   json_t *template_contract;
     119              : 
     120              :   /**
     121              :    * Editable defaults, NULL if absent.
     122              :    */
     123              :   json_t *editable_defaults;
     124              : 
     125              :   /**
     126              :    * Merchant information snapshot.
     127              :    */
     128              :   json_t *merchant;
     129              : 
     130              :   /**
     131              :    * Compatible exchange candidates found so far.
     132              :    */
     133              :   json_t *exchange_candidates;
     134              : 
     135              :   /**
     136              :    * Merchant public key snapshot.
     137              :    */
     138              :   struct TALER_MerchantPublicKeyP merchant_pub;
     139              : 
     140              :   /**
     141              :    * #GNUNET_YES while suspended, #GNUNET_NO otherwise, and
     142              :    * #GNUNET_SYSERR when resumed for shutdown.
     143              :    */
     144              :   enum GNUNET_GenericReturnValue suspended;
     145              : };
     146              : 
     147              : 
     148              : /**
     149              :  * Head of suspended template requests.
     150              :  */
     151              : static struct GetTemplateContext *gtc_head;
     152              : 
     153              : /**
     154              :  * Tail of suspended template requests.
     155              :  */
     156              : static struct GetTemplateContext *gtc_tail;
     157              : 
     158              : 
     159              : /**
     160              :  * Determine the currency the client must pay in, if the template
     161              :  * requires a particular one without fixing the amount.
     162              :  *
     163              :  * @param template_contract contract of the template
     164              :  * @return currency to require, NULL if the template does not say
     165              :  */
     166              : static const char *
     167            8 : get_required_currency (const json_t *template_contract)
     168              : {
     169              :   const json_t *currency;
     170              : 
     171            8 :   currency = json_object_get (template_contract,
     172              :                               "currency");
     173            8 :   if (NULL == currency)
     174            7 :     return NULL;
     175            1 :   if (! json_is_string (currency))
     176              :   {
     177            0 :     GNUNET_break (0);
     178            0 :     return NULL;
     179              :   }
     180            1 :   return json_string_value (currency);
     181              : }
     182              : 
     183              : 
     184              : /**
     185              :  * Test whether @a method is already in @a methods.
     186              :  *
     187              :  * @param methods JSON array of strings
     188              :  * @param method method to find
     189              :  * @return true if present
     190              :  */
     191              : static bool
     192            8 : wire_method_present (const json_t *methods,
     193              :                      const char *method)
     194              : {
     195              :   const json_t *entry;
     196              :   size_t index;
     197              : 
     198            8 :   json_array_foreach (methods, index, entry)
     199              :   {
     200            0 :     if (0 == strcasecmp (json_string_value (entry),
     201              :                          method))
     202            0 :       return true;
     203              :   }
     204            8 :   return false;
     205              : }
     206              : 
     207              : 
     208              : /**
     209              :  * Test whether an exchange has an account using @a wire_method.
     210              :  *
     211              :  * @param keys exchange keys
     212              :  * @param wire_method wire method to find
     213              :  * @return true if the method is supported
     214              :  */
     215              : static bool
     216            8 : exchange_has_wire_method (const struct TALER_EXCHANGE_Keys *keys,
     217              :                           const char *wire_method)
     218              : {
     219            8 :   for (unsigned int i = 0; i<keys->accounts_len; i++)
     220              :   {
     221            8 :     struct TALER_FullPayto full_payto = keys->accounts[i].fpayto_uri;
     222              :     char *method;
     223              :     bool matches;
     224              : 
     225            8 :     method = TALER_payto_get_method (full_payto.full_payto);
     226            8 :     matches = (0 == strcasecmp (method,
     227              :                                 wire_method));
     228            8 :     GNUNET_free (method);
     229            8 :     if (matches)
     230            8 :       return true;
     231              :   }
     232            0 :   return false;
     233              : }
     234              : 
     235              : 
     236              : /**
     237              :  * Add an exchange candidate based on current key data.
     238              :  *
     239              :  * @param gtc request context
     240              :  * @param url exchange base URL
     241              :  * @param keys exchange keys
     242              :  * @param exchange exchange handle
     243              :  */
     244              : static void
     245            8 : add_exchange_candidate (struct GetTemplateContext *gtc,
     246              :                         const char *url,
     247              :                         const struct TALER_EXCHANGE_Keys *keys,
     248              :                         const struct TMH_Exchange *exchange)
     249              : {
     250              :   const char *currency;
     251              :   json_t *wire_methods;
     252              : 
     253            8 :   if (NULL == keys)
     254            0 :     return;
     255            8 :   currency = TMH_EXCHANGES_get_currency (exchange);
     256            8 :   if ( (NULL == currency) ||
     257            8 :        (0 != strcasecmp (currency,
     258            8 :                          keys->currency)) )
     259            0 :     return;
     260            8 :   wire_methods = json_array ();
     261            8 :   GNUNET_assert (NULL != wire_methods);
     262            8 :   for (const struct TMH_WireMethod *wm = gtc->hc->instance->wm_head;
     263           16 :        NULL != wm;
     264            8 :        wm = wm->next)
     265              :   {
     266              :     struct TALER_NormalizedPayto np;
     267              :     bool allowed;
     268              : 
     269            8 :     if (! wm->active)
     270            0 :       continue;
     271            8 :     if (! exchange_has_wire_method (keys,
     272            8 :                                     wm->wire_method))
     273            0 :       continue;
     274            8 :     np = TALER_payto_normalize (wm->payto_uri);
     275            8 :     allowed = TALER_EXCHANGE_keys_test_account_allowed (keys,
     276              :                                                         false,
     277              :                                                         np);
     278            8 :     GNUNET_free (np.normalized_payto);
     279           16 :     if ( (! allowed) ||
     280            8 :          wire_method_present (wire_methods,
     281            8 :                               wm->wire_method) )
     282            0 :       continue;
     283            8 :     GNUNET_assert (0 ==
     284              :                    json_array_append_new (wire_methods,
     285              :                                           json_string (wm->wire_method)));
     286              :   }
     287            8 :   if (0 == json_array_size (wire_methods))
     288              :   {
     289            0 :     json_decref (wire_methods);
     290            0 :     return;
     291              :   }
     292            8 :   GNUNET_assert (0 ==
     293              :                  json_array_append_new (
     294              :                    gtc->exchange_candidates,
     295              :                    GNUNET_JSON_PACK (
     296              :                      GNUNET_JSON_pack_string ("base_url",
     297              :                                               url),
     298              :                      GNUNET_JSON_pack_string ("currency",
     299              :                                               currency),
     300              :                      GNUNET_JSON_pack_data_auto (
     301              :                        "master_pub",
     302              :                        TMH_EXCHANGES_get_master_pub (exchange)),
     303              :                      GNUNET_JSON_pack_array_steal ("wire_methods",
     304              :                                                    wire_methods))));
     305              : }
     306              : 
     307              : 
     308              : /**
     309              :  * Resume a template request after exchange discovery.
     310              :  *
     311              :  * @param gtc request to resume
     312              :  */
     313              : static void
     314            8 : resume_template_request (struct GetTemplateContext *gtc)
     315              : {
     316            8 :   if (GNUNET_YES != gtc->suspended)
     317            0 :     return;
     318            8 :   if (NULL != gtc->timeout_task)
     319              :   {
     320            8 :     GNUNET_SCHEDULER_cancel (gtc->timeout_task);
     321            8 :     gtc->timeout_task = NULL;
     322              :   }
     323            8 :   GNUNET_CONTAINER_DLL_remove (gtc_head,
     324              :                                gtc_tail,
     325              :                                gtc);
     326            8 :   gtc->suspended = GNUNET_NO;
     327            8 :   MHD_resume_connection (gtc->connection);
     328            8 :   TALER_MHD_daemon_trigger ();
     329              : }
     330              : 
     331              : 
     332              : /**
     333              :  * Exchange keys lookup completed.
     334              :  *
     335              :  * @param cls a `struct ExchangeLookup *`
     336              :  * @param keys exchange keys, NULL on failure
     337              :  * @param exchange exchange handle
     338              :  */
     339              : static void
     340            8 : exchange_keys_cb (void *cls,
     341              :                   struct TALER_EXCHANGE_Keys *keys,
     342              :                   struct TMH_Exchange *exchange)
     343              : {
     344            8 :   struct ExchangeLookup *lookup = cls;
     345            8 :   struct GetTemplateContext *gtc = lookup->gtc;
     346              : 
     347            8 :   lookup->ko = NULL;
     348            8 :   GNUNET_CONTAINER_DLL_remove (gtc->lookup_head,
     349              :                                gtc->lookup_tail,
     350              :                                lookup);
     351            8 :   add_exchange_candidate (gtc,
     352            8 :                           lookup->url,
     353              :                           keys,
     354              :                           exchange);
     355            8 :   GNUNET_free (lookup->url);
     356            8 :   GNUNET_free (lookup);
     357            8 :   if (NULL == gtc->lookup_head)
     358            8 :     resume_template_request (gtc);
     359            8 : }
     360              : 
     361              : 
     362              : /**
     363              :  * Start key discovery for one trusted exchange.
     364              :  *
     365              :  * @param cls a `struct GetTemplateContext *`
     366              :  * @param url exchange base URL
     367              :  * @param exchange exchange handle, unused
     368              :  */
     369              : static void
     370            8 : start_exchange_lookup (void *cls,
     371              :                        const char *url,
     372              :                        const struct TMH_Exchange *exchange)
     373              : {
     374            8 :   struct GetTemplateContext *gtc = cls;
     375              :   struct ExchangeLookup *lookup;
     376              : 
     377              :   (void) exchange;
     378            8 :   lookup = GNUNET_new (struct ExchangeLookup);
     379            8 :   lookup->gtc = gtc;
     380            8 :   lookup->url = GNUNET_strdup (url);
     381            8 :   lookup->ko = TMH_EXCHANGES_keys4exchange (url,
     382              :                                             false,
     383              :                                             &exchange_keys_cb,
     384              :                                             lookup);
     385            8 :   if (NULL == lookup->ko)
     386              :   {
     387            0 :     GNUNET_free (lookup->url);
     388            0 :     GNUNET_free (lookup);
     389            0 :     return;
     390              :   }
     391            8 :   GNUNET_CONTAINER_DLL_insert (gtc->lookup_head,
     392              :                                gtc->lookup_tail,
     393              :                                lookup);
     394              : }
     395              : 
     396              : 
     397              : /**
     398              :  * Stop waiting for exchange keys and return the partial candidate list.
     399              :  *
     400              :  * @param cls a `struct GetTemplateContext *`
     401              :  */
     402              : static void
     403            0 : exchange_lookup_timeout (void *cls)
     404              : {
     405            0 :   struct GetTemplateContext *gtc = cls;
     406              : 
     407            0 :   gtc->timeout_task = NULL;
     408            0 :   while (NULL != gtc->lookup_head)
     409              :   {
     410            0 :     struct ExchangeLookup *lookup = gtc->lookup_head;
     411              : 
     412            0 :     GNUNET_CONTAINER_DLL_remove (gtc->lookup_head,
     413              :                                  gtc->lookup_tail,
     414              :                                  lookup);
     415            0 :     TMH_EXCHANGES_keys4exchange_cancel (lookup->ko);
     416            0 :     GNUNET_free (lookup->url);
     417            0 :     GNUNET_free (lookup);
     418              :   }
     419            0 :   resume_template_request (gtc);
     420            0 : }
     421              : 
     422              : 
     423              : /**
     424              :  * Cleanup a template request context.
     425              :  *
     426              :  * @param cls a `struct GetTemplateContext *`
     427              :  */
     428              : static void
     429            8 : get_template_cleanup (void *cls)
     430              : {
     431            8 :   struct GetTemplateContext *gtc = cls;
     432              : 
     433            8 :   if (GNUNET_YES == gtc->suspended)
     434            0 :     GNUNET_CONTAINER_DLL_remove (gtc_head,
     435              :                                  gtc_tail,
     436              :                                  gtc);
     437            8 :   if (NULL != gtc->timeout_task)
     438            0 :     GNUNET_SCHEDULER_cancel (gtc->timeout_task);
     439            8 :   while (NULL != gtc->lookup_head)
     440              :   {
     441            0 :     struct ExchangeLookup *lookup = gtc->lookup_head;
     442              : 
     443            0 :     GNUNET_CONTAINER_DLL_remove (gtc->lookup_head,
     444              :                                  gtc->lookup_tail,
     445              :                                  lookup);
     446            0 :     TMH_EXCHANGES_keys4exchange_cancel (lookup->ko);
     447            0 :     GNUNET_free (lookup->url);
     448            0 :     GNUNET_free (lookup);
     449              :   }
     450            8 :   if (NULL != gtc->template_contract)
     451            8 :     json_decref (gtc->template_contract);
     452            8 :   if (NULL != gtc->editable_defaults)
     453            1 :     json_decref (gtc->editable_defaults);
     454            8 :   if (NULL != gtc->merchant)
     455            8 :     json_decref (gtc->merchant);
     456            8 :   if (NULL != gtc->exchange_candidates)
     457            8 :     json_decref (gtc->exchange_candidates);
     458            8 :   GNUNET_free (gtc);
     459            8 : }
     460              : 
     461              : 
     462              : void
     463           20 : TMH_force_get_templates_ID_resume (void)
     464              : {
     465           20 :   while (NULL != gtc_head)
     466              :   {
     467            0 :     struct GetTemplateContext *gtc = gtc_head;
     468              : 
     469            0 :     GNUNET_CONTAINER_DLL_remove (gtc_head,
     470              :                                  gtc_tail,
     471              :                                  gtc);
     472            0 :     gtc->suspended = GNUNET_SYSERR;
     473            0 :     MHD_resume_connection (gtc->connection);
     474            0 :     TALER_MHD_daemon_trigger ();
     475              :   }
     476           20 : }
     477              : 
     478              : 
     479              : /**
     480              :  * Context for building inventory template payloads.
     481              :  */
     482              : struct InventoryPayloadContext
     483              : {
     484              :   /**
     485              :    * Selected category IDs (as JSON array).
     486              :    */
     487              :   const json_t *selected_categories;
     488              : 
     489              :   /**
     490              :    * Selected product IDs (as JSON array) from contract_template.
     491              :    */
     492              :   const json_t *selected_products;
     493              : 
     494              :   /**
     495              :    * Whether all products are selected.
     496              :    */
     497              :   bool selected_all;
     498              : 
     499              :   /**
     500              :    * JSON array of products to build.
     501              :    */
     502              :   json_t *products;
     503              : 
     504              :   /**
     505              :    * JSON array of categories to build.
     506              :    */
     507              :   json_t *category_payload;
     508              : 
     509              :   /**
     510              :    * JSON array of units to build.
     511              :    */
     512              :   json_t *unit_payload;
     513              : 
     514              :   /**
     515              :    * Set of categories referenced by the products.
     516              :    */
     517              :   struct TMH_CategorySet category_set;
     518              : 
     519              :   /**
     520              :    * Set of unit identifiers referenced by the products.
     521              :    */
     522              :   struct TMH_UnitSet unit_set;
     523              : };
     524              : 
     525              : 
     526              : /**
     527              :  * Release resources associated with an inventory payload context.
     528              :  *
     529              :  * @param ipc inventory payload context
     530              :  */
     531              : static void
     532            4 : inventory_payload_cleanup (struct InventoryPayloadContext *ipc)
     533              : {
     534            4 :   if (NULL != ipc->products)
     535            0 :     json_decref (ipc->products);
     536            4 :   if (NULL != ipc->category_payload)
     537            0 :     json_decref (ipc->category_payload);
     538            4 :   if (NULL != ipc->unit_payload)
     539            0 :     json_decref (ipc->unit_payload);
     540            4 :   GNUNET_free (ipc->category_set.ids);
     541            4 :   TMH_unit_set_clear (&ipc->unit_set);
     542            4 :   ipc->products = NULL;
     543            4 :   ipc->category_payload = NULL;
     544            4 :   ipc->unit_payload = NULL;
     545            4 :   ipc->category_set.len = 0;
     546            4 : }
     547              : 
     548              : 
     549              : /**
     550              :  * Add inventory product to JSON payload.
     551              :  *
     552              :  * @param cls inventory payload context
     553              :  * @param product_id product identifier
     554              :  * @param pd product details
     555              :  * @param num_categories number of categories
     556              :  * @param categories category IDs
     557              :  */
     558              : static void
     559           10 : add_inventory_product (
     560              :   void *cls,
     561              :   const char *product_id,
     562              :   const struct TALER_MERCHANTDB_InventoryProductDetails *pd,
     563              :   size_t num_categories,
     564              :   const uint64_t *categories)
     565              : {
     566           10 :   struct InventoryPayloadContext *ipc = cls;
     567              :   json_t *jcategories;
     568              :   json_t *product;
     569              :   char remaining_stock_buf[64];
     570              : 
     571           10 :   jcategories = json_array ();
     572           10 :   GNUNET_assert (NULL != jcategories);
     573           14 :   for (size_t i = 0; i < num_categories; i++)
     574              :   {
     575              :     /* Adding per product category */
     576            4 :     TMH_category_set_add (&ipc->category_set,
     577            4 :                           categories[i]);
     578            4 :     GNUNET_assert (0 ==
     579              :                    json_array_append_new (jcategories,
     580              :                                           json_integer (categories[i])));
     581              :   }
     582           10 :   GNUNET_assert (0 < pd->price_array_length);
     583           10 :   TALER_MERCHANT_vk_format_fractional_string (
     584              :     TALER_MERCHANT_VK_STOCK,
     585           10 :     pd->remaining_stock,
     586           10 :     pd->remaining_stock_frac,
     587              :     sizeof (remaining_stock_buf),
     588              :     remaining_stock_buf);
     589           10 :   product = GNUNET_JSON_PACK (
     590              :     GNUNET_JSON_pack_string ("product_id",
     591              :                              product_id),
     592              :     GNUNET_JSON_pack_string ("product_name",
     593              :                              pd->product_name),
     594              :     GNUNET_JSON_pack_string ("description",
     595              :                              pd->description),
     596              :     GNUNET_JSON_pack_allow_null (
     597              :       GNUNET_JSON_pack_object_incref ("description_i18n",
     598              :                                       pd->description_i18n)),
     599              :     GNUNET_JSON_pack_string ("unit",
     600              :                              pd->unit),
     601              :     TALER_JSON_pack_amount_array ("unit_prices",
     602              :                                   pd->price_array_length,
     603              :                                   pd->price_array),
     604              :     GNUNET_JSON_pack_bool ("unit_allow_fraction",
     605              :                            pd->allow_fractional_quantity),
     606              :     GNUNET_JSON_pack_uint64 ("unit_precision_level",
     607              :                              pd->fractional_precision_level),
     608              :     GNUNET_JSON_pack_string ("remaining_stock",
     609              :                              remaining_stock_buf),
     610              :     GNUNET_JSON_pack_array_steal ("categories",
     611              :                                   jcategories),
     612              :     GNUNET_JSON_pack_allow_null (
     613              :       GNUNET_JSON_pack_array_incref ("taxes",
     614              :                                      pd->taxes)),
     615              :     GNUNET_JSON_pack_allow_null (
     616              :       GNUNET_JSON_pack_string ("image_hash",
     617              :                                pd->image_hash)));
     618              : 
     619              :   /* Adding per product unit */
     620           10 :   TMH_unit_set_add (&ipc->unit_set,
     621           10 :                     pd->unit);
     622              : 
     623           10 :   GNUNET_assert (0 ==
     624              :                  json_array_append_new (ipc->products,
     625              :                                         product));
     626           10 : }
     627              : 
     628              : 
     629              : /**
     630              :  * Add an inventory category to the payload if referenced.
     631              :  *
     632              :  * @param cls category payload context
     633              :  * @param category_id category identifier
     634              :  * @param category_name category name
     635              :  * @param category_name_i18n translated names
     636              :  * @param product_count number of products (unused)
     637              :  */
     638              : static void
     639            4 : add_inventory_category (void *cls,
     640              :                         uint64_t category_id,
     641              :                         const char *category_name,
     642              :                         const json_t *category_name_i18n,
     643              :                         uint64_t product_count)
     644              : {
     645            4 :   struct InventoryPayloadContext *ipc = cls;
     646              :   json_t *category;
     647              : 
     648              :   (void) product_count;
     649            4 :   category = GNUNET_JSON_PACK (
     650              :     GNUNET_JSON_pack_uint64 ("category_id",
     651              :                              category_id),
     652              :     GNUNET_JSON_pack_string ("category_name",
     653              :                              category_name),
     654              :     GNUNET_JSON_pack_allow_null (
     655              :       GNUNET_JSON_pack_object_incref ("category_name_i18n",
     656              :                                       (json_t *) category_name_i18n)));
     657            4 :   GNUNET_assert (0 ==
     658              :                  json_array_append_new (ipc->category_payload,
     659              :                                         category));
     660            4 : }
     661              : 
     662              : 
     663              : /**
     664              :  * Add an inventory unit to the payload if referenced and non-builtin.
     665              :  *
     666              :  * @param cls unit payload context
     667              :  * @param unit_serial unit identifier
     668              :  * @param ud unit details
     669              :  */
     670              : static void
     671            2 : add_inventory_unit (void *cls,
     672              :                     uint64_t unit_serial,
     673              :                     const struct TALER_MERCHANTDB_UnitDetails *ud)
     674              : {
     675            2 :   struct InventoryPayloadContext *ipc = cls;
     676              :   json_t *unit;
     677              : 
     678              :   (void) unit_serial;
     679              : 
     680            2 :   unit = GNUNET_JSON_PACK (
     681              :     GNUNET_JSON_pack_string ("unit",
     682              :                              ud->unit),
     683              :     GNUNET_JSON_pack_string ("unit_name_long",
     684              :                              ud->unit_name_long),
     685              :     GNUNET_JSON_pack_allow_null (
     686              :       GNUNET_JSON_pack_object_incref ("unit_name_long_i18n",
     687              :                                       ud->unit_name_long_i18n)),
     688              :     GNUNET_JSON_pack_string ("unit_name_short",
     689              :                              ud->unit_name_short),
     690              :     GNUNET_JSON_pack_allow_null (
     691              :       GNUNET_JSON_pack_object_incref ("unit_name_short_i18n",
     692              :                                       ud->unit_name_short_i18n)),
     693              :     GNUNET_JSON_pack_bool ("unit_allow_fraction",
     694              :                            ud->unit_allow_fraction),
     695              :     GNUNET_JSON_pack_uint64 ("unit_precision_level",
     696              :                              ud->unit_precision_level));
     697            2 :   GNUNET_assert (0 ==
     698              :                  json_array_append_new (ipc->unit_payload,
     699              :                                         unit));
     700            2 : }
     701              : 
     702              : 
     703              : /**
     704              :  * Build wallet-facing payload for inventory templates.
     705              :  *
     706              :  * @param connection HTTP connection
     707              :  * @param mi merchant instance
     708              :  * @param tp template details
     709              :  * @return newly allocated template contract, NULL if an error was queued
     710              :  */
     711              : static json_t *
     712            4 : build_inventory_template_contract (
     713              :   struct MHD_Connection *connection,
     714              :   const struct TMH_MerchantInstance *mi,
     715              :   const struct TALER_MERCHANTDB_TemplateDetails *tp)
     716              : {
     717              :   struct InventoryPayloadContext ipc;
     718            4 :   const char **product_ids = NULL;
     719            4 :   uint64_t *category_ids = NULL;
     720            4 :   size_t num_product_ids = 0;
     721            4 :   size_t num_category_ids = 0;
     722              :   json_t *inventory_payload;
     723              :   json_t *template_contract;
     724              : 
     725            4 :   memset (&ipc,
     726              :           0,
     727              :           sizeof (ipc));
     728            4 :   ipc.products = json_array ();
     729              :   {
     730              :     struct GNUNET_JSON_Specification spec[] = {
     731            4 :       GNUNET_JSON_spec_mark_optional (
     732              :         GNUNET_JSON_spec_array_const ("selected_categories",
     733              :                                       &ipc.selected_categories),
     734              :         NULL),
     735            4 :       GNUNET_JSON_spec_mark_optional (
     736              :         GNUNET_JSON_spec_array_const ("selected_products",
     737              :                                       &ipc.selected_products),
     738              :         NULL),
     739            4 :       GNUNET_JSON_spec_mark_optional (
     740              :         GNUNET_JSON_spec_bool ("selected_all",
     741              :                                &ipc.selected_all),
     742              :         NULL),
     743            4 :       GNUNET_JSON_spec_end ()
     744              :     };
     745              :     const char *err_name;
     746              :     unsigned int err_line;
     747              : 
     748            4 :     if (GNUNET_OK !=
     749            4 :         GNUNET_JSON_parse (tp->template_contract,
     750              :                            spec,
     751              :                            &err_name,
     752              :                            &err_line))
     753              :     {
     754            0 :       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
     755              :                   "Invalid inventory template_contract for field %s\n",
     756              :                   err_name);
     757            0 :       inventory_payload_cleanup (&ipc);
     758            0 :       (void) TALER_MHD_reply_with_error (
     759              :         connection,
     760              :         MHD_HTTP_INTERNAL_SERVER_ERROR,
     761              :         TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
     762              :         err_name);
     763            0 :       return NULL;
     764              :     }
     765              :   }
     766              : 
     767            4 :   if (! ipc.selected_all)
     768              :   {
     769            4 :     if (NULL != ipc.selected_products)
     770              :     {
     771              :       size_t max_ids;
     772              : 
     773            4 :       max_ids = json_array_size (ipc.selected_products);
     774            4 :       if (0 < max_ids)
     775            4 :         product_ids = GNUNET_new_array (max_ids,
     776              :                                         const char *);
     777           12 :       for (size_t i = 0; i < max_ids; i++)
     778              :       {
     779            8 :         const json_t *entry = json_array_get (ipc.selected_products,
     780              :                                               i);
     781              : 
     782            8 :         if (json_is_string (entry))
     783            8 :           product_ids[num_product_ids++] = json_string_value (entry);
     784              :         else
     785            0 :           GNUNET_break (0);
     786              :       }
     787              :     }
     788            4 :     if (NULL != ipc.selected_categories)
     789              :     {
     790              :       size_t max_categories;
     791              : 
     792            2 :       max_categories = json_array_size (ipc.selected_categories);
     793            2 :       if (0 < max_categories)
     794            2 :         category_ids = GNUNET_new_array (max_categories,
     795              :                                          uint64_t);
     796            4 :       for (size_t i = 0; i < max_categories; i++)
     797              :       {
     798            2 :         const json_t *entry = json_array_get (ipc.selected_categories,
     799              :                                               i);
     800              : 
     801            4 :         if (json_is_integer (entry) &&
     802            2 :             (0 < json_integer_value (entry)))
     803            2 :           category_ids[num_category_ids++]
     804            2 :             = (uint64_t) json_integer_value (entry);
     805              :         else
     806            0 :           GNUNET_break (0);
     807              :       }
     808              :     }
     809              :   }
     810              : 
     811            4 :   if (ipc.selected_all)
     812              :   {
     813              :     enum GNUNET_DB_QueryStatus qs;
     814              : 
     815            0 :     qs = TALER_MERCHANTDB_iterate_inventory_products (TMH_db,
     816            0 :                                                       mi->settings.id,
     817              :                                                       &add_inventory_product,
     818              :                                                       &ipc);
     819            0 :     if (0 > qs)
     820              :     {
     821            0 :       GNUNET_break (0);
     822            0 :       inventory_payload_cleanup (&ipc);
     823            0 :       (void) TALER_MHD_reply_with_error (
     824              :         connection,
     825              :         MHD_HTTP_INTERNAL_SERVER_ERROR,
     826              :         TALER_EC_GENERIC_DB_FETCH_FAILED,
     827              :         "iterate_inventory_products");
     828            0 :       return NULL;
     829              :     }
     830              :   }
     831            4 :   else if ( (0 < num_product_ids) ||
     832              :             (0 < num_category_ids) )
     833              :   {
     834              :     enum GNUNET_DB_QueryStatus qs;
     835              : 
     836            4 :     qs = TALER_MERCHANTDB_iterate_inventory_products_filtered (
     837              :       TMH_db,
     838            4 :       mi->settings.id,
     839              :       product_ids,
     840              :       num_product_ids,
     841              :       category_ids,
     842              :       num_category_ids,
     843              :       &add_inventory_product,
     844              :       &ipc);
     845            4 :     if (0 > qs)
     846              :     {
     847            0 :       GNUNET_break (0);
     848            0 :       inventory_payload_cleanup (&ipc);
     849            0 :       GNUNET_free (product_ids);
     850            0 :       GNUNET_free (category_ids);
     851            0 :       (void) TALER_MHD_reply_with_error (
     852              :         connection,
     853              :         MHD_HTTP_INTERNAL_SERVER_ERROR,
     854              :         TALER_EC_GENERIC_DB_FETCH_FAILED,
     855              :         "iterate_inventory_products_filtered");
     856            0 :       return NULL;
     857              :     }
     858              :   }
     859            4 :   GNUNET_free (product_ids);
     860            4 :   GNUNET_free (category_ids);
     861              : 
     862            4 :   ipc.category_payload = json_array ();
     863            4 :   GNUNET_assert (NULL != ipc.category_payload);
     864            4 :   if (0 < ipc.category_set.len)
     865              :   {
     866              :     enum GNUNET_DB_QueryStatus qs;
     867              : 
     868            2 :     qs = TALER_MERCHANTDB_iterate_categories_by_ids (
     869              :       TMH_db,
     870            2 :       mi->settings.id,
     871            2 :       ipc.category_set.ids,
     872            2 :       ipc.category_set.len,
     873              :       &add_inventory_category,
     874              :       &ipc);
     875            2 :     if (0 > qs)
     876              :     {
     877            0 :       GNUNET_break (0);
     878            0 :       inventory_payload_cleanup (&ipc);
     879            0 :       (void) TALER_MHD_reply_with_error (
     880              :         connection,
     881              :         MHD_HTTP_INTERNAL_SERVER_ERROR,
     882              :         TALER_EC_GENERIC_DB_FETCH_FAILED,
     883              :         "iterate_categories_by_ids");
     884            0 :       return NULL;
     885              :     }
     886              :   }
     887              : 
     888            4 :   ipc.unit_payload = json_array ();
     889            4 :   GNUNET_assert (NULL != ipc.unit_payload);
     890            4 :   if (0 < ipc.unit_set.len)
     891              :   {
     892              :     enum GNUNET_DB_QueryStatus qs;
     893              : 
     894            4 :     qs = TALER_MERCHANTDB_iterate_custom_units_by_names (
     895              :       TMH_db,
     896            4 :       mi->settings.id,
     897            4 :       (const char *const *) ipc.unit_set.units,
     898            4 :       ipc.unit_set.len,
     899              :       &add_inventory_unit,
     900              :       &ipc);
     901            4 :     if (0 > qs)
     902              :     {
     903            0 :       GNUNET_break (0);
     904            0 :       inventory_payload_cleanup (&ipc);
     905            0 :       (void) TALER_MHD_reply_with_error (
     906              :         connection,
     907              :         MHD_HTTP_INTERNAL_SERVER_ERROR,
     908              :         TALER_EC_GENERIC_DB_FETCH_FAILED,
     909              :         "iterate_custom_units_by_names");
     910            0 :       return NULL;
     911              :     }
     912              :   }
     913              : 
     914            4 :   inventory_payload = GNUNET_JSON_PACK (
     915              :     GNUNET_JSON_pack_array_steal ("products",
     916              :                                   ipc.products),
     917              :     GNUNET_JSON_pack_array_steal ("categories",
     918              :                                   ipc.category_payload),
     919              :     GNUNET_JSON_pack_array_steal ("units",
     920              :                                   ipc.unit_payload));
     921            4 :   ipc.products = NULL;
     922            4 :   ipc.category_payload = NULL;
     923            4 :   ipc.unit_payload = NULL;
     924              : 
     925            4 :   template_contract = json_deep_copy (tp->template_contract);
     926            4 :   GNUNET_assert (NULL != template_contract);
     927              :   /* remove internal fields */
     928            4 :   (void) json_object_del (template_contract,
     929              :                           "selected_categories");
     930            4 :   (void) json_object_del (template_contract,
     931              :                           "selected_products");
     932            4 :   (void) json_object_del (template_contract,
     933              :                           "selected_all");
     934              :   /* add inventory data */
     935            4 :   GNUNET_assert (0 ==
     936              :                  json_object_set_new (template_contract,
     937              :                                       "inventory_payload",
     938              :                                       inventory_payload));
     939            4 :   inventory_payload_cleanup (&ipc);
     940            4 :   return template_contract;
     941              : }
     942              : 
     943              : 
     944              : enum MHD_Result
     945           16 : TMH_get_templates_ID (
     946              :   const struct TMH_RequestHandler *rh,
     947              :   struct MHD_Connection *connection,
     948              :   struct TMH_HandlerContext *hc)
     949              : {
     950           16 :   struct GetTemplateContext *gtc = hc->ctx;
     951           16 :   struct TMH_MerchantInstance *mi = hc->instance;
     952              : 
     953              :   (void) rh;
     954           16 :   GNUNET_assert (NULL != mi);
     955           16 :   if (NULL == gtc)
     956              :   {
     957            8 :     struct TALER_MERCHANTDB_TemplateDetails tp = { 0 };
     958              :     enum GNUNET_DB_QueryStatus qs;
     959              : 
     960            8 :     gtc = GNUNET_new (struct GetTemplateContext);
     961            8 :     gtc->hc = hc;
     962            8 :     gtc->connection = connection;
     963            8 :     gtc->exchange_candidates = json_array ();
     964            8 :     GNUNET_assert (NULL != gtc->exchange_candidates);
     965            8 :     gtc->merchant = TMH_instance_metadata_to_json (mi);
     966            8 :     GNUNET_assert (NULL != gtc->merchant);
     967            8 :     gtc->merchant_pub = mi->merchant_pub;
     968            8 :     hc->ctx = gtc;
     969            8 :     hc->cc = &get_template_cleanup;
     970              : 
     971            8 :     qs = TALER_MERCHANTDB_get_template (TMH_db,
     972            8 :                                         mi->settings.id,
     973            8 :                                         hc->infix,
     974              :                                         &tp);
     975            8 :     if (0 > qs)
     976              :     {
     977            0 :       GNUNET_break (0);
     978            8 :       return TALER_MHD_reply_with_error (
     979              :         connection,
     980              :         MHD_HTTP_INTERNAL_SERVER_ERROR,
     981              :         TALER_EC_GENERIC_DB_FETCH_FAILED,
     982              :         "get_template");
     983              :     }
     984            8 :     if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
     985              :     {
     986            0 :       return TALER_MHD_reply_with_error (
     987              :         connection,
     988              :         MHD_HTTP_NOT_FOUND,
     989              :         TALER_EC_MERCHANT_GENERIC_TEMPLATE_UNKNOWN,
     990            0 :         hc->infix);
     991              :     }
     992            8 :     if (NULL != tp.editable_defaults)
     993            1 :       gtc->editable_defaults = json_incref (tp.editable_defaults);
     994            8 :     switch (TALER_MERCHANT_template_type_from_contract (tp.template_contract))
     995              :     {
     996            4 :     case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART:
     997            4 :       gtc->template_contract = build_inventory_template_contract (connection,
     998              :                                                                   mi,
     999              :                                                                   &tp);
    1000            4 :       break;
    1001            4 :     case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER:
    1002              :     case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA:
    1003            4 :       gtc->template_contract = json_incref (tp.template_contract);
    1004            4 :       break;
    1005            0 :     case TALER_MERCHANT_TEMPLATE_TYPE_INVALID:
    1006            0 :       GNUNET_break_op (0);
    1007            0 :       (void) TALER_MHD_reply_with_error (
    1008              :         connection,
    1009              :         MHD_HTTP_INTERNAL_SERVER_ERROR,
    1010              :         TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
    1011              :         "template_type");
    1012            0 :       break;
    1013              :     }
    1014            8 :     TALER_MERCHANTDB_template_details_free (&tp);
    1015            8 :     if (NULL == gtc->template_contract)
    1016            0 :       return MHD_YES;
    1017              : 
    1018            8 :     TMH_exchange_get_trusted (&start_exchange_lookup,
    1019              :                               gtc);
    1020            8 :     if (NULL != gtc->lookup_head)
    1021              :     {
    1022            8 :       gtc->timeout_task = GNUNET_SCHEDULER_add_delayed (
    1023              :         MAX_KEYS_WAIT,
    1024              :         &exchange_lookup_timeout,
    1025              :         gtc);
    1026            8 :       GNUNET_CONTAINER_DLL_insert (gtc_head,
    1027              :                                    gtc_tail,
    1028              :                                    gtc);
    1029            8 :       gtc->suspended = GNUNET_YES;
    1030            8 :       MHD_suspend_connection (connection);
    1031            8 :       return MHD_YES;
    1032              :     }
    1033              :   }
    1034            8 :   if (GNUNET_SYSERR == gtc->suspended)
    1035            0 :     return MHD_NO;
    1036            8 :   return TALER_MHD_REPLY_JSON_PACK (
    1037              :     connection,
    1038              :     MHD_HTTP_OK,
    1039              :     GNUNET_JSON_pack_object_incref ("template_contract",
    1040              :                                     gtc->template_contract),
    1041              :     GNUNET_JSON_pack_allow_null (
    1042              :       GNUNET_JSON_pack_object_incref ("editable_defaults",
    1043              :                                       gtc->editable_defaults)),
    1044              :     GNUNET_JSON_pack_allow_null (
    1045              :       GNUNET_JSON_pack_string ("required_currency",
    1046              :                                get_required_currency (
    1047              :                                  gtc->template_contract))),
    1048              :     GNUNET_JSON_pack_object_incref ("merchant",
    1049              :                                     gtc->merchant),
    1050              :     GNUNET_JSON_pack_data_auto ("merchant_pub",
    1051              :                                 &gtc->merchant_pub),
    1052              :     GNUNET_JSON_pack_array_incref ("exchange_candidates",
    1053              :                                    gtc->exchange_candidates));
    1054              : }
    1055              : 
    1056              : 
    1057              : /* end of taler-merchant-httpd_get-templates-TEMPLATE_ID.c */
        

Generated by: LCOV version 2.0-1