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 | |
parent | 7a9c8b206332c3500ffd733b79aa56b0ea01338a (diff) | |
download | merchant-26d5adb7e9ea120b356f40b444f410973c8a83d4.tar.gz merchant-26d5adb7e9ea120b356f40b444f410973c8a83d4.tar.bz2 merchant-26d5adb7e9ea120b356f40b444f410973c8a83d4.zip |
implementation of GET /private/tips
-rw-r--r-- | src/backend/taler-merchant-httpd.c | 7 | ||||
-rw-r--r-- | src/backend/taler-merchant-httpd_private-get-tips.c | 182 | ||||
-rw-r--r-- | src/include/taler_merchant_service.h | 10 | ||||
-rw-r--r-- | src/lib/merchant_api_get_tips.c | 369 |
4 files changed, 563 insertions, 5 deletions
diff --git a/src/backend/taler-merchant-httpd.c b/src/backend/taler-merchant-httpd.c index 9a71440a..a73feabc 100644 --- a/src/backend/taler-merchant-httpd.c +++ b/src/backend/taler-merchant-httpd.c @@ -43,6 +43,7 @@ #include "taler-merchant-httpd_private-get-reserves.h" #include "taler-merchant-httpd_private-get-reserves-ID.h" #include "taler-merchant-httpd_private-get-tips-ID.h" +#include "taler-merchant-httpd_private-get-tips.h" #include "taler-merchant-httpd_private-get-transfers.h" #include "taler-merchant-httpd_private-patch-instances-ID.h" #include "taler-merchant-httpd_private-patch-products-ID.h" @@ -867,6 +868,12 @@ url_handler (void *cls, .method = MHD_HTTP_METHOD_POST, .handler = &TMH_private_post_tips }, + /* GET /tips: */ + { + .url_prefix = "/tips", + .method = MHD_HTTP_METHOD_GET, + .handler = &TMH_private_get_tips + }, /* GET /reserves: */ { .url_prefix = "/reserves", diff --git a/src/backend/taler-merchant-httpd_private-get-tips.c b/src/backend/taler-merchant-httpd_private-get-tips.c index f874f5b5..a98bd110 100644 --- a/src/backend/taler-merchant-httpd_private-get-tips.c +++ b/src/backend/taler-merchant-httpd_private-get-tips.c @@ -18,3 +18,185 @@ * @brief implementation of a GET /private/tips handler * @author Jonathan Buchanan */ +#include "platform.h" +#include "taler-merchant-httpd_private-get-tips.h" + + +/** + * Add tip details to our JSON array. + * + * @param[in,out] cls a `json_t *` JSON array to build + * @param row_id row number of the tip + * @param tip_id ID of the tip + * @param amount the amount of the tip + */ +static void +add_tip (void *cls, + uint64_t row_id, + struct GNUNET_HashCode tip_id, + struct TALER_Amount amount) +{ + json_t *pa = cls; + + GNUNET_assert (0 == + json_array_append_new ( + pa, + json_pack ( + "{s:I, s:o, s:o}", + "row_id", + row_id, + "tip_id", + GNUNET_JSON_from_data_auto (&tip_id), + "amount", + TALER_JSON_from_amount (&amount)))); +} + + +/** + * Convert query argument to @a yna value. + * + * @param connection connection to take query argument from + * @param arg argument to try for + * @param[out] value to set + * @return true on success, false if the parameter was malformed + */ +static bool +arg_to_yna (struct MHD_Connection *connection, + const char *arg, + enum TALER_MERCHANTDB_YesNoAll *yna) +{ + const char *str; + + str = MHD_lookup_connection_value (connection, + MHD_GET_ARGUMENT_KIND, + arg); + if (NULL == str) + { + *yna = TALER_MERCHANTDB_YNA_NO; + return true; + } + if (0 == strcasecmp (str, "yes")) + { + *yna = TALER_MERCHANTDB_YNA_YES; + return true; + } + if (0 == strcasecmp (str, "no")) + { + *yna = TALER_MERCHANTDB_YNA_NO; + return true; + } + if (0 == strcasecmp (str, "all")) + { + *yna = TALER_MERCHANTDB_YNA_ALL; + return true; + } + return false; +} + + +/** + * Handle a GET "/tips/$ID" request. + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[in,out] hc context with further information about the request + * @return MHD result code + */ +MHD_RESULT +TMH_private_get_tips (const struct TMH_RequestHandler *rh, + struct MHD_Connection *connection, + struct TMH_HandlerContext *hc) +{ + json_t *pa; + enum GNUNET_DB_QueryStatus qs; + enum TALER_MERCHANTDB_YesNoAll expired; + uint64_t offset; + int64_t limit; + + if (! (arg_to_yna (connection, /* TODO: put this method in a header somewhere */ + "expired", + &expired)) ) + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_PARAMETER_MALFORMED, + "expired"); + { + const char *offset_str; + + offset_str = MHD_lookup_connection_value (connection, + MHD_GET_ARGUMENT_KIND, + "offset"); + if (NULL == offset_str) + { + offset = UINT64_MAX; + } + else + { + char dummy[2]; + unsigned long long ull; + + if (1 != + sscanf (offset_str, + "%llu%1s", + &ull, + dummy)) + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_PARAMETER_MALFORMED, + "date"); + offset = (uint64_t) ull; + } + } + { + const char *limit_str; + + limit_str = MHD_lookup_connection_value (connection, + MHD_GET_ARGUMENT_KIND, + "limit"); + if (NULL == limit_str) + { + limit = -20; + } + else + { + char dummy[2]; + long long ll; + + if (1 != + sscanf (limit_str, + "%lld%1s", + &ll, + dummy)) + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_PARAMETER_MALFORMED, + "limit"); + limit = (uint64_t) ll; + } + } + + pa = json_array (); + GNUNET_assert (NULL != pa); + qs = TMH_db->lookup_tips (TMH_db->cls, + hc->instance->settings.id, + expired, + limit, + offset, + &add_tip, + pa); + + if (0 > qs) + { + GNUNET_break (0); + json_decref (pa); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_ORDERS_GET_DB_LOOKUP_ERROR, + "failed to lookup tips in database"); + } + + return TALER_MHD_reply_json_pack (connection, + MHD_HTTP_OK, + "{s:o}", + "tips", pa); +} diff --git a/src/include/taler_merchant_service.h b/src/include/taler_merchant_service.h index 4c0fa0c7..9e7504aa 100644 --- a/src/include/taler_merchant_service.h +++ b/src/include/taler_merchant_service.h @@ -2833,7 +2833,7 @@ struct TALER_MERCHANT_TipEntry /** * Row number of the tip in the database. */ - unsigned int row_id; + uint64_t row_id; /** @@ -2886,7 +2886,7 @@ TALER_MERCHANT_tips_get (struct GNUNET_CURL_Context *ctx, * * @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 expired yes for expired tips, no for unexpired tips, all for all 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 @@ -2896,9 +2896,9 @@ TALER_MERCHANT_tips_get (struct GNUNET_CURL_Context *ctx, struct TALER_MERCHANT_TipsGetHandle * TALER_MERCHANT_tips_get2 (struct GNUNET_CURL_Context *ctx, const char *backend_url, - bool include_expired, - int limit, - unsigned int offset, + enum TALER_MERCHANT_YesNoAll expired, + int64_t limit, + uint64_t offset, TALER_MERCHANT_TipsGetCallback cb, void *cb_cls); 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); +} |