summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJonathan Buchanan <jonathan.russ.buchanan@gmail.com>2020-06-10 01:44:32 -0400
committerJonathan Buchanan <jonathan.russ.buchanan@gmail.com>2020-06-10 01:44:32 -0400
commit26d5adb7e9ea120b356f40b444f410973c8a83d4 (patch)
tree179c5625ba76c1b5cae36031b7f13ede9ae0223d
parent7a9c8b206332c3500ffd733b79aa56b0ea01338a (diff)
downloadmerchant-26d5adb7e9ea120b356f40b444f410973c8a83d4.tar.gz
merchant-26d5adb7e9ea120b356f40b444f410973c8a83d4.tar.bz2
merchant-26d5adb7e9ea120b356f40b444f410973c8a83d4.zip
implementation of GET /private/tips
-rw-r--r--src/backend/taler-merchant-httpd.c7
-rw-r--r--src/backend/taler-merchant-httpd_private-get-tips.c182
-rw-r--r--src/include/taler_merchant_service.h10
-rw-r--r--src/lib/merchant_api_get_tips.c369
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);
+}