diff options
author | Jonathan Buchanan <jonathan.russ.buchanan@gmail.com> | 2020-06-10 01:44:32 -0400 |
---|---|---|
committer | Jonathan Buchanan <jonathan.russ.buchanan@gmail.com> | 2020-06-10 01:44:32 -0400 |
commit | 26d5adb7e9ea120b356f40b444f410973c8a83d4 (patch) | |
tree | 179c5625ba76c1b5cae36031b7f13ede9ae0223d /src/lib/merchant_api_get_tips.c | |
parent | 7a9c8b206332c3500ffd733b79aa56b0ea01338a (diff) |
implementation of GET /private/tips
Diffstat (limited to 'src/lib/merchant_api_get_tips.c')
-rw-r--r-- | src/lib/merchant_api_get_tips.c | 369 |
1 files changed, 369 insertions, 0 deletions
diff --git a/src/lib/merchant_api_get_tips.c b/src/lib/merchant_api_get_tips.c index 05f8e7b4..63bcb940 100644 --- a/src/lib/merchant_api_get_tips.c +++ b/src/lib/merchant_api_get_tips.c @@ -19,3 +19,372 @@ * @brief Implementation of the GET /private/tips request of the merchant's HTTP API * @author Jonathan Buchanan */ +#include "platform.h" +#include <curl/curl.h> +#include <jansson.h> +#include <microhttpd.h> /* just for HTTP status codes */ +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_curl_lib.h> +#include "taler_merchant_service.h" +#include <taler/taler_json_lib.h> +#include <taler/taler_signatures.h> + + +/** + * Handle for a GET /private/tips operation. + */ +struct TALER_MERCHANT_TipsGetHandle +{ + /** + * The url for this request. + */ + char *url; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_MERCHANT_TipsGetCallback cb; + + /** + * Closure for @a cb. + */ + void *cb_cls; + + /** + * Reference to the execution context. + */ + struct GNUNET_CURL_Context *ctx; + +}; + + +/** + * Parse tip information from @a ia. + * + * @param ia JSON array (or NULL!) tip order data + * @param tgh operation handle + * @return #GNUNET_OK on success + */ +static int +parse_tips (const json_t *ia, + struct TALER_MERCHANT_TipsGetHandle *tgh) +{ + unsigned int tes_len = json_array_size (ia); + struct TALER_MERCHANT_TipEntry tes[tes_len]; + size_t index; + json_t *value; + int ret; + + ret = GNUNET_OK; + json_array_foreach (ia, index, value) { + struct TALER_MERCHANT_TipEntry *ie = &tes[index]; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_uint64 ("row_id", + &ie->row_id), + GNUNET_JSON_spec_fixed_auto ("tip_id", + &ie->tip_id), + TALER_JSON_spec_amount ("tip_amount", + &ie->tip_amount), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (value, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + ret = GNUNET_SYSERR; + continue; + } + if (GNUNET_SYSERR == ret) + break; + } + if (GNUNET_OK == ret) + { + struct TALER_MERCHANT_HttpResponse hr = { + .http_status = MHD_HTTP_OK + }; + + tgh->cb (tgh->cb_cls, + &hr, + tes_len, + tes); + tgh->cb = NULL; /* just to be sure */ + } + return ret; +} + + +/** + * Function called when we're done processing the + * HTTP GET /private/tips request. + * + * @param cls the `struct TALER_MERCHANT_TipsGetHandle` + * @param response_code HTTP response code, 0 on error + * @param json response body, NULL if not in JSON + */ +static void +handle_get_tips_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_MERCHANT_TipsGetHandle *tgh = cls; + const json_t *json = response; + struct TALER_MERCHANT_HttpResponse hr = { + .http_status = (unsigned int) response_code, + .reply = json + }; + + tgh->job = NULL; + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Got /private/tips response with status code %u\n", + (unsigned int) response_code); + switch (response_code) + { + case MHD_HTTP_OK: + { + json_t *tips; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_json ("tips", + &tips), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (json, + spec, + NULL, NULL)) + { + hr.http_status = 0; + hr.ec = TALER_EC_INVALID_RESPONSE; + } + else + { + if ( (! json_is_array (tips)) || + (GNUNET_OK == + parse_tips (tips, + tgh)) ) + { + GNUNET_JSON_parse_free (spec); + TALER_MERCHANT_tips_get_cancel (tgh); + return; + } + else + { + hr.http_status = 0; + hr.ec = TALER_EC_INVALID_RESPONSE; + } + } + GNUNET_JSON_parse_free (spec); + break; + } + default: + /* unexpected response code */ + hr.ec = TALER_JSON_get_error_code (json); + hr.hint = TALER_JSON_get_error_hint (json); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u/%d\n", + (unsigned int) response_code, + (int) hr.ec); + break; + } + tgh->cb (tgh->cb_cls, + &hr, + 0, + NULL); + TALER_MERCHANT_tips_get_cancel (tgh); +} + + +/** + * Make a GET /private/tips request. + * + * @param ctx the context + * @param backend_url HTTP base URL for the backend + * @param cb function to call with the backend's tip information + * @param cb_cls closure for @a cb + * @return the request handle; NULL upon error + */ +struct TALER_MERCHANT_TipsGetHandle * +TALER_MERCHANT_tips_get ( + struct GNUNET_CURL_Context *ctx, + const char *backend_url, + TALER_MERCHANT_TipsGetCallback cb, + void *cb_cls) +{ + return TALER_MERCHANT_tips_get2 (ctx, + backend_url, + false, + -20, + UINT64_MAX, + cb, + cb_cls); +} + + +/** + * Issue a GET /private/tips request with filters to the backend. + * + * @param ctx execution context + * @param backend_url base URL of the merchant backend + * @param include_expired whether to return all tips or only unexpired tips + * @param limit number of results to return, negative for descending row id, positive for ascending + * @param offset row id to start returning results from + * @param cb function to call with the result + * @param cb_cls closure for @a cb + * @return handle for this operation, NULL upon errors + */ +struct TALER_MERCHANT_TipsGetHandle * +TALER_MERCHANT_tips_get2 (struct GNUNET_CURL_Context *ctx, + const char *backend_url, + enum TALER_MERCHANT_YesNoAll expired, + int64_t limit, + uint64_t offset, + TALER_MERCHANT_TipsGetCallback cb, + void *cb_cls) +{ + struct TALER_MERCHANT_TipsGetHandle *tgh; + CURL *eh; + + GNUNET_assert (NULL != backend_url); + if (0 == limit) + { + GNUNET_break (0); + return NULL; + } + tgh = GNUNET_new (struct TALER_MERCHANT_TipsGetHandle); + tgh->ctx = ctx; + tgh->cb = cb; + tgh->cb_cls = cb_cls; + + /* build tgh->url with the various optional arguments */ + { + struct GNUNET_Buffer buf = { 0 }; + bool first = true; + /** + * Macro to append @a a and @a b to @a buf, using + * the right separators between key (@a a) and + * value (@a b). Uses "first" to decide between + * using "?" and "&" as the separator. + * + * @param a a key + * @param b a value + */ +#define APPEND(a,b) \ + do { \ + if (first) \ + GNUNET_buffer_write_str (&buf, \ + "?"); \ + else \ + GNUNET_buffer_write_str (&buf, \ + "&"); \ + first = false; \ + GNUNET_buffer_write_str (&buf, (a)); \ + GNUNET_buffer_write_str (&buf, "="); \ + GNUNET_buffer_write_str (&buf, (b)); \ + } while (0) + + { + char *url; + + url = TALER_url_join (backend_url, + "private/tips", + NULL); + if (NULL == url) + goto finished; + GNUNET_buffer_write_str (&buf, + url); + GNUNET_free (url); + } + if (TALER_MERCHANT_YNA_NO != expired) + APPEND ("expired", + (TALER_MERCHANT_YNA_YES == expired) ? "yes" : "all"); + if (limit > 0) + { + if (0 != offset) + { + char cbuf[30]; + + GNUNET_snprintf (cbuf, + sizeof (cbuf), + "%llu", + (unsigned long long) offset); + APPEND ("offset", + cbuf); + } + } + else + { + if (UINT64_MAX != offset) + { + char cbuf[30]; + + GNUNET_snprintf (cbuf, + sizeof (cbuf), + "%llu", + (unsigned long long) offset); + APPEND ("offset", + cbuf); + } + } + if (-20 != limit) + { + char cbuf[30]; + + GNUNET_snprintf (cbuf, + sizeof (cbuf), + "%lld", + (long long) limit); + APPEND ("limit", + cbuf); + } + tgh->url = GNUNET_buffer_reap_str (&buf); +#undef APPEND + } + +finished: + if (NULL == tgh->url) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Could not construct request URL.\n"); + GNUNET_free (tgh); + return NULL; + } + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Requesting URL '%s'\n", + tgh->url); + eh = curl_easy_init (); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_URL, + tgh->url)); + tgh->job = GNUNET_CURL_job_add (ctx, + eh, + GNUNET_YES, + &handle_get_tips_finished, + tgh); + return tgh; +} + + +/** + * Cancel GET /private/tips request. Must not be called by clients after + * the callback was invoked. + * + * @param ogh request to cancel. + */ +void +TALER_MERCHANT_tips_get_cancel ( + struct TALER_MERCHANT_TipsGetHandle *tgh) +{ + if (NULL != tgh->job) + GNUNET_CURL_job_cancel (tgh->job); + GNUNET_free (tgh->url); + GNUNET_free (tgh); +} |