commit 3a6001e7d781da004fe23c52f678b8ff620804c1
parent fdc5aa79c200e8417b554f0b8ec58d0f40ed9456
Author: bohdan-potuzhnyi <bohdan.potuzhnyi@gmail.com>
Date: Sun, 28 Sep 2025 00:17:16 +0200
#0010461 adding patch charity/$id
Diffstat:
17 files changed, 1029 insertions(+), 67 deletions(-)
diff --git a/src/donau/Makefile.am b/src/donau/Makefile.am
@@ -45,6 +45,7 @@ donau_httpd_SOURCES = \
donau-httpd_charities_get.c donau_httpd_charity.h \
donau-httpd_charity_delete.c \
donau-httpd_charity_get.c donau-httpd_charity_insert.c \
+ donau-httpd_charity_patch.c \
donau-httpd_history_get.c \
donau-httpd_donation-statement.c donau-httpd_donation-statement.h \
donau-httpd_batch-submit.c donau_httpd_batch-submit.h \
diff --git a/src/donau/donau-httpd.c b/src/donau/donau-httpd.c
@@ -74,7 +74,7 @@
* Above what request latency do we start to log?
*/
#define WARN_LATENCY GNUNET_TIME_relative_multiply ( \
- GNUNET_TIME_UNIT_MILLISECONDS, 500)
+ GNUNET_TIME_UNIT_MILLISECONDS, 500)
/**
* Are clients allowed to request /keys for times other than the
@@ -316,10 +316,14 @@ proceed_with_handler (struct DH_RequestContext *rc,
url);
}
- /* All POST endpoints come with a body in JSON format. So we parse
+ /* All POST and PATCH endpoints come with a body in JSON format. So we parse
the JSON here. */
- if (0 == strcasecmp (rh->method,
- MHD_HTTP_METHOD_POST))
+ const bool is_post = (0 == strcasecmp (rh->method,
+ MHD_HTTP_METHOD_POST));
+ const bool is_patch = (0 == strcasecmp (rh->method,
+ MHD_HTTP_METHOD_PATCH));
+
+ if (is_post || is_patch)
{
enum GNUNET_GenericReturnValue res;
@@ -383,11 +387,14 @@ proceed_with_handler (struct DH_RequestContext *rc,
/* Above logic ensures that 'root' is exactly non-NULL for POST operations,
so we test for 'root' to decide which handler to invoke. */
- if (0 == strcasecmp (rh->method,
- MHD_HTTP_METHOD_POST))
+ if (is_post)
ret = rh->handler.post (rc,
root,
args);
+ else if (is_patch)
+ ret = rh->handler.patch (rc,
+ root,
+ args);
else if (0 == strcasecmp (rh->method,
MHD_HTTP_METHOD_DELETE))
ret = rh->handler.delete (rc,
@@ -511,6 +518,14 @@ handle_mhd_request (void *cls,
.handler.post = &DH_handler_charity_post,
.needs_authorization = true
},
+ /* PATCH charities */
+ {
+ .url = "charities",
+ .method = MHD_HTTP_METHOD_PATCH,
+ .handler.patch = &DH_handler_charity_patch,
+ .nargs = 1,
+ .needs_authorization = true
+ },
/* DELETE charities */
{
.url = "charities",
@@ -583,8 +598,10 @@ handle_mhd_request (void *cls,
}
/* Check if upload is in bounds */
- if (0 == strcasecmp (method,
- MHD_HTTP_METHOD_POST))
+ if ( (0 == strcasecmp (method,
+ MHD_HTTP_METHOD_POST)) ||
+ (0 == strcasecmp (method,
+ MHD_HTTP_METHOD_PATCH)) )
{
TALER_MHD_check_content_length (connection,
TALER_MHD_REQUEST_BUFFER_MAX);
diff --git a/src/donau/donau-httpd.h b/src/donau/donau-httpd.h
@@ -205,6 +205,19 @@ struct DH_RequestHandler
const char *const args[]);
/**
+ * Function to call to handle PATCH requests.
+ *
+ * @param rc context for the request
+ * @param json uploaded JSON data
+ * @param args array of arguments, needs to be of length @e nargs
+ * @return MHD result code
+ */
+ MHD_RESULT
+ (*patch)(struct DH_RequestContext *rc,
+ const json_t *root,
+ const char *const args[]);
+
+ /**
* Function to call to handle DELETE requests.
*
* @param rc context for the request
diff --git a/src/donau/donau-httpd_charity.h b/src/donau/donau-httpd_charity.h
@@ -41,6 +41,21 @@ DH_handler_charity_post (
/**
+ * Handle a PATCH "/charities/$CHARITY_ID" request.
+ *
+ * @param rc request context
+ * @param root uploaded JSON data with updates
+ * @param args array with the charity identifier in args[0]
+ * @return MHD result code
+ */
+MHD_RESULT
+DH_handler_charity_patch (
+ struct DH_RequestContext *rc,
+ const json_t *root,
+ const char *const args[]);
+
+
+/**
* Handle a GET "/charities/$CHARITY_ID" request.
*
* @param rc request context
diff --git a/src/donau/donau-httpd_charity_patch.c b/src/donau/donau-httpd_charity_patch.c
@@ -0,0 +1,162 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2025 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file donau-httpd_charity_patch.c
+ * @brief Handle request to update a charity entry.
+ * @author Bohdan Potuzhnyi
+ */
+#include <donau_config.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <gnunet/gnunet_util_lib.h>
+#include <jansson.h>
+#include <microhttpd.h>
+#include <taler/taler_json_lib.h>
+#include <taler/taler_mhd_lib.h>
+#include <taler/taler_util.h>
+#include "donau-httpd_charity.h"
+#include "donau-httpd_db.h"
+
+
+MHD_RESULT
+DH_handler_charity_patch (struct DH_RequestContext *rc,
+ const json_t *root,
+ const char *const args[])
+{
+ struct DONAU_CharityPublicKeyP charity_pub;
+ uint64_t charity_id;
+ char dummy;
+ const char *charity_name = NULL;
+ const char *charity_url = NULL;
+ struct TALER_Amount max_per_year;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("charity_pub",
+ &charity_pub),
+ GNUNET_JSON_spec_string ("charity_name",
+ &charity_name),
+ GNUNET_JSON_spec_string ("charity_url",
+ &charity_url),
+ TALER_JSON_spec_amount ("max_per_year",
+ DH_currency,
+ &max_per_year),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if ( (NULL == args[0]) ||
+ (1 != sscanf (args[0],
+ "%lu%c",
+ &charity_id,
+ &dummy)) )
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "charity_id");
+ }
+
+ {
+ enum GNUNET_GenericReturnValue res;
+
+ res = TALER_MHD_parse_json_data (rc->connection,
+ root,
+ spec);
+ if (GNUNET_SYSERR == res)
+ return MHD_NO; /* hard failure */
+ if (GNUNET_NO == res)
+ {
+ GNUNET_break_op (0);
+ return MHD_YES; /* failure */
+ }
+ }
+
+ {
+ struct DONAUDB_CharityMetaData meta;
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = DH_plugin->lookup_charity (DH_plugin->cls,
+ charity_id,
+ &meta);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "lookup_charity");
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_DONAU_CHARITY_NOT_FOUND,
+ args[0]);
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ break;
+ }
+
+ if (0 < TALER_amount_cmp (&meta.receipts_to_date,
+ &max_per_year))
+ {
+ GNUNET_break_op (0);
+ GNUNET_free (meta.charity_name);
+ GNUNET_free (meta.charity_url);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "max_per_year must NOT be SMALLER than receipts_to_date");
+ }
+
+ qs = DH_plugin->update_charity (DH_plugin->cls,
+ charity_id,
+ &charity_pub,
+ charity_name,
+ charity_url,
+ &max_per_year);
+ GNUNET_free (meta.charity_name);
+ GNUNET_free (meta.charity_url);
+
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "update_charity");
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_DONAU_CHARITY_NOT_FOUND,
+ args[0]);
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ return TALER_MHD_reply_static (rc->connection,
+ MHD_HTTP_OK,
+ NULL,
+ NULL,
+ 0);
+ }
+ }
+
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+ "charity_patch");
+}
+
+
+/* end of donau-httpd_charity_patch.c */
diff --git a/src/donaudb/Makefile.am b/src/donaudb/Makefile.am
@@ -87,6 +87,7 @@ libdonau_plugin_donaudb_postgres_la_SOURCES = \
pg_get_history.h pg_get_history.c \
pg_get_charities.h pg_get_charities.c \
pg_insert_charity.h pg_insert_charity.c \
+ pg_update_charity.h pg_update_charity.c \
pg_do_charity_delete.h pg_do_charity_delete.c \
pg_insert_history_entry.h pg_insert_history_entry.c \
pg_lookup_charity.h pg_lookup_charity.c \
diff --git a/src/donaudb/pg_update_charity.c b/src/donaudb/pg_update_charity.c
@@ -0,0 +1,63 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2025 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file donaudb/pg_update_charity.c
+ * @brief Implementation of the update_charity function for Postgres
+ * @author Bohdan Potuzhnyi
+ */
+#include <donau_config.h>
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_update_charity.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+DH_PG_update_charity (
+ void *cls,
+ uint64_t charity_id,
+ const struct DONAU_CharityPublicKeyP *charity_pub,
+ const char *charity_name,
+ const char *charity_url,
+ const struct TALER_Amount *max_per_year)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&charity_id),
+ GNUNET_PQ_query_param_auto_from_type (charity_pub),
+ GNUNET_PQ_query_param_string (charity_name),
+ GNUNET_PQ_query_param_string (charity_url),
+ TALER_PQ_query_param_amount (pg->conn,
+ max_per_year),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "update_charity",
+ "UPDATE charities"
+ " SET charity_pub = $2"
+ " ,charity_name = $3"
+ " ,charity_url = $4"
+ " ,max_per_year = $5"
+ " WHERE charity_id = $1;");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "update_charity",
+ params);
+}
+
+
+/* end of pg_update_charity.c */
diff --git a/src/donaudb/pg_update_charity.h b/src/donaudb/pg_update_charity.h
@@ -0,0 +1,47 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2025 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file donaudb/pg_update_charity.h
+ * @brief Implementation of the update_charity function for Postgres
+ * @author Bohdan Potuzhnyi
+ */
+#ifndef PG_UPDATE_CHARITY_H
+#define PG_UPDATE_CHARITY_H
+
+#include <taler/taler_util.h>
+#include "donaudb_plugin.h"
+
+/**
+ * Update an existing charity entry.
+ *
+ * @param cls closure
+ * @param charity_id identifier of the charity to update
+ * @param charity_pub new public key for the charity
+ * @param charity_name new name
+ * @param charity_url new landing page URL
+ * @param max_per_year yearly donation limit
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+DH_PG_update_charity (
+ void *cls,
+ uint64_t charity_id,
+ const struct DONAU_CharityPublicKeyP *charity_pub,
+ const char *charity_name,
+ const char *charity_url,
+ const struct TALER_Amount *max_per_year);
+
+#endif
diff --git a/src/donaudb/plugin_donaudb_postgres.c b/src/donaudb/plugin_donaudb_postgres.c
@@ -54,6 +54,7 @@
#include "pg_lookup_issued_receipts.h"
#include "pg_get_charities.h"
#include "pg_insert_charity.h"
+#include "pg_update_charity.h"
#include "pg_do_charity_delete.h"
/**
@@ -71,14 +72,14 @@
* @param conn SQL connection that was used
*/
#define BREAK_DB_ERR(result,conn) do { \
- GNUNET_break (0); \
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR, \
- "Database failure: %s/%s/%s/%s/%s", \
- PQresultErrorField (result, PG_DIAG_MESSAGE_PRIMARY), \
- PQresultErrorField (result, PG_DIAG_MESSAGE_DETAIL), \
- PQresultErrorMessage (result), \
- PQresStatus (PQresultStatus (result)), \
- PQerrorMessage (conn)); \
+ GNUNET_break (0); \
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR, \
+ "Database failure: %s/%s/%s/%s/%s", \
+ PQresultErrorField (result, PG_DIAG_MESSAGE_PRIMARY), \
+ PQresultErrorField (result, PG_DIAG_MESSAGE_DETAIL), \
+ PQresultErrorMessage (result), \
+ PQresStatus (PQresultStatus (result)), \
+ PQerrorMessage (conn)); \
} while (0)
@@ -236,6 +237,8 @@ libdonau_plugin_donaudb_postgres_init (void *cls)
= &DH_PG_lookup_charity;
plugin->insert_charity
= &DH_PG_insert_charity;
+ plugin->update_charity
+ = &DH_PG_update_charity;
plugin->get_charities
= &DH_PG_get_charities;
plugin->do_charity_delete
diff --git a/src/donaudb/test_donaudb.c b/src/donaudb/test_donaudb.c
@@ -33,25 +33,25 @@ static int result;
* Report line of error if @a cond is true, and jump to label "drop".
*/
#define FAILIF(cond) \
- do { \
- if (! (cond)) { break;} \
- GNUNET_break (0); \
- goto drop; \
- } while (0)
+ do { \
+ if (! (cond)) { break;} \
+ GNUNET_break (0); \
+ goto drop; \
+ } while (0)
/**
* Initializes @a ptr with random data.
*/
#define RND_BLK(ptr) \
- GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, ptr, sizeof (* \
- ptr))
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, ptr, sizeof (* \
+ ptr))
/**
* Initializes @a ptr with zeros.
*/
#define ZR_BLK(ptr) \
- memset (ptr, 0, sizeof (*ptr))
+ memset (ptr, 0, sizeof (*ptr))
/**
* How big do we make the RSA keys?
@@ -238,6 +238,51 @@ run (void *cls)
&charities_cb,
charities));
+ {
+ /* Update the charity and verify the new key and metadata persist. */
+ const char *updated_charity_name = "charity_name_updated";
+ const char *updated_charity_url = "charity_url_updated";
+ struct TALER_Amount updated_max;
+ struct DONAU_CharityPrivateKeyP updated_charity_priv;
+ struct DONAU_CharityPublicKeyP updated_charity_pub;
+
+ GNUNET_CRYPTO_eddsa_key_create (&updated_charity_priv.eddsa_priv);
+ GNUNET_CRYPTO_eddsa_key_get_public (&updated_charity_priv.eddsa_priv,
+ &updated_charity_pub.eddsa_pub);
+
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (CURRENCY ":2.000000",
+ &updated_max));
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->update_charity (plugin->cls,
+ charity_id,
+ &updated_charity_pub,
+ updated_charity_name,
+ updated_charity_url,
+ &updated_max));
+
+ ZR_BLK (&charity_meta);
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->lookup_charity (plugin->cls,
+ charity_id,
+ &charity_meta));
+ GNUNET_assert (0 == GNUNET_memcmp (&charity_meta.charity_pub,
+ &updated_charity_pub));
+ GNUNET_assert (0 == strcmp (charity_meta.charity_name,
+ updated_charity_name));
+ GNUNET_assert (0 == strcmp (charity_meta.charity_url,
+ updated_charity_url));
+ GNUNET_assert (0 == TALER_amount_cmp (&charity_meta.max_per_year,
+ &updated_max));
+ GNUNET_free (charity_meta.charity_name);
+ GNUNET_free (charity_meta.charity_url);
+
+ charity_name = updated_charity_name;
+ charity_url = updated_charity_url;
+ max_per_year = updated_max;
+ charity_pub = updated_charity_pub;
+ }
+
/* test delete charity */
FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
plugin->do_charity_delete (plugin->cls,
diff --git a/src/include/donau_testing_lib.h b/src/include/donau_testing_lib.h
@@ -80,6 +80,27 @@ TALER_TESTING_cmd_charity_post (const char *label,
unsigned int expected_response_code);
/**
+ * Create a PATCH "charity" command.
+ *
+ * @param label the command label.
+ * @param charity_reference reference to an existing charity command
+ * @param name updated name for the charity
+ * @param url updated url for the charity
+ * @param max_per_year updated limit as amount string
+ * @param bearer authorization token
+ * @param expected_response_code expected HTTP response code.
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_charity_patch (const char *label,
+ const char *charity_reference,
+ const char *name,
+ const char *url,
+ const char *max_per_year,
+ const struct DONAU_BearerToken *bearer,
+ unsigned int expected_response_code);
+
+/**
* Create a DELETE "charity" command.
*
* @param label the command label.
@@ -198,24 +219,24 @@ TALER_TESTING_get_donau_url (
* Call #op on all simple traits.
*/
#define DONAU_TESTING_SIMPLE_TRAITS(op) \
- op (charity_priv, const struct DONAU_CharityPrivateKeyP) \
- op (charity_pub, const struct DONAU_CharityPublicKeyP) \
- op (charity_id, const uint64_t) \
- op (donau_url, const char) \
- op (donau_keys, struct DONAU_Keys) \
- op (donor_salt, const char) \
- op (donor_tax_id, const char) \
- op (salted_tax_id_hash, const struct DONAU_HashDonorTaxId) \
- op (donation_receipts, const struct DONAU_DonationReceipt*) \
- op (number_receipts, const size_t)
+ op (charity_priv, const struct DONAU_CharityPrivateKeyP) \
+ op (charity_pub, const struct DONAU_CharityPublicKeyP) \
+ op (charity_id, const uint64_t) \
+ op (donau_url, const char) \
+ op (donau_keys, struct DONAU_Keys) \
+ op (donor_salt, const char) \
+ op (donor_tax_id, const char) \
+ op (salted_tax_id_hash, const struct DONAU_HashDonorTaxId) \
+ op (donation_receipts, const struct DONAU_DonationReceipt*) \
+ op (number_receipts, const size_t)
/**
* Call #op on all indexed traits.
*/
#define DONAU_TESTING_INDEXED_TRAITS(op) \
- op (donation_unit_pub, const struct DONAU_DonationUnitInformation) \
- op (donau_pub, const struct TALER_ExchangePublicKeyP)
+ op (donation_unit_pub, const struct DONAU_DonationUnitInformation) \
+ op (donau_pub, const struct TALER_ExchangePublicKeyP)
DONAU_TESTING_SIMPLE_TRAITS (TALER_TESTING_MAKE_DECL_SIMPLE_TRAIT)
DONAU_TESTING_INDEXED_TRAITS (TALER_TESTING_MAKE_DECL_INDEXED_TRAIT)
diff --git a/src/include/donaudb_plugin.h b/src/include/donaudb_plugin.h
@@ -216,7 +216,7 @@ struct DONAUDB_Plugin
* @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure
*/
enum GNUNET_GenericReturnValue
- (*drop_tables)(void *cls);
+ (*drop_tables)(void *cls);
/**
* Create the necessary tables if they are not present
@@ -229,7 +229,7 @@ struct DONAUDB_Plugin
* @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure
*/
enum GNUNET_GenericReturnValue
- (*create_tables)(void *cls);
+ (*create_tables)(void *cls);
/**
@@ -241,8 +241,8 @@ struct DONAUDB_Plugin
* @return #GNUNET_OK on success
*/
enum GNUNET_GenericReturnValue
- (*start)(void *cls,
- const char *name);
+ (*start)(void *cls,
+ const char *name);
/**
@@ -254,8 +254,8 @@ struct DONAUDB_Plugin
* @return #GNUNET_OK on success
*/
enum GNUNET_GenericReturnValue
- (*start_read_committed)(void *cls,
- const char *name);
+ (*start_read_committed)(void *cls,
+ const char *name);
/**
* Start a READ ONLY serializable transaction.
@@ -266,8 +266,8 @@ struct DONAUDB_Plugin
* @return #GNUNET_OK on success
*/
enum GNUNET_GenericReturnValue
- (*start_read_only)(void *cls,
- const char *name);
+ (*start_read_only)(void *cls,
+ const char *name);
/**
@@ -277,7 +277,7 @@ struct DONAUDB_Plugin
* @return transaction status
*/
enum GNUNET_DB_QueryStatus
- (*commit)(void *cls);
+ (*commit)(void *cls);
/**
@@ -291,7 +291,7 @@ struct DONAUDB_Plugin
* #GNUNET_SYSERR on hard errors
*/
enum GNUNET_GenericReturnValue
- (*preflight)(void *cls);
+ (*preflight)(void *cls);
/**
@@ -312,7 +312,7 @@ struct DONAUDB_Plugin
* #GNUNET_SYSERR on DB errors
*/
enum GNUNET_GenericReturnValue
- (*gc)(void *cls);
+ (*gc)(void *cls);
/**
@@ -367,7 +367,7 @@ struct DONAUDB_Plugin
* @return database transaction status
*/
enum GNUNET_DB_QueryStatus
- (*lookup_charity)(
+ (*lookup_charity)(
void *cls,
uint64_t charity_id,
struct DONAUDB_CharityMetaData *meta);
@@ -382,7 +382,7 @@ struct DONAUDB_Plugin
* @return database transaction status
*/
enum GNUNET_DB_QueryStatus
- (*do_charity_delete)(
+ (*do_charity_delete)(
void *cls,
uint64_t charity_id);
@@ -395,7 +395,7 @@ struct DONAUDB_Plugin
* @return database transaction status
*/
enum GNUNET_DB_QueryStatus
- (*get_charities)(
+ (*get_charities)(
void *cls,
DONAUDB_GetCharitiesCallback cb,
void *cb_cls);
@@ -414,7 +414,7 @@ struct DONAUDB_Plugin
* @return database transaction status
*/
enum GNUNET_DB_QueryStatus
- (*insert_charity)(
+ (*insert_charity)(
void *cls,
const struct DONAU_CharityPublicKeyP *charity_pub,
const char *charity_name,
@@ -424,6 +424,27 @@ struct DONAUDB_Plugin
/**
+ * Update existing charity meta data.
+ *
+ * @param cls closure
+ * @param charity_id identifier of the charity to update
+ * @param charity_pub new public key
+ * @param charity_name new human-readable name
+ * @param charity_url new URL of the charity
+ * @param max_per_year new yearly donation cap
+ * @return database transaction status
+ */
+ enum GNUNET_DB_QueryStatus
+ (*update_charity)(
+ void *cls,
+ uint64_t charity_id,
+ const struct DONAU_CharityPublicKeyP *charity_pub,
+ const char *charity_name,
+ const char *charity_url,
+ const struct TALER_Amount *max_per_year);
+
+
+ /**
* Iterate donation units.
*
* @param cls closure
@@ -432,7 +453,7 @@ struct DONAUDB_Plugin
* @return database transaction status
*/
enum GNUNET_DB_QueryStatus
- (*iterate_donation_units)(
+ (*iterate_donation_units)(
void *cls,
DONAUDB_IterateDonationUnitsCallback cb,
void *cb_cls);
@@ -446,7 +467,7 @@ struct DONAUDB_Plugin
* @return database transaction status
*/
enum GNUNET_DB_QueryStatus
- (*get_history)(
+ (*get_history)(
void *cls,
DONAUDB_GetHistoryCallback cb,
void *cb_cls);
@@ -460,7 +481,7 @@ struct DONAUDB_Plugin
* @return database transaction status
*/
enum GNUNET_DB_QueryStatus
- (*lookup_history_entry)(
+ (*lookup_history_entry)(
void *cls,
const unsigned long long charity_id,
const struct TALER_Amount *final_amount,
@@ -474,7 +495,7 @@ struct DONAUDB_Plugin
* @return database transaction status
*/
enum GNUNET_DB_QueryStatus
- (*insert_donation_unit)(
+ (*insert_donation_unit)(
void *cls,
const struct DONAU_DonationUnitHashP *h_donation_unit_pub,
const struct DONAU_DonationUnitPublicKey *donation_unit_pub,
@@ -491,7 +512,7 @@ struct DONAUDB_Plugin
* @return transaction status code
*/
enum GNUNET_DB_QueryStatus
- (*insert_history_entry)(
+ (*insert_history_entry)(
void *cls,
const uint64_t charity_id,
const struct TALER_Amount *final_amount,
@@ -510,7 +531,7 @@ struct DONAUDB_Plugin
* @return transaction status code
*/
enum GNUNET_DB_QueryStatus
- (*insert_issued_receipt)(
+ (*insert_issued_receipt)(
void *cls,
const size_t num_blinded_sig,
const struct DONAU_BlindedDonationUnitSignature signatures[num_blinded_sig],
@@ -531,7 +552,7 @@ struct DONAUDB_Plugin
* @return transaction status code
*/
enum GNUNET_DB_QueryStatus
- (*insert_submitted_receipts)(
+ (*insert_submitted_receipts)(
void *cls,
struct DONAU_HashDonorTaxId *h_donor_tax_id,
size_t num_dr,
@@ -546,7 +567,7 @@ struct DONAUDB_Plugin
* @return database transaction status
*/
enum GNUNET_DB_QueryStatus
- (*iterate_submitted_receipts)(
+ (*iterate_submitted_receipts)(
void *cls,
const uint64_t donation_year,
const struct DONAU_HashDonorTaxId *h_donor_tax_id,
@@ -560,7 +581,7 @@ struct DONAUDB_Plugin
* @param value the amount of the donation unit
*/
enum GNUNET_DB_QueryStatus
- (*lookup_donation_unit_amount)(
+ (*lookup_donation_unit_amount)(
void *cls,
const struct DONAU_DonationUnitHashP *h_donation_unit_pub,
struct TALER_Amount *value);
@@ -574,7 +595,7 @@ struct DONAUDB_Plugin
* @return transaction status code
*/
enum GNUNET_DB_QueryStatus
- (*lookup_issued_receipts)(
+ (*lookup_issued_receipts)(
void *cls,
struct DONAU_DonationReceiptHashP *h_receitps,
struct DONAUDB_IssuedReceiptsMetaData *meta);
@@ -588,7 +609,7 @@ struct DONAUDB_Plugin
* @return transaction status code
*/
enum GNUNET_DB_QueryStatus
- (*insert_signing_key)(
+ (*insert_signing_key)(
void *cls,
const struct DONAU_DonauPublicKeyP *donau_pub,
struct DONAUDB_SignkeyMetaData *meta);
@@ -602,7 +623,7 @@ struct DONAUDB_Plugin
* @return transaction status code
*/
enum GNUNET_DB_QueryStatus
- (*lookup_signing_key)(
+ (*lookup_signing_key)(
void *cls,
const struct DONAU_DonauPublicKeyP *donau_pub,
struct DONAUDB_SignkeyMetaData *meta);
@@ -616,7 +637,7 @@ struct DONAUDB_Plugin
* @return database transaction status
*/
enum GNUNET_DB_QueryStatus
- (*iterate_active_signing_keys)(
+ (*iterate_active_signing_keys)(
void *cls,
DONAUDB_IterateActiveSigningKeysCallback cb,
void *cb_cls);
diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am
@@ -23,6 +23,7 @@ libdonau_la_SOURCES = \
donau_api_handle.c \
donau_api_charity_get.c \
donau_api_charity_post.c \
+ donau_api_charity_patch.c \
donau_api_charity_delete.c \
donau_api_charities_get.c \
donau_api_curl_defaults.c donau_api_curl_defaults.h \
diff --git a/src/lib/donau_api_charity_patch.c b/src/lib/donau_api_charity_patch.c
@@ -0,0 +1,273 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2025 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published
+ by the Free Software Foundation; either version 3, or (at your
+ option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+
+/**
+ * @file lib/donau_api_charity_patch.c
+ * @brief Implementation of the PATCH /charities/$ID call for the donau HTTP API
+ * @author Bohdan Potuzhnyi
+ */
+#include <jansson.h>
+#include <microhttpd.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include <taler/taler_curl_lib.h>
+#include <taler/taler_json_lib.h>
+#include "donau_service.h"
+#include "donau_api_curl_defaults.h"
+#include "donau_json_lib.h"
+
+
+/**
+ * Handle for a PATCH /charities/$ID request.
+ */
+struct DONAU_CharityPatchHandle
+{
+ /**
+ * Fully qualified request URL.
+ */
+ char *url;
+
+ /**
+ * CURL job for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Callback for the response.
+ */
+ DONAU_PatchCharityResponseCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Reference to the CURL context.
+ */
+ struct GNUNET_CURL_Context *ctx;
+
+ /**
+ * Helper context for POST-style uploads.
+ */
+ struct TALER_CURL_PostContext post_ctx;
+};
+
+
+/**
+ * Finalizer called once the PATCH request is complete.
+ */
+static void
+handle_charity_patch_finished (void *cls,
+ long response_code,
+ const void *resp_obj)
+{
+ struct DONAU_CharityPatchHandle *cph = cls;
+ const json_t *j = resp_obj;
+ struct DONAU_PatchCharityResponse pcresp = {
+ .hr.reply = j,
+ .hr.http_status = (unsigned int) response_code
+ };
+
+ cph->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ pcresp.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ GNUNET_break (0);
+ break;
+ case MHD_HTTP_OK:
+ case MHD_HTTP_NO_CONTENT:
+ /* nothing further to parse */
+ break;
+ case MHD_HTTP_BAD_REQUEST:
+ case MHD_HTTP_NOT_FOUND:
+ case MHD_HTTP_FORBIDDEN:
+ case MHD_HTTP_UNAUTHORIZED:
+ case MHD_HTTP_CONFLICT:
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ pcresp.hr.ec = TALER_JSON_get_error_code (j);
+ pcresp.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ default:
+ pcresp.hr.ec = TALER_JSON_get_error_code (j);
+ pcresp.hr.hint = TALER_JSON_get_error_hint (j);
+ GNUNET_break_op (0);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %ld for PATCH %s\n",
+ response_code,
+ cph->url);
+ break;
+ }
+
+ if (NULL != cph->cb)
+ {
+ cph->cb (cph->cb_cls,
+ &pcresp);
+ cph->cb = NULL;
+ }
+ DONAU_charity_patch_cancel (cph);
+}
+
+
+struct DONAU_CharityPatchHandle *
+DONAU_charity_patch (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ const uint64_t charity_id,
+ const char *charity_name,
+ const char *charity_url,
+ const struct TALER_Amount *max_per_year,
+ const struct DONAU_CharityPublicKeyP *charity_pub,
+ const struct DONAU_BearerToken *bearer,
+ DONAU_PatchCharityResponseCallback cb,
+ void *cb_cls)
+{
+ struct DONAU_CharityPatchHandle *cph;
+ CURL *eh;
+ json_t *body;
+
+ if ( (NULL == charity_name) ||
+ (NULL == charity_url) ||
+ (NULL == max_per_year) ||
+ (NULL == charity_pub) )
+ {
+ /* Caller must provide the complete CharityRequest payload. */
+ GNUNET_break_op (0);
+ return NULL;
+ }
+
+ body = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("charity_pub",
+ charity_pub),
+ GNUNET_JSON_pack_string ("charity_url",
+ charity_url),
+ GNUNET_JSON_pack_string ("charity_name",
+ charity_name),
+ TALER_JSON_pack_amount ("max_per_year",
+ max_per_year));
+ if (NULL == body)
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+
+ cph = GNUNET_new (struct DONAU_CharityPatchHandle);
+ cph->ctx = ctx;
+ cph->cb = cb;
+ cph->cb_cls = cb_cls;
+
+ {
+ char *path;
+
+ GNUNET_asprintf (&path,
+ "charities/%llu",
+ (unsigned long long) charity_id);
+ cph->url = TALER_url_join (url,
+ path,
+ NULL);
+ GNUNET_free (path);
+ }
+ if (NULL == cph->url)
+ {
+ json_decref (body);
+ GNUNET_free (cph);
+ GNUNET_break (0);
+ return NULL;
+ }
+
+ eh = DONAU_curl_easy_get_ (cph->url);
+ if (NULL == eh)
+ {
+ json_decref (body);
+ GNUNET_free (cph->url);
+ GNUNET_free (cph);
+ GNUNET_break (0);
+ return NULL;
+ }
+ if (GNUNET_OK !=
+ TALER_curl_easy_post (&cph->post_ctx,
+ eh,
+ body))
+ {
+ GNUNET_break (0);
+ curl_easy_cleanup (eh);
+ json_decref (body);
+ GNUNET_free (cph->url);
+ GNUNET_free (cph);
+ return NULL;
+ }
+ json_decref (body);
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_CUSTOMREQUEST,
+ MHD_HTTP_METHOD_PATCH));
+ cph->job = GNUNET_CURL_job_add2 (ctx,
+ eh,
+ cph->post_ctx.headers,
+ &handle_charity_patch_finished,
+ cph);
+ if (NULL == cph->job)
+ {
+ GNUNET_break (0);
+ TALER_curl_easy_post_finished (&cph->post_ctx);
+ curl_easy_cleanup (eh);
+ GNUNET_free (cph->url);
+ GNUNET_free (cph);
+ return NULL;
+ }
+
+ if (NULL != bearer)
+ {
+ struct curl_slist *auth;
+ char *hdr;
+
+ GNUNET_asprintf (&hdr,
+ "%s: Bearer %s",
+ MHD_HTTP_HEADER_AUTHORIZATION,
+ bearer->token);
+ auth = curl_slist_append (NULL,
+ hdr);
+ GNUNET_free (hdr);
+ GNUNET_CURL_extend_headers (cph->job,
+ auth);
+ curl_slist_free_all (auth);
+ }
+
+ return cph;
+}
+
+
+void
+DONAU_charity_patch_cancel (
+ struct DONAU_CharityPatchHandle *cph)
+{
+ if (NULL == cph)
+ return;
+ if (NULL != cph->job)
+ {
+ GNUNET_CURL_job_cancel (cph->job);
+ cph->job = NULL;
+ }
+ TALER_curl_easy_post_finished (&cph->post_ctx);
+ GNUNET_free (cph->url);
+ GNUNET_free (cph);
+}
+
+
+/* end of donau_api_charity_patch.c */
diff --git a/src/testing/Makefile.am b/src/testing/Makefile.am
@@ -24,6 +24,7 @@ libdonautesting_la_SOURCES = \
testing_api_cmd_charities_get.c \
testing_api_cmd_charity_get.c \
testing_api_cmd_charity_post.c \
+ testing_api_cmd_charity_patch.c \
testing_api_cmd_charity_delete.c \
testing_api_cmd_issue_receipts.c \
testing_api_cmd_submit_receipts.c \
@@ -78,4 +79,3 @@ EXTRA_DIST = \
coins-cs.conf \
coins-rsa.conf \
test_donau_api.conf
-
diff --git a/src/testing/test_donau_api.c b/src/testing/test_donau_api.c
@@ -80,12 +80,22 @@ run (void *cls,
TALER_TESTING_cmd_charity_get ("get-charity-by-id",
"post-charity", // cmd trait reference
MHD_HTTP_OK),
+ TALER_TESTING_cmd_charity_patch ("patch-charity",
+ "post-charity",
+ "example-updated",
+ "example.org",
+ "EUR:15",
+ &bearer,
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_charity_get ("get-charity-after-patch",
+ "patch-charity",
+ MHD_HTTP_OK),
TALER_TESTING_cmd_charities_get ("get-charities",
&bearer,
MHD_HTTP_OK),
// FIXME: CSR signatures
TALER_TESTING_cmd_issue_receipts ("issue-receipts",
- "post-charity",
+ "patch-charity",
uses_cs,
2025,
"7560001010000", // tax id
@@ -100,7 +110,7 @@ run (void *cls,
2025,
MHD_HTTP_OK),
TALER_TESTING_cmd_charity_delete ("delete-charity",
- "post-charity", // cmd trait reference
+ "patch-charity", // cmd trait reference
&bearer,
MHD_HTTP_NO_CONTENT),
/* End the suite. */
diff --git a/src/testing/testing_api_cmd_charity_patch.c b/src/testing/testing_api_cmd_charity_patch.c
@@ -0,0 +1,269 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2025 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 3, or
+ any later version.
+
+ TALER is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>.
+*/
+/**
+ * @file testing/testing_api_cmd_charity_patch.c
+ * @brief Implement the PATCH /charities/$ID test command.
+ * @author Bohdan Potuzhnyi
+ */
+#include <donau_config.h>
+#include <taler/taler_json_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include <taler/taler_testing_lib.h>
+#include "donau_testing_lib.h"
+
+
+/**
+ * Command state for PATCH /charities/$ID.
+ */
+struct CharityPatchState
+{
+ /**
+ * Handle for the in-flight PATCH request (if any).
+ */
+ struct DONAU_CharityPatchHandle *cph;
+
+ /**
+ * Reference label of the command that created the original charity.
+ */
+ const char *charity_reference;
+
+ /**
+ * Fresh charity key pair generated for the update.
+ */
+ struct DONAU_CharityPrivateKeyP charity_priv;
+
+ /**
+ * Corresponding public key transmitted in the PATCH body.
+ */
+ struct DONAU_CharityPublicKeyP charity_pub;
+
+ /**
+ * Updated yearly donation limit.
+ */
+ struct TALER_Amount max_per_year;
+
+ /**
+ * Updated human-readable name.
+ */
+ const char *charity_name;
+
+ /**
+ * Updated contact URL.
+ */
+ const char *charity_url;
+
+ /**
+ * Administrator bearer token used for authentication.
+ */
+ const struct DONAU_BearerToken *bearer;
+
+ /**
+ * Expected HTTP status code for the PATCH response.
+ */
+ unsigned int expected_response_code;
+
+ /**
+ * Database identifier of the charity being updated.
+ */
+ uint64_t charity_id;
+
+ /**
+ * Interpreter instance driving the command sequence.
+ */
+ struct TALER_TESTING_Interpreter *is;
+};
+
+
+/**
+ * Check HTTP response for the PATCH request and advance the interpreter on success.
+ *
+ * @param cls closure
+ * @param resp HTTP response details
+ */
+static void
+charity_patch_cb (void *cls,
+ const struct DONAU_PatchCharityResponse *resp)
+{
+ struct CharityPatchState *ps = cls;
+
+ ps->cph = NULL;
+ if (ps->expected_response_code != resp->hr.http_status)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected HTTP response code: %u\n",
+ resp->hr.http_status);
+ json_dumpf (resp->hr.reply,
+ stderr,
+ 0);
+ TALER_TESTING_interpreter_fail (ps->is);
+ return;
+ }
+ TALER_TESTING_interpreter_next (ps->is);
+}
+
+
+/**
+ * Run the PATCH command after extracting the referenced charity id.
+ *
+ * @param cls closure
+ * @param cmd command definition
+ * @param is interpreter state
+ */
+static void
+charity_patch_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct CharityPatchState *ps = cls;
+ const struct TALER_TESTING_Command *charity_cmd;
+ const uint64_t *charity_id;
+
+ (void) cmd;
+ ps->is = is;
+
+ charity_cmd =
+ TALER_TESTING_interpreter_lookup_command (is,
+ ps->charity_reference);
+ if ( (NULL == charity_cmd) ||
+ (GNUNET_OK !=
+ TALER_TESTING_get_trait_charity_id (charity_cmd,
+ &charity_id)) )
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ ps->charity_id = *charity_id;
+
+ ps->cph = DONAU_charity_patch (
+ TALER_TESTING_interpreter_get_context (is),
+ TALER_TESTING_get_donau_url (is),
+ ps->charity_id,
+ ps->charity_name,
+ ps->charity_url,
+ &ps->max_per_year,
+ &ps->charity_pub,
+ ps->bearer,
+ &charity_patch_cb,
+ ps);
+ if (NULL == ps->cph)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ }
+}
+
+
+/**
+ * Cancel any outstanding PATCH request and release resources.
+ *
+ * @param cls closure
+ * @param cmd command being cleaned up
+ */
+static void
+charity_patch_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct CharityPatchState *ps = cls;
+
+ if (NULL != ps->cph)
+ {
+ TALER_TESTING_command_incomplete (ps->is,
+ cmd->label);
+ DONAU_charity_patch_cancel (ps->cph);
+ ps->cph = NULL;
+ }
+ GNUNET_free (ps);
+}
+
+
+/**
+ * Offer traits produced by the PATCH command to subsequent commands.
+ *
+ * @param cls closure
+ * @param[out] ret location to store the requested trait
+ * @param trait trait identifier
+ * @param index trait index
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+charity_patch_traits (void *cls,
+ const void **ret,
+ const char *trait,
+ unsigned int index)
+{
+ struct CharityPatchState *ps = cls;
+ struct TALER_TESTING_Trait traits[] = {
+ TALER_TESTING_make_trait_charity_priv (&ps->charity_priv),
+ TALER_TESTING_make_trait_charity_pub (&ps->charity_pub),
+ TALER_TESTING_make_trait_charity_id (&ps->charity_id),
+ TALER_TESTING_trait_end ()
+ };
+
+ return TALER_TESTING_get_trait (traits,
+ ret,
+ trait,
+ index);
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_charity_patch (const char *label,
+ const char *charity_reference,
+ const char *name,
+ const char *url,
+ const char *max_per_year,
+ const struct DONAU_BearerToken *bearer,
+ unsigned int expected_response_code)
+{
+ struct CharityPatchState *ps;
+
+ ps = GNUNET_new (struct CharityPatchState);
+ GNUNET_CRYPTO_eddsa_key_create (&ps->charity_priv.eddsa_priv);
+ GNUNET_CRYPTO_eddsa_key_get_public (&ps->charity_priv.eddsa_priv,
+ &ps->charity_pub.eddsa_pub);
+ ps->charity_reference = charity_reference;
+ ps->charity_name = name;
+ ps->charity_url = url;
+ ps->bearer = bearer;
+ ps->expected_response_code = expected_response_code;
+ if (GNUNET_OK !=
+ TALER_string_to_amount (max_per_year,
+ &ps->max_per_year))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to parse amount `%s' for command %s\n",
+ max_per_year,
+ label);
+ GNUNET_free (ps);
+ GNUNET_assert (0);
+ }
+
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = ps,
+ .label = label,
+ .run = &charity_patch_run,
+ .cleanup = &charity_patch_cleanup,
+ .traits = &charity_patch_traits
+ };
+
+ return cmd;
+ }
+}