gitdataai/libs/service/user/billing.rs

184 lines
5.9 KiB
Rust

use crate::AppService;
use crate::error::AppError;
use chrono::{DateTime, Utc};
use models::ai::billing_error;
use models::projects::project_billing_history;
use models::users::user_billing;
use sea_orm::sea_query::prelude::rust_decimal::prelude::ToPrimitive;
use sea_orm::*;
use serde::{Deserialize, Serialize};
use session::Session;
use utoipa::{IntoParams, ToSchema};
use uuid::Uuid;
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct UserBillingResponse {
pub user_uid: Uuid,
pub balance: f64,
pub currency: String,
pub is_pro: bool,
pub monthly_quota: f64,
pub month_used: f64,
pub cycle_start_utc: Option<DateTime<Utc>>,
pub cycle_end_utc: Option<DateTime<Utc>>,
pub updated_at: DateTime<Utc>,
pub created_at: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct UserBillingErrorsResponse {
pub list: Vec<UserBillingErrorItem>,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct UserBillingErrorItem {
pub id: Uuid,
pub scope: String,
pub scope_id: Uuid,
pub error_type: String,
pub message: String,
pub details: Option<serde_json::Value>,
pub resolved: bool,
pub created_at: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema, IntoParams)]
pub struct UserBillingHistoryQuery {
pub page: Option<u64>,
pub per_page: Option<u64>,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct UserBillingHistoryItem {
pub uid: Uuid,
pub project_uid: Uuid,
pub user_uid: Option<Uuid>,
pub amount: f64,
pub currency: String,
pub reason: String,
pub extra: Option<serde_json::Value>,
pub created_at: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct UserBillingHistoryResponse {
pub page: u64,
pub per_page: u64,
pub total: u64,
pub list: Vec<UserBillingHistoryItem>,
}
impl AppService {
pub async fn user_billing_current(
&self,
ctx: &Session,
) -> Result<UserBillingResponse, AppError> {
let user_uid = ctx.user().ok_or(AppError::Unauthorized)?;
let billing = match user_billing::Entity::find_by_id(user_uid)
.one(&self.db)
.await?
{
Some(b) => b,
None => {
let now = Utc::now();
let active = user_billing::ActiveModel {
user: Set(user_uid),
balance: Set(rust_decimal::Decimal::from(10)),
currency: Set("USD".to_string()),
is_pro: Set(false),
monthly_quota: Set(rust_decimal::Decimal::from(0)),
month_used: Set(rust_decimal::Decimal::from(0)),
cycle_start: Set(None),
cycle_end: Set(None),
updated_at: Set(now),
created_at: Set(now),
};
active.insert(&self.db).await?
}
};
Ok(UserBillingResponse {
user_uid: billing.user,
balance: billing.balance.to_f64().unwrap_or_default(),
currency: billing.currency,
is_pro: billing.is_pro,
monthly_quota: billing.monthly_quota.to_f64().unwrap_or_default(),
month_used: billing.month_used.to_f64().unwrap_or_default(),
cycle_start_utc: billing.cycle_start,
cycle_end_utc: billing.cycle_end,
updated_at: billing.updated_at,
created_at: billing.created_at,
})
}
pub async fn user_billing_errors(
&self,
ctx: &Session,
) -> Result<UserBillingErrorsResponse, AppError> {
let user_uid = ctx.user().ok_or(AppError::Unauthorized)?;
let errors = billing_error::Entity::find()
.filter(billing_error::Column::Scope.eq("user"))
.filter(billing_error::Column::ScopeId.eq(user_uid))
.filter(billing_error::Column::Resolved.eq(false))
.order_by_desc(billing_error::Column::CreatedAt)
.all(&self.db)
.await?;
Ok(UserBillingErrorsResponse {
list: errors
.into_iter()
.map(|e| UserBillingErrorItem {
id: e.id,
scope: e.scope,
scope_id: e.scope_id,
error_type: e.error_type,
message: e.message,
details: e.details,
resolved: e.resolved,
created_at: e.created_at,
})
.collect(),
})
}
pub async fn user_billing_history(
&self,
ctx: &Session,
query: UserBillingHistoryQuery,
) -> Result<UserBillingHistoryResponse, AppError> {
let user_uid = ctx.user().ok_or(AppError::Unauthorized)?;
let page = std::cmp::max(query.page.unwrap_or(1), 1);
let per_page = query.per_page.unwrap_or(20).clamp(1, 200);
let paginator = project_billing_history::Entity::find()
.filter(project_billing_history::Column::User.eq(user_uid))
.order_by_desc(project_billing_history::Column::CreatedAt)
.paginate(&self.db, per_page);
let total = paginator.num_items().await?;
let rows = paginator.fetch_page(page - 1).await?;
let list = rows
.into_iter()
.map(|x| UserBillingHistoryItem {
uid: x.uid,
project_uid: x.project,
user_uid: x.user,
amount: x.amount.to_f64().unwrap_or_default(),
currency: x.currency,
reason: x.reason,
extra: x.extra.map(|v| v.into()),
created_at: x.created_at,
})
.collect();
Ok(UserBillingHistoryResponse {
page,
per_page,
total,
list,
})
}
}