From ef767297f775bbbd36090748954a8a20b9a64bd0 Mon Sep 17 00:00:00 2001 From: ZhenYi <434836402@qq.com> Date: Sun, 26 Apr 2026 14:04:01 +0800 Subject: [PATCH] chore(api): remove admin AI model CRUD routes Admin Next.js app now handles DB access directly for provider/model/ version/pricing management. Keep only health, sync, alerts, and billing. --- libs/api/admin/ai_models.rs | 409 ------------------------------------ libs/api/admin/mod.rs | 21 +- 2 files changed, 3 insertions(+), 427 deletions(-) delete mode 100644 libs/api/admin/ai_models.rs diff --git a/libs/api/admin/ai_models.rs b/libs/api/admin/ai_models.rs deleted file mode 100644 index f3c67d2..0000000 --- a/libs/api/admin/ai_models.rs +++ /dev/null @@ -1,409 +0,0 @@ -//! Admin CRUD endpoints for AI Providers, Models, Versions, and Pricing. -//! -//! All write operations use `Session::no_op()` (system caller) which passes -//! the `require_system_caller` check in the service layer. - -use actix_web::{HttpRequest, HttpResponse, Result, web}; -use service::agent::provider::{CreateProviderRequest as SvcCreateProvider, UpdateProviderRequest as SvcUpdateProvider}; -use service::agent::model::{CreateModelRequest as SvcCreateModel, UpdateModelRequest as SvcUpdateModel}; -use service::agent::model_version::{CreateModelVersionRequest as SvcCreateVersion, UpdateModelVersionRequest as SvcUpdateVersion}; -use service::agent::model_pricing::UpdateModelPricingRequest as SvcUpdatePricing; -use service::AppService; -use uuid::Uuid; - -use crate::error::ApiError; -use crate::ApiResponse; -use service::error::AppError; - -/// Validate the `x-admin-api-key` header against ADMIN_API_SHARED_KEY env var. -fn validate_admin_key(req: &HttpRequest) -> Result<(), ApiError> { - let expected = std::env::var("ADMIN_API_SHARED_KEY").ok(); - let Some(expected) = expected else { - return Err(ApiError(AppError::InternalServerError( - "ADMIN_API_SHARED_KEY not configured".into(), - ))); - }; - let provided = req - .headers() - .get("x-admin-api-key") - .and_then(|v| v.to_str().ok()) - .ok_or(ApiError(AppError::Unauthorized))?; - if provided != expected { - return Err(ApiError(AppError::Unauthorized)); - } - Ok(()) -} - -// ─── Provider CRUD ───────────────────────────────────────────────────────────── - -#[derive(serde::Deserialize, utoipa::ToSchema)] -pub struct AdminCreateProvider { - pub name: String, - pub display_name: String, - pub website: Option, -} - -#[derive(serde::Deserialize, utoipa::ToSchema)] -pub struct AdminUpdateProvider { - pub display_name: Option, - pub website: Option, - pub status: Option, -} - -#[utoipa::path( - post, - path = "/api/admin/ai/providers", - request_body = AdminCreateProvider, - responses( - (status = 200, description = "Provider created"), - (status = 401), - (status = 400), - ), - tag = "Admin" -)] -pub async fn admin_provider_create( - req: HttpRequest, - service: web::Data, - body: web::Json, -) -> Result { - validate_admin_key(&req)?; - let session = session::Session::no_op(); - let svc_body = SvcCreateProvider { - name: body.name.clone(), - display_name: body.display_name.clone(), - website: body.website.clone(), - }; - let resp = service.agent_provider_create(svc_body, &session).await?; - Ok(ApiResponse::ok(resp).to_response()) -} - -#[utoipa::path( - patch, - path = "/api/admin/ai/providers/{id}", - params(("id" = Uuid, Path)), - request_body = AdminUpdateProvider, - responses( - (status = 200, description = "Provider updated"), - (status = 401), - (status = 404), - ), - tag = "Admin" -)] -pub async fn admin_provider_update( - req: HttpRequest, - service: web::Data, - path: web::Path, - body: web::Json, -) -> Result { - validate_admin_key(&req)?; - let id = path.into_inner(); - let session = session::Session::no_op(); - let svc_body = SvcUpdateProvider { - display_name: body.display_name.clone(), - website: body.website.clone(), - status: body.status.clone(), - }; - let resp = service.agent_provider_update(id, svc_body, &session).await?; - Ok(ApiResponse::ok(resp).to_response()) -} - -#[utoipa::path( - delete, - path = "/api/admin/ai/providers/{id}", - params(("id" = Uuid, Path)), - responses( - (status = 200), - (status = 401), - (status = 404), - ), - tag = "Admin" -)] -pub async fn admin_provider_delete( - req: HttpRequest, - service: web::Data, - path: web::Path, -) -> Result { - validate_admin_key(&req)?; - let id = path.into_inner(); - let session = session::Session::no_op(); - service.agent_provider_delete(id, &session).await?; - Ok(ApiResponse::ok(serde_json::json!({ "deleted": true })).to_response()) -} - -// ─── Model CRUD ──────────────────────────────────────────────────────────────── - -#[derive(serde::Deserialize, utoipa::ToSchema)] -pub struct AdminCreateModel { - pub provider_id: Uuid, - pub name: String, - pub modality: String, - pub capability: String, - pub context_length: i64, - #[serde(default)] - pub max_output_tokens: Option, - #[serde(default)] - pub training_cutoff: Option>, - #[serde(default)] - pub is_open_source: bool, -} - -#[derive(serde::Deserialize, utoipa::ToSchema)] -pub struct AdminUpdateModel { - pub display_name: Option, - pub modality: Option, - pub capability: Option, - pub context_length: Option, - pub max_output_tokens: Option, - pub training_cutoff: Option>, - #[serde(default)] - pub is_open_source: Option, - pub status: Option, -} - -#[utoipa::path( - post, - path = "/api/admin/ai/models", - request_body = AdminCreateModel, - responses( - (status = 200, description = "Model created"), - (status = 401), - (status = 400), - ), - tag = "Admin" -)] -pub async fn admin_model_create( - req: HttpRequest, - service: web::Data, - body: web::Json, -) -> Result { - validate_admin_key(&req)?; - let session = session::Session::no_op(); - let svc_body = SvcCreateModel { - provider_id: body.provider_id, - name: body.name.clone(), - modality: body.modality.clone(), - capability: body.capability.clone(), - context_length: body.context_length, - max_output_tokens: body.max_output_tokens, - training_cutoff: body.training_cutoff, - is_open_source: body.is_open_source, - }; - let resp = service.agent_model_create(svc_body, &session).await?; - Ok(ApiResponse::ok(resp).to_response()) -} - -#[utoipa::path( - patch, - path = "/api/admin/ai/models/{id}", - params(("id" = Uuid, Path)), - request_body = AdminUpdateModel, - responses( - (status = 200, description = "Model updated"), - (status = 401), - (status = 404), - ), - tag = "Admin" -)] -pub async fn admin_model_update( - req: HttpRequest, - service: web::Data, - path: web::Path, - body: web::Json, -) -> Result { - validate_admin_key(&req)?; - let id = path.into_inner(); - let session = session::Session::no_op(); - let svc_body = SvcUpdateModel { - display_name: body.display_name.clone(), - modality: body.modality.clone(), - capability: body.capability.clone(), - context_length: body.context_length, - max_output_tokens: body.max_output_tokens, - training_cutoff: body.training_cutoff, - is_open_source: body.is_open_source, - status: body.status.clone(), - }; - let resp = service.agent_model_update(id, svc_body, &session).await?; - Ok(ApiResponse::ok(resp).to_response()) -} - -#[utoipa::path( - delete, - path = "/api/admin/ai/models/{id}", - params(("id" = Uuid, Path)), - responses( - (status = 200), - (status = 401), - (status = 404), - ), - tag = "Admin" -)] -pub async fn admin_model_delete( - req: HttpRequest, - service: web::Data, - path: web::Path, -) -> Result { - validate_admin_key(&req)?; - let id = path.into_inner(); - let session = session::Session::no_op(); - service.agent_model_delete(id, &session).await?; - Ok(ApiResponse::ok(serde_json::json!({ "deleted": true })).to_response()) -} - -// ─── Model Version CRUD ──────────────────────────────────────────────────────── - -#[derive(serde::Deserialize, utoipa::ToSchema)] -pub struct AdminCreateVersion { - pub model_id: Uuid, - pub version: String, - #[serde(default)] - pub release_date: Option>, - #[serde(default)] - pub change_log: Option, - #[serde(default)] - pub is_default: bool, -} - -#[derive(serde::Deserialize, utoipa::ToSchema)] -pub struct AdminUpdateVersion { - pub version: Option, - pub release_date: Option>, - pub change_log: Option, - #[serde(default)] - pub is_default: Option, - pub status: Option, -} - -#[utoipa::path( - post, - path = "/api/admin/ai/versions", - request_body = AdminCreateVersion, - responses( - (status = 200, description = "Version created"), - (status = 401), - (status = 400), - ), - tag = "Admin" -)] -pub async fn admin_version_create( - req: HttpRequest, - service: web::Data, - body: web::Json, -) -> Result { - validate_admin_key(&req)?; - let session = session::Session::no_op(); - let svc_body = SvcCreateVersion { - model_id: body.model_id, - version: body.version.clone(), - release_date: body.release_date, - change_log: body.change_log.clone(), - is_default: body.is_default, - }; - let resp = service.agent_model_version_create(svc_body, &session).await?; - Ok(ApiResponse::ok(resp).to_response()) -} - -#[utoipa::path( - patch, - path = "/api/admin/ai/versions/{id}", - params(("id" = Uuid, Path)), - request_body = AdminUpdateVersion, - responses( - (status = 200), - (status = 401), - (status = 404), - ), - tag = "Admin" -)] -pub async fn admin_version_update( - req: HttpRequest, - service: web::Data, - path: web::Path, - body: web::Json, -) -> Result { - validate_admin_key(&req)?; - let id = path.into_inner(); - let session = session::Session::no_op(); - let svc_body = SvcUpdateVersion { - version: body.version.clone(), - release_date: body.release_date, - change_log: body.change_log.clone(), - is_default: body.is_default, - status: body.status.clone(), - }; - let resp = service.agent_model_version_update(id, svc_body, &session).await?; - Ok(ApiResponse::ok(resp).to_response()) -} - -#[utoipa::path( - delete, - path = "/api/admin/ai/versions/{id}", - params(("id" = Uuid, Path)), - responses( - (status = 200), - (status = 401), - (status = 404), - ), - tag = "Admin" -)] -pub async fn admin_version_delete( - req: HttpRequest, - service: web::Data, - path: web::Path, -) -> Result { - validate_admin_key(&req)?; - let id = path.into_inner(); - let session = session::Session::no_op(); - service.agent_model_version_delete(id, &session).await?; - Ok(ApiResponse::ok(serde_json::json!({ "deleted": true })).to_response()) -} - -// ─── Model Pricing CRUD ─────────────────────────────────────────────────────── - -#[derive(serde::Deserialize, utoipa::ToSchema)] -pub struct AdminCreatePricing { - pub model_version_id: Uuid, - pub input_price_per_1k_tokens: String, - pub output_price_per_1k_tokens: String, - pub currency: String, - pub effective_from: chrono::DateTime, -} - -#[derive(serde::Deserialize, utoipa::ToSchema)] -pub struct AdminUpdatePricing { - pub input_price_per_1k_tokens: Option, - pub output_price_per_1k_tokens: Option, - pub currency: Option, - pub effective_from: Option>, -} - -#[utoipa::path( - patch, - path = "/api/admin/ai/pricing/{id}", - params(("id" = i64, Path)), - request_body = AdminUpdatePricing, - responses( - (status = 200), - (status = 401), - (status = 404), - ), - tag = "Admin" -)] -pub async fn admin_pricing_update( - req: HttpRequest, - service: web::Data, - path: web::Path, - body: web::Json, -) -> Result { - validate_admin_key(&req)?; - let id = path.into_inner(); - let session = session::Session::no_op(); - let svc_body = SvcUpdatePricing { - input_price_per_1k_tokens: body.input_price_per_1k_tokens.clone(), - output_price_per_1k_tokens: body.output_price_per_1k_tokens.clone(), - currency: body.currency.clone(), - effective_from: body.effective_from, - }; - let resp = service.agent_model_pricing_update(id, svc_body, &session).await?; - Ok(ApiResponse::ok(resp).to_response()) -} diff --git a/libs/api/admin/mod.rs b/libs/api/admin/mod.rs index 85001d8..db4459f 100644 --- a/libs/api/admin/mod.rs +++ b/libs/api/admin/mod.rs @@ -1,12 +1,11 @@ //! Admin API endpoints — protected by `x-admin-api-key` header. //! -//! These endpoints are called by the Admin Dashboard (Next.js) to perform -//! platform-wide operations that bypass normal user-session authorization. +//! Only platform-wide operations remain. AI model management is handled +//! directly by the admin Next.js app via database access. use actix_web::web; pub mod alerts; -pub mod ai_models; pub mod billing; pub mod sync; @@ -19,21 +18,7 @@ pub fn init_admin_routes(cfg: &mut web::ServiceConfig) { .route( "/workspaces/{slug}/add-credit", web::post().to(billing::admin_workspace_add_credit), - ) - // Provider CRUD - .route("/ai/providers", web::post().to(ai_models::admin_provider_create)) - .route("/ai/providers/{id}", web::patch().to(ai_models::admin_provider_update)) - .route("/ai/providers/{id}", web::delete().to(ai_models::admin_provider_delete)) - // Model CRUD - .route("/ai/models", web::post().to(ai_models::admin_model_create)) - .route("/ai/models/{id}", web::patch().to(ai_models::admin_model_update)) - .route("/ai/models/{id}", web::delete().to(ai_models::admin_model_delete)) - // Version CRUD - .route("/ai/versions", web::post().to(ai_models::admin_version_create)) - .route("/ai/versions/{id}", web::patch().to(ai_models::admin_version_update)) - .route("/ai/versions/{id}", web::delete().to(ai_models::admin_version_delete)) - // Pricing update - .route("/ai/pricing/{id}", web::patch().to(ai_models::admin_pricing_update)), + ), ); }