//! 账号管理 HTTP 处理器 use axum::{ extract::{Extension, Path, Query, State}, Json, }; use crate::state::AppState; use crate::error::SaasResult; use crate::auth::types::AuthContext; use crate::auth::handlers::{log_operation, check_permission}; use super::{types::*, service}; fn require_admin(ctx: &AuthContext) -> SaasResult<()> { check_permission(ctx, "account:admin") } /// GET /api/v1/accounts (admin only) pub async fn list_accounts( State(state): State, Query(query): Query, Extension(ctx): Extension, ) -> SaasResult>> { require_admin(&ctx)?; service::list_accounts(&state.db, &query).await.map(Json) } /// GET /api/v1/accounts/:id pub async fn get_account( State(state): State, Path(id): Path, Extension(ctx): Extension, ) -> SaasResult> { // 只能查看自己,或 admin 查看任何人 if id != ctx.account_id { require_admin(&ctx)?; } service::get_account(&state.db, &id).await.map(Json) } /// PUT /api/v1/accounts/:id (admin or self for limited fields) pub async fn update_account( State(state): State, Path(id): Path, Extension(ctx): Extension, Json(req): Json, ) -> SaasResult> { // 非管理员只能修改自己的资料 if id != ctx.account_id { require_admin(&ctx)?; } let result = service::update_account(&state.db, &id, &req).await?; log_operation(&state.db, &ctx.account_id, "account.update", "account", &id, None, None).await?; Ok(Json(result)) } /// PATCH /api/v1/accounts/:id/status (admin only) pub async fn update_status( State(state): State, Path(id): Path, Extension(ctx): Extension, Json(req): Json, ) -> SaasResult> { require_admin(&ctx)?; service::update_account_status(&state.db, &id, &req.status).await?; log_operation(&state.db, &ctx.account_id, "account.update_status", "account", &id, Some(serde_json::json!({"status": &req.status})), None).await?; Ok(Json(serde_json::json!({"ok": true}))) } /// GET /api/v1/tokens pub async fn list_tokens( State(state): State, Extension(ctx): Extension, ) -> SaasResult>> { service::list_api_tokens(&state.db, &ctx.account_id).await.map(Json) } /// POST /api/v1/tokens pub async fn create_token( State(state): State, Extension(ctx): Extension, Json(req): Json, ) -> SaasResult> { let token = service::create_api_token(&state.db, &ctx.account_id, &req).await?; log_operation(&state.db, &ctx.account_id, "token.create", "api_token", &token.id, Some(serde_json::json!({"name": &req.name})), None).await?; Ok(Json(token)) } /// DELETE /api/v1/tokens/:id pub async fn revoke_token( State(state): State, Path(id): Path, Extension(ctx): Extension, ) -> SaasResult> { service::revoke_api_token(&state.db, &id, &ctx.account_id).await?; log_operation(&state.db, &ctx.account_id, "token.revoke", "api_token", &id, None, None).await?; Ok(Json(serde_json::json!({"ok": true}))) } /// GET /api/v1/logs/operations (admin only) pub async fn list_operation_logs( State(state): State, Query(params): Query>, Extension(ctx): Extension, ) -> SaasResult>> { require_admin(&ctx)?; let page: i64 = params.get("page").and_then(|v| v.parse().ok()).unwrap_or(1); let page_size: i64 = params.get("page_size").and_then(|v| v.parse().ok()).unwrap_or(50); let offset = (page - 1) * page_size; let rows: Vec<(i64, Option, String, Option, Option, Option, Option, String)> = sqlx::query_as( "SELECT id, account_id, action, target_type, target_id, details, ip_address, created_at FROM operation_logs ORDER BY created_at DESC LIMIT ?1 OFFSET ?2" ) .bind(page_size) .bind(offset) .fetch_all(&state.db) .await?; let items: Vec = rows.into_iter().map(|(id, account_id, action, target_type, target_id, details, ip_address, created_at)| { serde_json::json!({ "id": id, "account_id": account_id, "action": action, "target_type": target_type, "target_id": target_id, "details": details.and_then(|d| serde_json::from_str::(&d).ok()), "ip_address": ip_address, "created_at": created_at, }) }).collect(); Ok(Json(items)) } /// GET /api/v1/stats/dashboard — 仪表盘聚合统计 (需要 admin 权限) pub async fn dashboard_stats( State(state): State, Extension(ctx): Extension, ) -> SaasResult> { require_admin(&ctx)?; let total_accounts: (i64,) = sqlx::query_as("SELECT COUNT(*) FROM accounts") .fetch_one(&state.db).await?; let active_accounts: (i64,) = sqlx::query_as("SELECT COUNT(*) FROM accounts WHERE status = 'active'") .fetch_one(&state.db).await?; let tasks_today: (i64,) = sqlx::query_as( "SELECT COUNT(*) FROM relay_tasks WHERE date(created_at) = date('now')" ).fetch_one(&state.db).await?; let active_providers: (i64,) = sqlx::query_as("SELECT COUNT(*) FROM providers WHERE enabled = 1") .fetch_one(&state.db).await?; let active_models: (i64,) = sqlx::query_as("SELECT COUNT(*) FROM models WHERE enabled = 1") .fetch_one(&state.db).await?; let tokens_today_input: (i64,) = sqlx::query_as( "SELECT COALESCE(SUM(input_tokens), 0) FROM usage_records WHERE date(created_at) = date('now')" ).fetch_one(&state.db).await?; let tokens_today_output: (i64,) = sqlx::query_as( "SELECT COALESCE(SUM(output_tokens), 0) FROM usage_records WHERE date(created_at) = date('now')" ).fetch_one(&state.db).await?; Ok(Json(serde_json::json!({ "total_accounts": total_accounts.0, "active_accounts": active_accounts.0, "tasks_today": tasks_today.0, "active_providers": active_providers.0, "active_models": active_models.0, "tokens_today_input": tokens_today_input.0, "tokens_today_output": tokens_today_output.0, }))) }