feat(router): add mandates list api (#1143)

This commit is contained in:
Sai Harsha Vardhan
2023-05-17 21:31:13 +05:30
committed by GitHub
parent bd8868efd0
commit 75ba3ff09f
8 changed files with 198 additions and 1 deletions

View File

@ -1,5 +1,6 @@
use common_utils::{ext_traits::Encode, pii};
use error_stack::{report, ResultExt};
use futures::future;
use router_env::{instrument, logger, tracing};
use storage_models::enums as storage_enums;
@ -259,6 +260,25 @@ where
Ok(resp)
}
#[instrument(skip(state))]
pub async fn retrieve_mandates_list(
state: &AppState,
merchant_account: storage::MerchantAccount,
constraints: api_models::mandates::MandateListConstraints,
) -> RouterResponse<Vec<api_models::mandates::MandateResponse>> {
let mandates = state
.store
.find_mandates_by_merchant_id(&merchant_account.merchant_id, constraints)
.await
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Unable to retrieve mandates")?;
let mandates_list = future::try_join_all(mandates.into_iter().map(|mandate| {
mandates::MandateResponse::from_db_mandate(state, mandate, &merchant_account)
}))
.await?;
Ok(services::ApplicationResponse::Json(mandates_list))
}
impl ForeignTryFrom<Result<types::PaymentsResponseData, types::ErrorResponse>>
for Option<pii::SecretSerdeValue>
{

View File

@ -4,7 +4,7 @@ use super::{MockDb, Store};
use crate::{
connection,
core::errors::{self, CustomResult},
types::storage,
types::storage::{self, MandateDbExt},
};
#[async_trait::async_trait]
@ -28,6 +28,12 @@ pub trait MandateInterface {
mandate: storage::MandateUpdate,
) -> CustomResult<storage::Mandate, errors::StorageError>;
async fn find_mandates_by_merchant_id(
&self,
merchant_id: &str,
mandate_constraints: api_models::mandates::MandateListConstraints,
) -> CustomResult<Vec<storage::Mandate>, errors::StorageError>;
async fn insert_mandate(
&self,
mandate: storage::MandateNew,
@ -73,6 +79,18 @@ impl MandateInterface for Store {
.into_report()
}
async fn find_mandates_by_merchant_id(
&self,
merchant_id: &str,
mandate_constraints: api_models::mandates::MandateListConstraints,
) -> CustomResult<Vec<storage::Mandate>, errors::StorageError> {
let conn = connection::pg_connection_read(self).await?;
storage::Mandate::filter_by_constraints(&conn, merchant_id, mandate_constraints)
.await
.map_err(Into::into)
.into_report()
}
async fn insert_mandate(
&self,
mandate: storage::MandateNew,
@ -116,6 +134,15 @@ impl MandateInterface for MockDb {
Err(errors::StorageError::MockDbError)?
}
async fn find_mandates_by_merchant_id(
&self,
_merchant_id: &str,
_mandate_constraints: api_models::mandates::MandateListConstraints,
) -> CustomResult<Vec<storage::Mandate>, errors::StorageError> {
// [#172]: Implement function for `MockDb`
Err(errors::StorageError::MockDbError)?
}
async fn insert_mandate(
&self,
_mandate: storage::MandateNew,

View File

@ -365,6 +365,8 @@ impl Mandates {
#[cfg(feature = "olap")]
{
route =
route.service(web::resource("/list").route(web::get().to(retrieve_mandates_list)));
route = route.service(web::resource("/{id}").route(web::get().to(get_mandate)));
}
#[cfg(feature = "oltp")]

View File

@ -87,3 +87,44 @@ pub async fn revoke_mandate(
)
.await
}
/// Mandates - List Mandates
#[utoipa::path(
get,
path = "/mandates/list",
params(
("limit" = Option<i64>, Query, description = "The maximum number of Mandate Objects to include in the response"),
("mandate_status" = Option<MandateStatus>, Query, description = "The status of mandate"),
("connector" = Option<String>, Query, description = "The connector linked to mandate"),
("created_time" = Option<PrimitiveDateTime>, Query, description = "The time at which mandate is created"),
("created_time.lt" = Option<PrimitiveDateTime>, Query, description = "Time less than the mandate created time"),
("created_time.gt" = Option<PrimitiveDateTime>, Query, description = "Time greater than the mandate created time"),
("created_time.lte" = Option<PrimitiveDateTime>, Query, description = "Time less than or equals to the mandate created time"),
("created_time.gte" = Option<PrimitiveDateTime>, Query, description = "Time greater than or equals to the mandate created time"),
),
responses(
(status = 200, description = "The mandate list was retrieved successfully", body = Vec<MandateResponse>),
(status = 401, description = "Unauthorized request")
),
tag = "Mandates",
operation_id = "List Mandates",
security(("api_key" = []))
)]
#[instrument(skip_all, fields(flow = ?Flow::MandatesList))]
pub async fn retrieve_mandates_list(
state: web::Data<AppState>,
req: HttpRequest,
payload: web::Query<api_models::mandates::MandateListConstraints>,
) -> HttpResponse {
let flow = Flow::MandatesList;
let payload = payload.into_inner();
api::server_wrap(
flow,
state.get_ref(),
&req,
payload,
mandate::retrieve_mandates_list,
auth::auth_type(&auth::ApiKeyAuth, &auth::JWTAuth, req.headers()),
)
.await
}

View File

@ -1,3 +1,71 @@
use async_bb8_diesel::AsyncRunQueryDsl;
use common_utils::errors::CustomResult;
use diesel::{associations::HasTable, ExpressionMethods, QueryDsl};
use error_stack::{IntoReport, ResultExt};
pub use storage_models::mandate::{
Mandate, MandateNew, MandateUpdate, MandateUpdateInternal, SingleUseMandate,
};
use storage_models::{errors, schema::mandate::dsl};
use crate::{connection::PgPooledConn, logger, types::transformers::ForeignInto};
#[async_trait::async_trait]
pub trait MandateDbExt: Sized {
async fn filter_by_constraints(
conn: &PgPooledConn,
merchant_id: &str,
mandate_list_constraints: api_models::mandates::MandateListConstraints,
) -> CustomResult<Vec<Self>, errors::DatabaseError>;
}
#[async_trait::async_trait]
impl MandateDbExt for Mandate {
async fn filter_by_constraints(
conn: &PgPooledConn,
merchant_id: &str,
mandate_list_constraints: api_models::mandates::MandateListConstraints,
) -> CustomResult<Vec<Self>, errors::DatabaseError> {
let mut filter = <Self as HasTable>::table()
.filter(dsl::merchant_id.eq(merchant_id.to_owned()))
.order(dsl::created_at.desc())
.into_boxed();
if let Some(created_time) = mandate_list_constraints.created_time {
filter = filter.filter(dsl::created_at.eq(created_time));
}
if let Some(created_time_lt) = mandate_list_constraints.created_time_lt {
filter = filter.filter(dsl::created_at.lt(created_time_lt));
}
if let Some(created_time_gt) = mandate_list_constraints.created_time_gt {
filter = filter.filter(dsl::created_at.gt(created_time_gt));
}
if let Some(created_time_lte) = mandate_list_constraints.created_time_lte {
filter = filter.filter(dsl::created_at.le(created_time_lte));
}
if let Some(created_time_gte) = mandate_list_constraints.created_time_gte {
filter = filter.filter(dsl::created_at.ge(created_time_gte));
}
if let Some(connector) = mandate_list_constraints.connector {
filter = filter.filter(dsl::connector.eq(connector));
}
if let Some(mandate_status) = mandate_list_constraints.mandate_status {
let storage_mandate_status: storage_models::enums::MandateStatus =
mandate_status.foreign_into();
filter = filter.filter(dsl::mandate_status.eq(storage_mandate_status));
}
if let Some(limit) = mandate_list_constraints.limit {
filter = filter.limit(limit);
}
logger::debug!(query = %diesel::debug_query::<diesel::pg::Pg, _>(&filter).to_string());
filter
.get_results_async(conn)
.await
.into_report()
// The query built here returns an empty Vec when no records are found, and if any error does occur,
// it would be an internal database error, due to which we are raising a DatabaseError::Unknown error
.change_context(errors::DatabaseError::Others)
.attach_printable("Error filtering mandates by specified constraints")
}
}

View File

@ -76,6 +76,12 @@ impl ForeignFrom<storage_enums::MandateStatus> for api_enums::MandateStatus {
}
}
impl ForeignFrom<api_enums::MandateStatus> for storage_enums::MandateStatus {
fn foreign_from(status: api_enums::MandateStatus) -> Self {
frunk::labelled_convert_from(status)
}
}
impl ForeignFrom<api_enums::PaymentMethod> for storage_enums::PaymentMethod {
fn foreign_from(pm_type: api_enums::PaymentMethod) -> Self {
frunk::labelled_convert_from(pm_type)