184 lines
5.9 KiB
Rust
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,
|
|
})
|
|
}
|
|
}
|