/*
This file is part of TALER
(C) 2014, 2015, 2016, 2017 INRIA
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 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
*/
/**
* @file backend/taler-merchant-httpd_history.c
* @brief HTTP serving layer mainly intended to communicate with the frontend
* @author Marcello Stanisci
*/
#include "platform.h"
#include
#include
#include
#include "taler-merchant-httpd.h"
/**
* Closure for #pd_cb.
*/
struct ProcessContractClosure
{
/**
* Updated by #pd_cb to build the response.
*/
json_t *response;
/**
* Set to #GNUNET_SYSERR if the database returned a contract
* that was not well-formed.
*/
int failure;
};
/**
* Function called with information about a transaction.
*
* @param cls closure of type `struct ProcessContractClosure`
* @param order_id transaction's order ID.
* @param row_id serial numer of the transaction in the table,
* used as index by the frontend to skip previous results.
*/
static void
pd_cb (void *cls,
const char *order_id,
uint64_t row_id,
const json_t *contract_terms)
{
struct ProcessContractClosure *pcc = cls;
json_t *entry;
json_t *amount;
json_t *timestamp;
json_t *instance;
json_t *summary;
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"/history's row_id: %llu\n",
(unsigned long long) row_id);
summary = NULL;
if (-1 == json_unpack ((json_t *) contract_terms,
"{s:o, s:o, s:{s:o}, s?:o}",
"amount", &amount,
"timestamp", ×tamp,
"merchant", "instance", &instance,
"summary", &summary))
{
GNUNET_break (0);
pcc->failure = GNUNET_SYSERR;
return;
}
/* summary is optional, but we need something, so we use
the order ID if it is not given. */
if (NULL == summary)
summary = json_string (order_id);
if (NULL == (entry =
json_pack ("{s:I, s:s, s:O, s:O, s:O, s:O}",
"row_id", row_id,
"order_id", order_id,
"amount", amount,
"timestamp", timestamp,
"instance", instance,
"summary", summary)))
{
GNUNET_break (0);
pcc->failure = GNUNET_SYSERR;
return;
}
if (0 !=
json_array_append_new (pcc->response,
entry))
{
GNUNET_break (0);
pcc->failure = GNUNET_SYSERR;
json_decref (entry);
return;
}
}
/**
* Manage a /history request. Query the db and returns transactions
* younger than the date given as parameter
*
* @param rh context of the handler
* @param connection the MHD connection to handle
* @param[in,out] connection_cls the connection's closure (can be updated)
* @param upload_data upload data
* @param[in,out] upload_data_size number of bytes (left) in @a upload_data
* @param mi merchant backend instance, never NULL
* @return MHD result code
*/
MHD_RESULT
MH_handler_history (struct TMH_RequestHandler *rh,
struct MHD_Connection *connection,
void **connection_cls,
const char *upload_data,
size_t *upload_data_size,
struct MerchantInstance *mi)
{
#define LOG_INFO(...) GNUNET_log (GNUNET_ERROR_TYPE_INFO, __VA_ARGS__)
const char *str;
struct GNUNET_TIME_Absolute date;
json_t *response;
MHD_RESULT ret;
unsigned int ascending = GNUNET_NO;
unsigned long long seconds;
unsigned long long start = INT64_MAX;
long long delta = -20;
enum GNUNET_DB_QueryStatus qs;
struct ProcessContractClosure pcc;
LOG_INFO ("Serving /history\n");
response = json_array ();
str = MHD_lookup_connection_value (connection,
MHD_GET_ARGUMENT_KIND,
"date");
date = GNUNET_TIME_absolute_get ();
(void) GNUNET_TIME_round_abs (&date);
if (NULL != str)
{
if (1 != sscanf (str,
"%llu",
&seconds))
{
json_decref (response);
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_PARAMETER_MALFORMED,
"date");
}
date.abs_value_us = seconds * 1000LL * 1000LL;
if (date.abs_value_us / 1000LL / 1000LL != seconds)
{
json_decref (response);
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_HISTORY_TIMESTAMP_OVERFLOW,
"Timestamp overflowed");
}
}
/* Sanity check that we don't have some odd stale transaction running */
db->preflight (db->cls);
/* Here goes the cherry-picking logic */
str = MHD_lookup_connection_value (connection,
MHD_GET_ARGUMENT_KIND,
"order_id");
if (NULL != str)
{
pcc.response = response;
pcc.failure = GNUNET_NO;
qs = db->find_contract_terms_history (db->cls,
str,
&mi->pubkey,
&pd_cb,
&pcc);
/* single, read-only SQL statements should never cause
serialization problems */
GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
/* Always report on hard error as well to enable diagnostics */
GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
if ( (0 > qs) ||
(GNUNET_SYSERR == pcc.failure) )
{
json_decref (response);
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_HISTORY_DB_FETCH_ERROR,
"db error to get history");
}
ret = TALER_MHD_reply_json (connection,
response,
MHD_HTTP_OK);
json_decref (response);
return ret;
}
str = MHD_lookup_connection_value (connection,
MHD_GET_ARGUMENT_KIND,
"start");
if (NULL != str)
{
TALER_LOG_DEBUG ("'start' argument given ('%s')\n",
str);
if (1 != sscanf (str,
"%llu",
&start))
{
json_decref (response);
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_PARAMETER_MALFORMED,
"start");
}
}
str = MHD_lookup_connection_value (connection,
MHD_GET_ARGUMENT_KIND,
"delta");
if (NULL != str)
{
if (1 != sscanf (str,
"%lld",
&delta))
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_PARAMETER_MALFORMED,
"delta");
}
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Querying history back to %s, start: %llu, delta: %lld\n",
GNUNET_STRINGS_absolute_time_to_string (date),
start,
delta);
pcc.response = response;
pcc.failure = GNUNET_NO;
str = MHD_lookup_connection_value (connection,
MHD_GET_ARGUMENT_KIND,
"ordering");
if ( (NULL != str) &&
(0 == strcmp ("ascending",
str)) )
ascending = GNUNET_YES;
qs = db->find_contract_terms_by_date_and_range (db->cls,
date,
&mi->pubkey,
start,
llabs (delta),
(delta < 0) ? GNUNET_YES :
GNUNET_NO,
ascending,
&pd_cb,
&pcc);
if ( (0 > qs) ||
(GNUNET_SYSERR == pcc.failure) )
{
/* single, read-only SQL statements should never cause
serialization problems */
GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
/* Always report on hard error as well to enable diagnostics */
GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
json_decref (response);
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_HISTORY_DB_FETCH_ERROR,
"db error to get history");
}
ret = TALER_MHD_reply_json_pack (connection,
MHD_HTTP_OK,
"{ s:o }",
"history",
response /* consumes 'response' */);
LOG_INFO ("/history, http code: %d\n",
MHD_HTTP_OK);
return ret;
}
/* end of taler-merchant-httpd_history.c */