refactor: exposed auth analytics at merchant,org and profile levels (#8335)

This commit is contained in:
Sanskar Atrey
2025-07-01 16:51:35 +05:30
committed by GitHub
parent 69ab255394
commit e638f239d3
17 changed files with 368 additions and 200 deletions

View File

@ -116,11 +116,7 @@ pub mod routes {
)
.service(
web::resource("metrics/auth_events")
.route(web::post().to(get_auth_event_metrics)),
)
.service(
web::resource("metrics/auth_events/sankey")
.route(web::post().to(get_auth_event_sankey)),
.route(web::post().to(get_merchant_auth_event_metrics)),
)
.service(
web::resource("filters/auth_events")
@ -177,6 +173,10 @@ pub mod routes {
web::resource("metrics/sankey")
.route(web::post().to(get_merchant_sankey)),
)
.service(
web::resource("metrics/auth_events/sankey")
.route(web::post().to(get_merchant_auth_event_sankey)),
)
.service(
web::scope("/merchant")
.service(
@ -191,6 +191,10 @@ pub mod routes {
web::resource("metrics/refunds")
.route(web::post().to(get_merchant_refund_metrics)),
)
.service(
web::resource("metrics/auth_events")
.route(web::post().to(get_merchant_auth_event_metrics)),
)
.service(
web::resource("filters/payments")
.route(web::post().to(get_merchant_payment_filters)),
@ -203,6 +207,10 @@ pub mod routes {
web::resource("filters/refunds")
.route(web::post().to(get_merchant_refund_filters)),
)
.service(
web::resource("filters/auth_events")
.route(web::post().to(get_merchant_auth_events_filters)),
)
.service(
web::resource("{domain}/info").route(web::get().to(get_info)),
)
@ -242,6 +250,10 @@ pub mod routes {
.service(
web::resource("metrics/sankey")
.route(web::post().to(get_merchant_sankey)),
)
.service(
web::resource("metrics/auth_events/sankey")
.route(web::post().to(get_merchant_auth_event_sankey)),
),
)
.service(
@ -277,10 +289,18 @@ pub mod routes {
web::resource("metrics/disputes")
.route(web::post().to(get_org_dispute_metrics)),
)
.service(
web::resource("metrics/auth_events")
.route(web::post().to(get_org_auth_event_metrics)),
)
.service(
web::resource("filters/disputes")
.route(web::post().to(get_org_dispute_filters)),
)
.service(
web::resource("filters/auth_events")
.route(web::post().to(get_org_auth_events_filters)),
)
.service(
web::resource("report/dispute")
.route(web::post().to(generate_org_dispute_report)),
@ -300,6 +320,10 @@ pub mod routes {
.service(
web::resource("metrics/sankey")
.route(web::post().to(get_org_sankey)),
)
.service(
web::resource("metrics/auth_events/sankey")
.route(web::post().to(get_org_auth_event_sankey)),
),
)
.service(
@ -335,10 +359,18 @@ pub mod routes {
web::resource("metrics/disputes")
.route(web::post().to(get_profile_dispute_metrics)),
)
.service(
web::resource("metrics/auth_events")
.route(web::post().to(get_profile_auth_event_metrics)),
)
.service(
web::resource("filters/disputes")
.route(web::post().to(get_profile_dispute_filters)),
)
.service(
web::resource("filters/auth_events")
.route(web::post().to(get_profile_auth_events_filters)),
)
.service(
web::resource("connector_event_logs")
.route(web::get().to(get_profile_connector_events)),
@ -379,6 +411,10 @@ pub mod routes {
.service(
web::resource("metrics/sankey")
.route(web::post().to(get_profile_sankey)),
)
.service(
web::resource("metrics/auth_events/sankey")
.route(web::post().to(get_profile_auth_event_sankey)),
),
),
)
@ -1043,7 +1079,7 @@ pub mod routes {
/// # Panics
///
/// Panics if `json_payload` array does not contain one `GetAuthEventMetricRequest` element.
pub async fn get_auth_event_metrics(
pub async fn get_merchant_auth_event_metrics(
state: web::Data<AppState>,
req: actix_web::HttpRequest,
json_payload: web::Json<[GetAuthEventMetricRequest; 1]>,
@ -1062,13 +1098,16 @@ pub mod routes {
&req,
payload,
|state, auth: AuthenticationData, req, _| async move {
analytics::auth_events::get_metrics(
&state.pool,
auth.merchant_account.get_id(),
req,
)
.await
.map(ApplicationResponse::Json)
let org_id = auth.merchant_account.get_org_id();
let merchant_id = auth.merchant_account.get_id();
let auth: AuthInfo = AuthInfo::MerchantLevel {
org_id: org_id.clone(),
merchant_ids: vec![merchant_id.clone()],
};
analytics::auth_events::get_metrics(&state.pool, &auth, req)
.await
.map(ApplicationResponse::Json)
},
&auth::JWTAuth {
permission: Permission::MerchantAnalyticsRead,
@ -1078,6 +1117,98 @@ pub mod routes {
.await
}
#[cfg(feature = "v1")]
/// # Panics
///
/// Panics if `json_payload` array does not contain one `GetAuthEventMetricRequest` element.
pub async fn get_profile_auth_event_metrics(
state: web::Data<AppState>,
req: actix_web::HttpRequest,
json_payload: web::Json<[GetAuthEventMetricRequest; 1]>,
) -> impl Responder {
// safety: This shouldn't panic owing to the data type
#[allow(clippy::expect_used)]
let payload = json_payload
.into_inner()
.to_vec()
.pop()
.expect("Couldn't get GetAuthEventMetricRequest");
let flow = AnalyticsFlow::GetAuthMetrics;
Box::pin(api::server_wrap(
flow,
state,
&req,
payload,
|state, auth: AuthenticationData, req, _| async move {
let org_id = auth.merchant_account.get_org_id();
let merchant_id = auth.merchant_account.get_id();
let profile_id = auth
.profile_id
.ok_or(report!(UserErrors::JwtProfileIdMissing))
.change_context(AnalyticsError::AccessForbiddenError)?;
let auth: AuthInfo = AuthInfo::ProfileLevel {
org_id: org_id.clone(),
merchant_id: merchant_id.clone(),
profile_ids: vec![profile_id.clone()],
};
analytics::auth_events::get_metrics(&state.pool, &auth, req)
.await
.map(ApplicationResponse::Json)
},
&auth::JWTAuth {
permission: Permission::ProfileAnalyticsRead,
},
api_locking::LockAction::NotApplicable,
))
.await
}
#[cfg(feature = "v1")]
/// # Panics
///
/// Panics if `json_payload` array does not contain one `GetAuthEventMetricRequest` element.
pub async fn get_org_auth_event_metrics(
state: web::Data<AppState>,
req: actix_web::HttpRequest,
json_payload: web::Json<[GetAuthEventMetricRequest; 1]>,
) -> impl Responder {
// safety: This shouldn't panic owing to the data type
#[allow(clippy::expect_used)]
let payload = json_payload
.into_inner()
.to_vec()
.pop()
.expect("Couldn't get GetAuthEventMetricRequest");
let flow = AnalyticsFlow::GetAuthMetrics;
Box::pin(api::server_wrap(
flow,
state,
&req,
payload,
|state, auth: AuthenticationData, req, _| async move {
let org_id = auth.merchant_account.get_org_id();
let auth: AuthInfo = AuthInfo::OrgLevel {
org_id: org_id.clone(),
};
analytics::auth_events::get_metrics(&state.pool, &auth, req)
.await
.map(ApplicationResponse::Json)
},
auth::auth_type(
&auth::PlatformOrgAdminAuth {
is_admin_auth_allowed: false,
organization_id: None,
},
&auth::JWTAuth {
permission: Permission::OrganizationAnalyticsRead,
},
req.headers(),
),
api_locking::LockAction::NotApplicable,
))
.await
}
pub async fn get_merchant_payment_filters(
state: web::Data<AppState>,
req: actix_web::HttpRequest,
@ -1120,13 +1251,16 @@ pub mod routes {
&req,
json_payload.into_inner(),
|state, auth: AuthenticationData, req, _| async move {
analytics::auth_events::get_filters(
&state.pool,
req,
auth.merchant_account.get_id(),
)
.await
.map(ApplicationResponse::Json)
let org_id = auth.merchant_account.get_org_id();
let merchant_id = auth.merchant_account.get_id();
let auth: AuthInfo = AuthInfo::MerchantLevel {
org_id: org_id.clone(),
merchant_ids: vec![merchant_id.clone()],
};
analytics::auth_events::get_filters(&state.pool, req, &auth)
.await
.map(ApplicationResponse::Json)
},
&auth::JWTAuth {
permission: Permission::MerchantAnalyticsRead,
@ -1136,6 +1270,80 @@ pub mod routes {
.await
}
#[cfg(feature = "v1")]
pub async fn get_org_auth_events_filters(
state: web::Data<AppState>,
req: actix_web::HttpRequest,
json_payload: web::Json<GetAuthEventFilterRequest>,
) -> impl Responder {
let flow = AnalyticsFlow::GetAuthEventFilters;
Box::pin(api::server_wrap(
flow,
state,
&req,
json_payload.into_inner(),
|state, auth: AuthenticationData, req, _| async move {
let org_id = auth.merchant_account.get_org_id();
let auth: AuthInfo = AuthInfo::OrgLevel {
org_id: org_id.clone(),
};
analytics::auth_events::get_filters(&state.pool, req, &auth)
.await
.map(ApplicationResponse::Json)
},
auth::auth_type(
&auth::PlatformOrgAdminAuth {
is_admin_auth_allowed: false,
organization_id: None,
},
&auth::JWTAuth {
permission: Permission::OrganizationAnalyticsRead,
},
req.headers(),
),
api_locking::LockAction::NotApplicable,
))
.await
}
#[cfg(feature = "v1")]
pub async fn get_profile_auth_events_filters(
state: web::Data<AppState>,
req: actix_web::HttpRequest,
json_payload: web::Json<GetAuthEventFilterRequest>,
) -> impl Responder {
let flow = AnalyticsFlow::GetAuthEventFilters;
Box::pin(api::server_wrap(
flow,
state,
&req,
json_payload.into_inner(),
|state, auth: AuthenticationData, req, _| async move {
let org_id = auth.merchant_account.get_org_id();
let merchant_id = auth.merchant_account.get_id();
let profile_id = auth
.profile_id
.ok_or(report!(UserErrors::JwtProfileIdMissing))
.change_context(AnalyticsError::AccessForbiddenError)?;
let auth: AuthInfo = AuthInfo::ProfileLevel {
org_id: org_id.clone(),
merchant_id: merchant_id.clone(),
profile_ids: vec![profile_id.clone()],
};
analytics::auth_events::get_filters(&state.pool, req, &auth)
.await
.map(ApplicationResponse::Json)
},
&auth::JWTAuth {
permission: Permission::ProfileAnalyticsRead,
},
api_locking::LockAction::NotApplicable,
))
.await
}
#[cfg(feature = "v1")]
pub async fn get_org_payment_filters(
state: web::Data<AppState>,
@ -2809,7 +3017,7 @@ pub mod routes {
.await
}
pub async fn get_auth_event_sankey(
pub async fn get_merchant_auth_event_sankey(
state: web::Data<AppState>,
req: actix_web::HttpRequest,
json_payload: web::Json<TimeRange>,
@ -2840,6 +3048,80 @@ pub mod routes {
.await
}
#[cfg(feature = "v1")]
pub async fn get_org_auth_event_sankey(
state: web::Data<AppState>,
req: actix_web::HttpRequest,
json_payload: web::Json<TimeRange>,
) -> impl Responder {
let flow = AnalyticsFlow::GetSankey;
let payload = json_payload.into_inner();
Box::pin(api::server_wrap(
flow,
state,
&req,
payload,
|state, auth: AuthenticationData, req, _| async move {
let org_id = auth.merchant_account.get_org_id();
let auth: AuthInfo = AuthInfo::OrgLevel {
org_id: org_id.clone(),
};
analytics::auth_events::get_sankey(&state.pool, &auth, req)
.await
.map(ApplicationResponse::Json)
},
auth::auth_type(
&auth::PlatformOrgAdminAuth {
is_admin_auth_allowed: false,
organization_id: None,
},
&auth::JWTAuth {
permission: Permission::OrganizationAnalyticsRead,
},
req.headers(),
),
api_locking::LockAction::NotApplicable,
))
.await
}
#[cfg(feature = "v1")]
pub async fn get_profile_auth_event_sankey(
state: web::Data<AppState>,
req: actix_web::HttpRequest,
json_payload: web::Json<TimeRange>,
) -> impl Responder {
let flow = AnalyticsFlow::GetSankey;
let payload = json_payload.into_inner();
Box::pin(api::server_wrap(
flow,
state,
&req,
payload,
|state, auth: AuthenticationData, req, _| async move {
let org_id = auth.merchant_account.get_org_id();
let merchant_id = auth.merchant_account.get_id();
let profile_id = auth
.profile_id
.ok_or(report!(UserErrors::JwtProfileIdMissing))
.change_context(AnalyticsError::AccessForbiddenError)?;
let auth: AuthInfo = AuthInfo::ProfileLevel {
org_id: org_id.clone(),
merchant_id: merchant_id.clone(),
profile_ids: vec![profile_id.clone()],
};
analytics::auth_events::get_sankey(&state.pool, &auth, req)
.await
.map(ApplicationResponse::Json)
},
&auth::JWTAuth {
permission: Permission::ProfileAnalyticsRead,
},
api_locking::LockAction::NotApplicable,
))
.await
}
#[cfg(feature = "v1")]
pub async fn get_org_sankey(
state: web::Data<AppState>,