diff --git a/src/apis/mod.rs b/src/apis/mod.rs index a159d61..f5efcb9 100644 --- a/src/apis/mod.rs +++ b/src/apis/mod.rs @@ -72,9 +72,10 @@ pub fn urlencode>(s: T) -> String { pub mod access_token_service_api; pub mod api_key_service_api; pub mod auth_service_api; -pub mod org_service_api; -pub mod user_service_api; pub mod employee_service_api; pub mod mfa_service_api; +pub mod org_service_api; +pub(crate) mod user_insights_service_api; +pub mod user_service_api; pub mod configuration; diff --git a/src/apis/user_insights_service_api.rs b/src/apis/user_insights_service_api.rs new file mode 100644 index 0000000..ed81833 --- /dev/null +++ b/src/apis/user_insights_service_api.rs @@ -0,0 +1,185 @@ +/* + * propelauth + * + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 0.1.0 + * + * Generated by: https://openapi-generator.tech + */ + +use reqwest; + +use super::{configuration, Error}; +use crate::apis::ResponseContent; +use crate::models::user_insights::{ + ChartData, ChartMetric, FetchChartDataQuery, FetchReportQuery, OrgReport, OrgReportType, + UserReportPage, UserReportType, +}; +use crate::propelauth::auth::AUTH_HOSTNAME_HEADER; + +/// struct for typed errors of methods [`fetch_user_report`] or [`fetch_org_report`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum FetchUserInsightsDataRequestError { + Status401(serde_json::Value), + Status400(serde_json::Value), + Status429(serde_json::Value), + UnknownValue(serde_json::Value), +} + +pub(crate) async fn fetch_user_report( + configuration: &configuration::Configuration, + report_key: UserReportType, + params: FetchReportQuery, +) -> Result> { + let local_var_configuration = configuration; + + let local_var_client = &local_var_configuration.client; + + let local_var_uri_str = format!( + "{}/api/backend/v1/user_report/{report_key}", + local_var_configuration.base_path, + report_key = report_key.as_str(), + ); + let mut local_var_req_builder = + local_var_client.request(reqwest::Method::GET, local_var_uri_str.as_str()); + + local_var_req_builder = local_var_req_builder.query(¶ms); + + if let Some(ref local_var_user_agent) = local_var_configuration.user_agent { + local_var_req_builder = + local_var_req_builder.header(reqwest::header::USER_AGENT, local_var_user_agent.clone()); + } + if let Some(ref local_var_token) = local_var_configuration.bearer_access_token { + local_var_req_builder = local_var_req_builder.bearer_auth(local_var_token.to_owned()); + }; + local_var_req_builder = local_var_req_builder.header( + AUTH_HOSTNAME_HEADER, + local_var_configuration.auth_hostname.to_owned(), + ); + + let local_var_req = local_var_req_builder.build()?; + let local_var_resp = local_var_client.execute(local_var_req).await?; + + let local_var_status = local_var_resp.status(); + let local_var_content = local_var_resp.text().await?; + + if !local_var_status.is_client_error() && !local_var_status.is_server_error() { + serde_json::from_str(&local_var_content).map_err(Error::from) + } else { + let local_var_entity: Option = + serde_json::from_str(&local_var_content).ok(); + let local_var_error: crate::apis::ResponseContent = + ResponseContent { + status: local_var_status, + content: local_var_content, + entity: local_var_entity, + }; + Err(Error::ResponseError(local_var_error)) + } +} + +pub(crate) async fn fetch_org_report( + configuration: &configuration::Configuration, + report_key: OrgReportType, + params: FetchReportQuery, +) -> Result> { + let local_var_configuration = configuration; + + let local_var_client = &local_var_configuration.client; + + let local_var_uri_str = format!( + "{}/api/backend/v1/org_report/{report_key}", + local_var_configuration.base_path, + report_key = report_key.as_str(), + ); + let mut local_var_req_builder = + local_var_client.request(reqwest::Method::GET, local_var_uri_str.as_str()); + + local_var_req_builder = local_var_req_builder.query(¶ms); + + if let Some(ref local_var_user_agent) = local_var_configuration.user_agent { + local_var_req_builder = + local_var_req_builder.header(reqwest::header::USER_AGENT, local_var_user_agent.clone()); + } + if let Some(ref local_var_token) = local_var_configuration.bearer_access_token { + local_var_req_builder = local_var_req_builder.bearer_auth(local_var_token.to_owned()); + }; + local_var_req_builder = local_var_req_builder.header( + AUTH_HOSTNAME_HEADER, + local_var_configuration.auth_hostname.to_owned(), + ); + + let local_var_req = local_var_req_builder.build()?; + let local_var_resp = local_var_client.execute(local_var_req).await?; + + let local_var_status = local_var_resp.status(); + let local_var_content = local_var_resp.text().await?; + + if !local_var_status.is_client_error() && !local_var_status.is_server_error() { + serde_json::from_str(&local_var_content).map_err(Error::from) + } else { + let local_var_entity: Option = + serde_json::from_str(&local_var_content).ok(); + let local_var_error: crate::apis::ResponseContent = + ResponseContent { + status: local_var_status, + content: local_var_content, + entity: local_var_entity, + }; + Err(Error::ResponseError(local_var_error)) + } +} + +pub(crate) async fn fetch_chart_metric_data( + configuration: &configuration::Configuration, + chart_metric: ChartMetric, + params: FetchChartDataQuery, +) -> Result> { + let local_var_configuration = configuration; + + let local_var_client = &local_var_configuration.client; + + let local_var_uri_str = format!( + "{}/api/backend/v1/chart_metrics/{chart_metric}", + local_var_configuration.base_path, + chart_metric = chart_metric.as_str(), + ); + let mut local_var_req_builder = + local_var_client.request(reqwest::Method::GET, local_var_uri_str.as_str()); + + local_var_req_builder = local_var_req_builder.query(¶ms); + + if let Some(ref local_var_user_agent) = local_var_configuration.user_agent { + local_var_req_builder = + local_var_req_builder.header(reqwest::header::USER_AGENT, local_var_user_agent.clone()); + } + if let Some(ref local_var_token) = local_var_configuration.bearer_access_token { + local_var_req_builder = local_var_req_builder.bearer_auth(local_var_token.to_owned()); + }; + local_var_req_builder = local_var_req_builder.header( + AUTH_HOSTNAME_HEADER, + local_var_configuration.auth_hostname.to_owned(), + ); + + let local_var_req = local_var_req_builder.build()?; + let local_var_resp = local_var_client.execute(local_var_req).await?; + + let local_var_status = local_var_resp.status(); + let local_var_content = local_var_resp.text().await?; + + if !local_var_status.is_client_error() && !local_var_status.is_server_error() { + serde_json::from_str(&local_var_content).map_err(Error::from) + } else { + let local_var_entity: Option = + serde_json::from_str(&local_var_content).ok(); + let local_var_error: crate::apis::ResponseContent = + ResponseContent { + status: local_var_status, + content: local_var_content, + entity: local_var_entity, + }; + Err(Error::ResponseError(local_var_error)) + } +} diff --git a/src/models/mod.rs b/src/models/mod.rs index ac152ac..14e46cb 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -89,6 +89,7 @@ pub mod user_ids_query; pub use self::user_ids_query::UserIdsQuery; pub mod user_in_org; pub use self::user_in_org::UserInOrg; +pub mod user_insights; pub mod user_metadata; pub use self::user_metadata::UserMetadata; pub mod user_paged_response; diff --git a/src/models/user_insights.rs b/src/models/user_insights.rs new file mode 100644 index 0000000..28a15f3 --- /dev/null +++ b/src/models/user_insights.rs @@ -0,0 +1,222 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, PartialEq, Default)] +pub struct ReportPagination { + pub page_size: Option, + pub page_number: Option, +} + +#[derive(Clone, Debug, PartialEq, Serialize)] +pub(crate) struct FetchReportQuery { + pub(crate) report_interval: ReportInterval, + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) page_size: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) page_number: Option, +} + +#[derive(Clone, Debug, PartialEq)] +pub enum OrgReportType { + Attrition, + Growth, + Reengagement, + Churn, +} + +impl OrgReportType { + pub fn as_str(&self) -> &'static str { + match self { + OrgReportType::Attrition => "attrition", + OrgReportType::Growth => "growth", + OrgReportType::Reengagement => "reengagement", + OrgReportType::Churn => "churn", + } + } +} + +#[derive(Clone, Debug, PartialEq)] +pub enum UserReportType { + TopInviter, + Champion, + Reengagement, + Churn, +} + +impl UserReportType { + pub fn as_str(&self) -> &'static str { + match self { + UserReportType::TopInviter => "top_inviter", + UserReportType::Champion => "champion", + UserReportType::Reengagement => "reengagement", + UserReportType::Churn => "churn", + } + } +} + +#[derive(Clone, Debug, PartialEq, Serialize)] +#[serde(untagged)] +pub(crate) enum ReportInterval { + TopInviter(TopInviterReportInterval), + Champion(ChampionReportInterval), + Reengagement(ReengagementReportInterval), + Churn(ChurnReportInterval), + Attrition(AttritionReportInterval), + Growth(GrowthReportInterval), +} + +#[derive(Clone, Debug, PartialEq, Serialize)] +pub enum TopInviterReportInterval { + #[serde(rename = "30")] + ThirtyDays, + #[serde(rename = "60")] + SixtyDays, + #[serde(rename = "90")] + NinetyDays, +} + +#[derive(Clone, Debug, PartialEq, Serialize)] +pub enum ChampionReportInterval { + #[serde(rename = "30")] + ThirtyDays, + #[serde(rename = "60")] + SixtyDays, + #[serde(rename = "90")] + NinetyDays, +} + +#[derive(Clone, Debug, PartialEq, Serialize)] +pub enum ReengagementReportInterval { + #[serde(rename = "Weekly")] + Weekly, + #[serde(rename = "Monthly")] + Monthly, +} + +#[derive(Clone, Debug, PartialEq, Serialize)] +pub enum ChurnReportInterval { + #[serde(rename = "7")] + SevenDays, + #[serde(rename = "14")] + FourteenDays, + #[serde(rename = "30")] + ThirtyDays, +} + +#[derive(Clone, Debug, PartialEq, Serialize)] +pub enum AttritionReportInterval { + #[serde(rename = "30")] + ThirtyDays, + #[serde(rename = "60")] + SixtyDays, + #[serde(rename = "90")] + NinetyDays, +} + +#[derive(Clone, Debug, PartialEq, Serialize)] +pub enum GrowthReportInterval { + #[serde(rename = "30")] + ThirtyDays, + #[serde(rename = "60")] + SixtyDays, + #[serde(rename = "90")] + NinetyDays, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct OrgReportRecord { + pub id: String, + pub report_id: String, + pub org_id: String, + pub name: String, + pub num_users: i32, + pub org_created_at: i64, + pub extra_properties: Option, +} + +#[derive(Deserialize)] +pub struct OrgReport { + pub org_reports: Vec, + pub current_page: i64, + pub total_count: i64, + pub page_size: i64, + pub has_more_results: bool, + pub report_time: i64, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct UserReportRecord { + pub id: String, + pub report_id: String, + pub user_id: String, + pub user_created_at: i64, + pub username: Option, + pub first_name: Option, + pub last_name: Option, + pub email: String, + pub last_active_at: i64, + pub org_data: serde_json::Value, + pub extra_properties: Option, +} +#[derive(Deserialize)] +pub struct UserReportPage { + pub user_reports: Vec, + pub current_page: i64, + pub total_count: i64, + pub page_size: i64, + pub has_more_results: bool, + pub report_time: i64, +} +// chart metrics types + +#[derive(Clone, Debug, PartialEq, Deserialize)] +pub enum ChartMetric { + Signups, + OrgsCreated, + ActiveUsers, + ActiveOrgs, +} + +impl ChartMetric { + pub fn as_str(&self) -> &'static str { + match self { + ChartMetric::Signups => "signups", + ChartMetric::OrgsCreated => "orgs_created", + ChartMetric::ActiveUsers => "active_users", + ChartMetric::ActiveOrgs => "active_orgs", + } + } +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub enum ChartMetricCadence { + #[serde(rename = "Daily")] + Daily, + #[serde(rename = "Weekly")] + Weekly, + #[serde(rename = "Monthly")] + Monthly, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct ChartDataPoint { + pub result: i64, + pub date: String, // YYYY-MM-DD format date, 24 hours in UTC timezone + pub cadence_completed: bool, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct ChartData { + pub metrics: Vec, + pub chart_type: ChartMetric, + pub cadence: ChartMetricCadence, +} + +#[derive(Clone, Debug, PartialEq, Serialize, Default)] +pub struct FetchChartDataQuery { + #[serde(skip_serializing_if = "Option::is_none", rename = "cadence")] + pub cadence: Option, + #[serde(skip_serializing_if = "Option::is_none", rename = "start_date")] + pub start_date: Option, // YYYY-MM-DD format date + #[serde(skip_serializing_if = "Option::is_none", rename = "end_date")] + pub end_date: Option, // YYYY-MM-DD format date +} diff --git a/src/propelauth/auth.rs b/src/propelauth/auth.rs index 71b8b87..55f0ab4 100644 --- a/src/propelauth/auth.rs +++ b/src/propelauth/auth.rs @@ -5,6 +5,7 @@ use crate::apis::configuration::Configuration; use crate::models::AuthTokenVerificationMetadata; use crate::propelauth::access_token::AccessTokenService; use crate::propelauth::api_key::ApiKeyService; +use crate::propelauth::employee::EmployeeService; use crate::propelauth::errors::InitializationError; use crate::propelauth::helpers::map_autogenerated_error; use crate::propelauth::mfa::MfaService; @@ -12,7 +13,7 @@ use crate::propelauth::options::{AuthOptions, AuthOptionsWithTokenVerification}; use crate::propelauth::org::OrgService; use crate::propelauth::token::TokenService; use crate::propelauth::user::UserService; -use crate::propelauth::employee::EmployeeService; +use crate::propelauth::user_insights::UserInsightsService; static BACKEND_API_BASE_URL: &str = "https://propelauth-api.com"; pub(crate) static AUTH_HOSTNAME_HEADER: &str = "X-Propelauth-url"; @@ -124,12 +125,19 @@ impl PropelAuth { } } - /// API requests related to employees. + /// API requests related to mfa. pub fn mfa(&self) -> MfaService { MfaService { config: &self.config, } } + + /// API requests related to user insights data. + pub fn user_insights(&self) -> UserInsightsService { + UserInsightsService { + config: &self.config, + } + } } fn validate_auth_url_extract_hostname(auth_url: &str) -> Result { diff --git a/src/propelauth/errors.rs b/src/propelauth/errors.rs index 7f79515..6189581 100644 --- a/src/propelauth/errors.rs +++ b/src/propelauth/errors.rs @@ -1,8 +1,8 @@ use crate::models::{ BadCreateAccessTokenError, BadCreateMagicLinkRequest, BadCreateOrgRequest, BadCreateUserRequest, BadFetchOrgQuery, BadFetchUsersByQuery, BadFetchUsersInOrgQuery, - BadMigrateUserRequest, BadMigrateUserPasswordRequest, BadUpdateOrgRequest, BadUpdatePasswordRequest, - BadUpdateUserEmailRequest, BadUpdateUserMetadataRequest, + BadMigrateUserPasswordRequest, BadMigrateUserRequest, BadUpdateOrgRequest, + BadUpdatePasswordRequest, BadUpdateUserEmailRequest, BadUpdateUserMetadataRequest, }; use thiserror::Error; @@ -462,7 +462,7 @@ pub enum VerifyStepUpGrantError { #[derive(Error, Debug, PartialEq, Clone)] pub enum SendSmsCodeError { - #[error("Invalid API Key")] + #[error("Invalid API Key")] InvalidApiKey, #[error("Rate limited by PropelAuth")] @@ -511,3 +511,17 @@ pub enum VerifySmsChallengeError { UnexpectedException, } +#[derive(Error, Debug, PartialEq, Clone)] +pub enum FetchUserInsightsError { + #[error("Invalid API Key")] + InvalidApiKey, + + #[error("Invalid parameters: {0}")] + InvalidParams(&'static str), + + #[error("Rate limited by PropelAuth")] + PropelAuthRateLimit, + + #[error("Unexpected exception, please try again")] + UnexpectedException, +} diff --git a/src/propelauth/mfa.rs b/src/propelauth/mfa.rs index c83c014..c3e50fc 100644 --- a/src/propelauth/mfa.rs +++ b/src/propelauth/mfa.rs @@ -1,11 +1,13 @@ use crate::apis::configuration::Configuration; -use crate::apis::mfa_service_api::{StepUpMfaError, VerifySmsChallengeParams, VerifyStepUpGrantParams}; +use crate::apis::mfa_service_api::{SendSmsMfaCodeParams, VerifyTotpChallengeParams}; +use crate::apis::mfa_service_api::{VerifySmsChallengeParams, VerifyStepUpGrantParams}; use crate::apis::Error; -use crate::models::{SendSmsCodeResponse, VerifySmsChallengeResponse, VerifyStepUpGrantResponse}; -use crate::propelauth::errors::{VerifyStepUpGrantError, VerifyStepUpTotpChallengeError, SendSmsCodeError, VerifySmsChallengeError}; -use crate::propelauth::helpers::map_autogenerated_error; use crate::models::VerifyTotpChallengeResponse; -use crate::apis::mfa_service_api::{SendSmsMfaCodeParams, VerifyTotpChallengeParams}; +use crate::models::{SendSmsCodeResponse, VerifySmsChallengeResponse, VerifyStepUpGrantResponse}; +use crate::propelauth::errors::{ + SendSmsCodeError, VerifySmsChallengeError, VerifyStepUpGrantError, + VerifyStepUpTotpChallengeError, +}; pub struct MfaService<'a> { pub(crate) config: &'a Configuration, @@ -66,8 +68,7 @@ impl MfaService<'_> { &self, params: VerifyStepUpGrantParams, ) -> Result { - let result = - crate::apis::mfa_service_api::verify_step_up_grant(&self.config, params).await; + let result = crate::apis::mfa_service_api::verify_step_up_grant(&self.config, params).await; match result { Ok(_) => Ok(VerifyStepUpGrantResponse { success: true }), @@ -85,23 +86,24 @@ impl MfaService<'_> { { match error_code { "invalid_request_fields" => { - if let Some(field_to_errors) = - error_json.get("field_to_errors").and_then(|v| v.as_object()) + if let Some(field_to_errors) = error_json + .get("field_to_errors") + .and_then(|v| v.as_object()) { if let Some(grant_error) = field_to_errors.get("grant").and_then(|v| v.as_str()) { if grant_error == "grant_not_found" { - return Ok(VerifyStepUpGrantResponse { success: false }); + return Ok(VerifyStepUpGrantResponse { + success: false, + }); } } } return Err(VerifyStepUpGrantError::BadRequest(response.content)); } - "feature_gated" => { - return Err(VerifyStepUpGrantError::FeatureGated) - } + "feature_gated" => return Err(VerifyStepUpGrantError::FeatureGated), _ => {} } } @@ -117,8 +119,7 @@ impl MfaService<'_> { &self, params: SendSmsMfaCodeParams, ) -> Result { - let result = - crate::apis::mfa_service_api::send_sms_mfa_code(&self.config, params).await; + let result = crate::apis::mfa_service_api::send_sms_mfa_code(&self.config, params).await; match result { Ok(response) => Ok(response), @@ -135,20 +136,12 @@ impl MfaService<'_> { if let Some(error_code) = error_json.get("error_code").and_then(|v| v.as_str()) { match error_code { - "user_not_found" => { - return Err(SendSmsCodeError::UserNotFound) - } - "mfa_not_enabled" => { - return Err(SendSmsCodeError::MfaNotEnabled) - } + "user_not_found" => return Err(SendSmsCodeError::UserNotFound), + "mfa_not_enabled" => return Err(SendSmsCodeError::MfaNotEnabled), "invalid_request_fields" => { - return Err(SendSmsCodeError::BadRequest( - response.content, - )) - } - "feature_gated" => { - return Err(SendSmsCodeError::FeatureGated) + return Err(SendSmsCodeError::BadRequest(response.content)) } + "feature_gated" => return Err(SendSmsCodeError::FeatureGated), _ => {} } } @@ -163,8 +156,7 @@ impl MfaService<'_> { &self, params: VerifySmsChallengeParams, ) -> Result { - let result = - crate::apis::mfa_service_api::verify_sms_challenge(&self.config, params).await; + let result = crate::apis::mfa_service_api::verify_sms_challenge(&self.config, params).await; match result { Ok(response) => Ok(response), @@ -181,20 +173,14 @@ impl MfaService<'_> { if let Some(error_code) = error_json.get("error_code").and_then(|v| v.as_str()) { match error_code { - "user_not_found" => { - return Err(VerifySmsChallengeError::UserNotFound) - } + "user_not_found" => return Err(VerifySmsChallengeError::UserNotFound), "mfa_not_enabled" => { return Err(VerifySmsChallengeError::MfaNotEnabled) } "invalid_request_fields" => { - return Err(VerifySmsChallengeError::BadRequest( - response.content, - )) - } - "feature_gated" => { - return Err(VerifySmsChallengeError::FeatureGated) + return Err(VerifySmsChallengeError::BadRequest(response.content)) } + "feature_gated" => return Err(VerifySmsChallengeError::FeatureGated), _ => {} } } diff --git a/src/propelauth/mod.rs b/src/propelauth/mod.rs index 2d69864..70459b1 100644 --- a/src/propelauth/mod.rs +++ b/src/propelauth/mod.rs @@ -1,12 +1,13 @@ +pub mod access_token; pub mod api_key; pub mod auth; +pub mod employee; pub mod errors; pub(crate) mod helpers; +pub mod mfa; pub mod options; pub mod org; pub mod token; pub mod token_models; pub mod user; -pub mod access_token; -pub mod employee; -pub mod mfa; \ No newline at end of file +pub mod user_insights; diff --git a/src/propelauth/user_insights.rs b/src/propelauth/user_insights.rs new file mode 100644 index 0000000..4b95d96 --- /dev/null +++ b/src/propelauth/user_insights.rs @@ -0,0 +1,309 @@ +use crate::apis::configuration::Configuration; +use crate::apis::{user_insights_service_api, Error}; +use crate::models::user_insights::{ + AttritionReportInterval, ChampionReportInterval, ChartMetric, ChurnReportInterval, + FetchChartDataQuery, FetchReportQuery, GrowthReportInterval, OrgReportType, + ReengagementReportInterval, ReportInterval, ReportPagination, TopInviterReportInterval, + UserReportPage, UserReportType, +}; +use crate::propelauth::errors::FetchUserInsightsError; + +pub struct UserInsightsService<'a> { + pub(crate) config: &'a Configuration, +} + +impl UserInsightsService<'_> { + pub async fn fetch_user_top_inviter_report( + &self, + report_interval: TopInviterReportInterval, + pagination: ReportPagination, + ) -> Result { + let result = user_insights_service_api::fetch_user_report( + &self.config, + UserReportType::TopInviter, + FetchReportQuery { + report_interval: ReportInterval::TopInviter(report_interval), + page_size: pagination.page_size, + page_number: pagination.page_number, + }, + ) + .await; + + match result { + Ok(response) => Ok(response), + Err(Error::ResponseError(response)) => { + if response.status == 401 { + return Err(FetchUserInsightsError::InvalidApiKey); + } else if response.status == 429 { + return Err(FetchUserInsightsError::PropelAuthRateLimit); + } else { + Err(FetchUserInsightsError::UnexpectedException) + } + } + Err(_) => Err(FetchUserInsightsError::UnexpectedException), + } + } + + pub async fn fetch_user_champion_report( + &self, + report_interval: ChampionReportInterval, + pagination: ReportPagination, + ) -> Result { + let result = user_insights_service_api::fetch_user_report( + &self.config, + UserReportType::Champion, + FetchReportQuery { + report_interval: ReportInterval::Champion(report_interval), + page_size: pagination.page_size, + page_number: pagination.page_number, + }, + ) + .await; + + match result { + Ok(response) => Ok(response), + Err(Error::ResponseError(response)) => { + if response.status == 401 { + return Err(FetchUserInsightsError::InvalidApiKey); + } else if response.status == 429 { + return Err(FetchUserInsightsError::PropelAuthRateLimit); + } else { + Err(FetchUserInsightsError::UnexpectedException) + } + } + Err(_) => Err(FetchUserInsightsError::UnexpectedException), + } + } + + pub async fn fetch_user_reengagement_report( + &self, + report_interval: ReengagementReportInterval, + pagination: ReportPagination, + ) -> Result { + let result = user_insights_service_api::fetch_user_report( + &self.config, + UserReportType::Reengagement, + FetchReportQuery { + report_interval: ReportInterval::Reengagement(report_interval), + page_size: pagination.page_size, + page_number: pagination.page_number, + }, + ) + .await; + + match result { + Ok(response) => Ok(response), + Err(Error::ResponseError(response)) => { + if response.status == 401 { + return Err(FetchUserInsightsError::InvalidApiKey); + } else if response.status == 429 { + return Err(FetchUserInsightsError::PropelAuthRateLimit); + } else { + Err(FetchUserInsightsError::UnexpectedException) + } + } + Err(_) => Err(FetchUserInsightsError::UnexpectedException), + } + } + + pub async fn fetch_user_churn_report( + &self, + report_interval: ChurnReportInterval, + pagination: ReportPagination, + ) -> Result { + let result = user_insights_service_api::fetch_user_report( + &self.config, + UserReportType::Churn, + FetchReportQuery { + report_interval: ReportInterval::Churn(report_interval), + page_size: pagination.page_size, + page_number: pagination.page_number, + }, + ) + .await; + + match result { + Ok(response) => Ok(response), + Err(Error::ResponseError(response)) => { + if response.status == 401 { + return Err(FetchUserInsightsError::InvalidApiKey); + } else if response.status == 429 { + return Err(FetchUserInsightsError::PropelAuthRateLimit); + } else { + Err(FetchUserInsightsError::UnexpectedException) + } + } + Err(_) => Err(FetchUserInsightsError::UnexpectedException), + } + } + + pub async fn fetch_org_attrition_report( + &self, + report_interval: AttritionReportInterval, + pagination: ReportPagination, + ) -> Result { + let result = user_insights_service_api::fetch_org_report( + &self.config, + OrgReportType::Attrition, + FetchReportQuery { + report_interval: ReportInterval::Attrition(report_interval), + page_size: pagination.page_size, + page_number: pagination.page_number, + }, + ) + .await; + + match result { + Ok(response) => Ok(response), + Err(Error::ResponseError(response)) => { + if response.status == 401 { + return Err(FetchUserInsightsError::InvalidApiKey); + } else if response.status == 429 { + return Err(FetchUserInsightsError::PropelAuthRateLimit); + } else { + Err(FetchUserInsightsError::UnexpectedException) + } + } + Err(_) => Err(FetchUserInsightsError::UnexpectedException), + } + } + + pub async fn fetch_org_growth_report( + &self, + report_interval: GrowthReportInterval, + pagination: ReportPagination, + ) -> Result { + let result = user_insights_service_api::fetch_org_report( + &self.config, + OrgReportType::Growth, + FetchReportQuery { + report_interval: ReportInterval::Growth(report_interval), + page_size: pagination.page_size, + page_number: pagination.page_number, + }, + ) + .await; + + match result { + Ok(response) => Ok(response), + Err(Error::ResponseError(response)) => { + if response.status == 401 { + return Err(FetchUserInsightsError::InvalidApiKey); + } else if response.status == 429 { + return Err(FetchUserInsightsError::PropelAuthRateLimit); + } else { + Err(FetchUserInsightsError::UnexpectedException) + } + } + Err(_) => Err(FetchUserInsightsError::UnexpectedException), + } + } + + pub async fn fetch_org_reengagement_report( + &self, + report_interval: ReengagementReportInterval, + pagination: ReportPagination, + ) -> Result { + let result = user_insights_service_api::fetch_org_report( + &self.config, + OrgReportType::Reengagement, + FetchReportQuery { + report_interval: ReportInterval::Reengagement(report_interval), + page_size: pagination.page_size, + page_number: pagination.page_number, + }, + ) + .await; + + match result { + Ok(response) => Ok(response), + Err(Error::ResponseError(response)) => { + if response.status == 401 { + return Err(FetchUserInsightsError::InvalidApiKey); + } else if response.status == 429 { + return Err(FetchUserInsightsError::PropelAuthRateLimit); + } else { + Err(FetchUserInsightsError::UnexpectedException) + } + } + Err(_) => Err(FetchUserInsightsError::UnexpectedException), + } + } + + pub async fn fetch_org_churn_report( + &self, + report_interval: ChurnReportInterval, + pagination: ReportPagination, + ) -> Result { + let result = user_insights_service_api::fetch_org_report( + &self.config, + OrgReportType::Churn, + FetchReportQuery { + report_interval: ReportInterval::Churn(report_interval), + page_size: pagination.page_size, + page_number: pagination.page_number, + }, + ) + .await; + + match result { + Ok(response) => Ok(response), + Err(Error::ResponseError(response)) => { + if response.status == 401 { + return Err(FetchUserInsightsError::InvalidApiKey); + } else if response.status == 429 { + return Err(FetchUserInsightsError::PropelAuthRateLimit); + } else { + Err(FetchUserInsightsError::UnexpectedException) + } + } + Err(_) => Err(FetchUserInsightsError::UnexpectedException), + } + } + + pub async fn fetch_chart_metric_data( + &self, + chart_metric: ChartMetric, + params: FetchChartDataQuery, + ) -> Result { + if let Some(start_date_raw) = ¶ms.start_date { + if let Ok(start_date) = chrono::NaiveDate::parse_from_str(start_date_raw, "%Y-%m-%d") { + if start_date > chrono::Utc::now().naive_utc().date() { + return Err(FetchUserInsightsError::InvalidParams( + "start_date cannot be in the future", + )); + } + } else { + return Err(FetchUserInsightsError::InvalidParams( + "start_date must be in YYYY-MM-DD format", + )); + } + } + + if let Some(end_date_raw) = ¶ms.end_date { + if chrono::NaiveDate::parse_from_str(end_date_raw, "%Y-%m-%d").is_err() { + return Err(FetchUserInsightsError::InvalidParams( + "end_date must be in YYYY-MM-DD format", + )); + } + } + + let result = + user_insights_service_api::fetch_chart_metric_data(&self.config, chart_metric, params) + .await; + + match result { + Ok(response) => Ok(response), + Err(Error::ResponseError(response)) => { + if response.status == 401 { + return Err(FetchUserInsightsError::InvalidApiKey); + } else if response.status == 429 { + return Err(FetchUserInsightsError::PropelAuthRateLimit); + } else { + Err(FetchUserInsightsError::UnexpectedException) + } + } + Err(_) => Err(FetchUserInsightsError::UnexpectedException), + } + } +}