feat(webhook): Return events list and total_count on list initial delivery attempt call (#7243)

Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
Amey Wale
2025-03-21 17:31:34 +05:30
committed by GitHub
parent 80218d0f27
commit 4d57f5e62d
9 changed files with 317 additions and 88 deletions

View File

@ -1,3 +1,4 @@
use common_utils::{self, fp_utils};
use error_stack::ResultExt;
use masking::PeekInterface;
use router_env::{instrument, tracing};
@ -11,6 +12,7 @@ use crate::{
};
const INITIAL_DELIVERY_ATTEMPTS_LIST_MAX_LIMIT: i64 = 100;
const INITIAL_DELIVERY_ATTEMPTS_LIST_MAX_DAYS: i64 = 90;
#[derive(Debug)]
enum MerchantAccountOrProfile {
@ -22,16 +24,21 @@ enum MerchantAccountOrProfile {
pub async fn list_initial_delivery_attempts(
state: SessionState,
merchant_id: common_utils::id_type::MerchantId,
constraints: api::webhook_events::EventListConstraints,
) -> RouterResponse<Vec<api::webhook_events::EventListItemResponse>> {
let profile_id = constraints.profile_id.clone();
let constraints =
api::webhook_events::EventListConstraintsInternal::foreign_try_from(constraints)?;
api_constraints: api::webhook_events::EventListConstraints,
) -> RouterResponse<api::webhook_events::TotalEventsResponse> {
let profile_id = api_constraints.profile_id.clone();
let constraints = api::webhook_events::EventListConstraintsInternal::foreign_try_from(
api_constraints.clone(),
)?;
let store = state.store.as_ref();
let key_manager_state = &(&state).into();
let (account, key_store) =
get_account_and_key_store(state.clone(), merchant_id, profile_id).await?;
get_account_and_key_store(state.clone(), merchant_id.clone(), profile_id.clone()).await?;
let now = common_utils::date_time::now();
let events_list_begin_time =
(now.date() - time::Duration::days(INITIAL_DELIVERY_ATTEMPTS_LIST_MAX_DAYS)).midnight();
let events = match constraints {
api_models::webhook_events::EventListConstraintsInternal::ObjectIdFilter { object_id } => {
@ -72,6 +79,33 @@ pub async fn list_initial_delivery_attempts(
_ => None,
};
fp_utils::when(!created_after.zip(created_before).map(|(created_after,created_before)| created_after<=created_before).unwrap_or(true), || {
Err(errors::ApiErrorResponse::InvalidRequestData { message: "The `created_after` timestamp must be an earlier timestamp compared to the `created_before` timestamp".to_string() })
})?;
let created_after = match created_after {
Some(created_after) => {
if created_after < events_list_begin_time {
Err(errors::ApiErrorResponse::InvalidRequestData { message: format!("`created_after` must be a timestamp within the past {INITIAL_DELIVERY_ATTEMPTS_LIST_MAX_DAYS} days.") })
}else{
Ok(created_after)
}
},
None => Ok(events_list_begin_time)
}?;
let created_before = match created_before{
Some(created_before) => {
if created_before < events_list_begin_time{
Err(errors::ApiErrorResponse::InvalidRequestData { message: format!("`created_before` must be a timestamp within the past {INITIAL_DELIVERY_ATTEMPTS_LIST_MAX_DAYS} days.") })
}
else{
Ok(created_before)
}
},
None => Ok(now)
}?;
match account {
MerchantAccountOrProfile::MerchantAccount(merchant_account) => store
.list_initial_events_by_merchant_id_constraints(key_manager_state,
@ -99,11 +133,29 @@ pub async fn list_initial_delivery_attempts(
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Failed to list events with specified constraints")?;
let events = events
.into_iter()
.map(api::webhook_events::EventListItemResponse::try_from)
.collect::<Result<Vec<_>, _>>()?;
let created_after = api_constraints
.created_after
.unwrap_or(events_list_begin_time);
let created_before = api_constraints.created_before.unwrap_or(now);
let total_count = store
.count_initial_events_by_constraints(
&merchant_id,
profile_id,
created_after,
created_before,
)
.await
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Failed to get total events count")?;
Ok(ApplicationResponse::Json(
events
.into_iter()
.map(api::webhook_events::EventListItemResponse::try_from)
.collect::<Result<Vec<_>, _>>()?,
api::webhook_events::TotalEventsResponse::new(total_count, events),
))
}

View File

@ -49,8 +49,8 @@ where
&self,
state: &KeyManagerState,
merchant_id: &common_utils::id_type::MerchantId,
created_after: Option<time::PrimitiveDateTime>,
created_before: Option<time::PrimitiveDateTime>,
created_after: time::PrimitiveDateTime,
created_before: time::PrimitiveDateTime,
limit: Option<i64>,
offset: Option<i64>,
merchant_key_store: &domain::MerchantKeyStore,
@ -77,8 +77,8 @@ where
&self,
state: &KeyManagerState,
profile_id: &common_utils::id_type::ProfileId,
created_after: Option<time::PrimitiveDateTime>,
created_before: Option<time::PrimitiveDateTime>,
created_after: time::PrimitiveDateTime,
created_before: time::PrimitiveDateTime,
limit: Option<i64>,
offset: Option<i64>,
merchant_key_store: &domain::MerchantKeyStore,
@ -92,6 +92,14 @@ where
event: domain::EventUpdate,
merchant_key_store: &domain::MerchantKeyStore,
) -> CustomResult<domain::Event, errors::StorageError>;
async fn count_initial_events_by_constraints(
&self,
merchant_id: &common_utils::id_type::MerchantId,
profile_id: Option<common_utils::id_type::ProfileId>,
created_after: time::PrimitiveDateTime,
created_before: time::PrimitiveDateTime,
) -> CustomResult<i64, errors::StorageError>;
}
#[async_trait::async_trait]
@ -181,8 +189,8 @@ impl EventInterface for Store {
&self,
state: &KeyManagerState,
merchant_id: &common_utils::id_type::MerchantId,
created_after: Option<time::PrimitiveDateTime>,
created_before: Option<time::PrimitiveDateTime>,
created_after: time::PrimitiveDateTime,
created_before: time::PrimitiveDateTime,
limit: Option<i64>,
offset: Option<i64>,
merchant_key_store: &domain::MerchantKeyStore,
@ -292,8 +300,8 @@ impl EventInterface for Store {
&self,
state: &KeyManagerState,
profile_id: &common_utils::id_type::ProfileId,
created_after: Option<time::PrimitiveDateTime>,
created_before: Option<time::PrimitiveDateTime>,
created_after: time::PrimitiveDateTime,
created_before: time::PrimitiveDateTime,
limit: Option<i64>,
offset: Option<i64>,
merchant_key_store: &domain::MerchantKeyStore,
@ -351,6 +359,25 @@ impl EventInterface for Store {
.await
.change_context(errors::StorageError::DecryptionError)
}
async fn count_initial_events_by_constraints(
&self,
merchant_id: &common_utils::id_type::MerchantId,
profile_id: Option<common_utils::id_type::ProfileId>,
created_after: time::PrimitiveDateTime,
created_before: time::PrimitiveDateTime,
) -> CustomResult<i64, errors::StorageError> {
let conn = connection::pg_connection_read(self).await?;
storage::Event::count_initial_attempts_by_constraints(
&conn,
merchant_id,
profile_id,
created_after,
created_before,
)
.await
.map_err(|error| report!(errors::StorageError::from(error)))
}
}
#[async_trait::async_trait]
@ -452,28 +479,21 @@ impl EventInterface for MockDb {
&self,
state: &KeyManagerState,
merchant_id: &common_utils::id_type::MerchantId,
created_after: Option<time::PrimitiveDateTime>,
created_before: Option<time::PrimitiveDateTime>,
created_after: time::PrimitiveDateTime,
created_before: time::PrimitiveDateTime,
limit: Option<i64>,
offset: Option<i64>,
merchant_key_store: &domain::MerchantKeyStore,
) -> CustomResult<Vec<domain::Event>, errors::StorageError> {
let locked_events = self.events.lock().await;
let events_iter = locked_events.iter().filter(|event| {
let mut check = event.merchant_id == Some(merchant_id.to_owned())
&& event.initial_attempt_id.as_ref() == Some(&event.event_id);
if let Some(created_after) = created_after {
check = check && (event.created_at >= created_after);
}
if let Some(created_before) = created_before {
check = check && (event.created_at <= created_before);
}
let check = event.merchant_id == Some(merchant_id.to_owned())
&& event.initial_attempt_id.as_ref() == Some(&event.event_id)
&& (event.created_at >= created_after)
&& (event.created_at <= created_before);
check
});
let offset: usize = if let Some(offset) = offset {
if offset < 0 {
Err(errors::StorageError::MockDbError)?;
@ -590,24 +610,18 @@ impl EventInterface for MockDb {
&self,
state: &KeyManagerState,
profile_id: &common_utils::id_type::ProfileId,
created_after: Option<time::PrimitiveDateTime>,
created_before: Option<time::PrimitiveDateTime>,
created_after: time::PrimitiveDateTime,
created_before: time::PrimitiveDateTime,
limit: Option<i64>,
offset: Option<i64>,
merchant_key_store: &domain::MerchantKeyStore,
) -> CustomResult<Vec<domain::Event>, errors::StorageError> {
let locked_events = self.events.lock().await;
let events_iter = locked_events.iter().filter(|event| {
let mut check = event.business_profile_id == Some(profile_id.to_owned())
&& event.initial_attempt_id.as_ref() == Some(&event.event_id);
if let Some(created_after) = created_after {
check = check && (event.created_at >= created_after);
}
if let Some(created_before) = created_before {
check = check && (event.created_at <= created_before);
}
let check = event.business_profile_id == Some(profile_id.to_owned())
&& event.initial_attempt_id.as_ref() == Some(&event.event_id)
&& (event.created_at >= created_after)
&& (event.created_at <= created_before);
check
});
@ -692,6 +706,32 @@ impl EventInterface for MockDb {
.await
.change_context(errors::StorageError::DecryptionError)
}
async fn count_initial_events_by_constraints(
&self,
merchant_id: &common_utils::id_type::MerchantId,
profile_id: Option<common_utils::id_type::ProfileId>,
created_after: time::PrimitiveDateTime,
created_before: time::PrimitiveDateTime,
) -> CustomResult<i64, errors::StorageError> {
let locked_events = self.events.lock().await;
let iter_events = locked_events.iter().filter(|event| {
let check = event.initial_attempt_id.as_ref() == Some(&event.event_id)
&& (event.merchant_id == Some(merchant_id.to_owned()))
&& (event.business_profile_id == profile_id)
&& (event.created_at >= created_after)
&& (event.created_at <= created_before);
check
});
let events = iter_events.cloned().collect::<Vec<_>>();
i64::try_from(events.len())
.change_context(errors::StorageError::MockDbError)
.attach_printable("Failed to convert usize to i64")
}
}
#[cfg(test)]

View File

@ -768,8 +768,8 @@ impl EventInterface for KafkaStore {
&self,
state: &KeyManagerState,
merchant_id: &id_type::MerchantId,
created_after: Option<PrimitiveDateTime>,
created_before: Option<PrimitiveDateTime>,
created_after: PrimitiveDateTime,
created_before: PrimitiveDateTime,
limit: Option<i64>,
offset: Option<i64>,
merchant_key_store: &domain::MerchantKeyStore,
@ -825,8 +825,8 @@ impl EventInterface for KafkaStore {
&self,
state: &KeyManagerState,
profile_id: &id_type::ProfileId,
created_after: Option<PrimitiveDateTime>,
created_before: Option<PrimitiveDateTime>,
created_after: PrimitiveDateTime,
created_before: PrimitiveDateTime,
limit: Option<i64>,
offset: Option<i64>,
merchant_key_store: &domain::MerchantKeyStore,
@ -862,6 +862,23 @@ impl EventInterface for KafkaStore {
)
.await
}
async fn count_initial_events_by_constraints(
&self,
merchant_id: &id_type::MerchantId,
profile_id: Option<id_type::ProfileId>,
created_after: PrimitiveDateTime,
created_before: PrimitiveDateTime,
) -> CustomResult<i64, errors::StorageError> {
self.diesel_store
.count_initial_events_by_constraints(
merchant_id,
profile_id,
created_after,
created_before,
)
.await
}
}
#[async_trait::async_trait]

View File

@ -1,6 +1,6 @@
pub use api_models::webhook_events::{
EventListConstraints, EventListConstraintsInternal, EventListItemResponse,
EventListRequestInternal, EventRetrieveResponse, OutgoingWebhookRequestContent,
OutgoingWebhookResponseContent, WebhookDeliveryAttemptListRequestInternal,
OutgoingWebhookResponseContent, TotalEventsResponse, WebhookDeliveryAttemptListRequestInternal,
WebhookDeliveryRetryRequestInternal,
};