Files
zclaw_openfang/crates/zclaw-saas/src/prompt/handlers.rs
iven a504a40395
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
fix: 7 项 E2E Bug 修复 — Dashboard 404 / 记忆去重 / 记忆注入 / invoice_id / Prompt 版本
P0:
- BUG-H1: Dashboard 路由 /api/v1/stats/dashboard → /api/v1/admin/dashboard

P1:
- BUG-H2: viking_add 预检查 content_hash 去重,返回 "deduped" 状态;SqliteStorage 启动时回填已有条目 content_hash
- BUG-M5: saas-relay-client 发送前调用 viking_inject_prompt 注入跨会话记忆

P2:
- BUG-M1: PaymentResult 添加 invoice_id 字段,query_payment_status 返回 invoice_id
- BUG-M2: UpdatePromptRequest 添加内容字段,更新时自动创建新版本并递增 current_version
- BUG-M3: viking_find scope 参数文档化(设计行为,调用方需传 agent scope)
- BUG-M4: Dashboard 路由缺失已修复,handler 层 require_admin 已正确返回 403

P3 (确认已修复/非代码问题):
- BUG-L1: pain_seed_categories 已统一,无 pain_seeds 残留
- BUG-L2: pipeline_create 参数格式正确,E2E 测试方法问题
2026-04-17 03:31:06 +08:00

178 lines
6.3 KiB
Rust

//! 提示词模板 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::*;
use super::service;
/// GET /api/v1/prompts/check — OTA 批量检查更新
pub async fn check_updates(
State(state): State<AppState>,
Extension(ctx): Extension<AuthContext>,
Json(req): Json<PromptCheckRequest>,
) -> SaasResult<Json<PromptCheckResponse>> {
let result = service::check_updates(&state.db, &req.device_id, &req.versions).await?;
log_operation(&state.db, &ctx.account_id, "prompt.check", "prompt", &req.device_id,
Some(serde_json::json!({"updates_count": result.updates.len()})), ctx.client_ip.as_deref()).await?;
Ok(Json(result))
}
/// GET /api/v1/prompts — 列表全部模板
pub async fn list_prompts(
State(state): State<AppState>,
Extension(ctx): Extension<AuthContext>,
Query(query): Query<PromptListQuery>,
) -> SaasResult<Json<crate::common::PaginatedResponse<PromptTemplateInfo>>> {
check_permission(&ctx, "prompt:read")?;
Ok(Json(service::list_templates(&state.db, &query).await?))
}
/// POST /api/v1/prompts — 创建提示词模板
pub async fn create_prompt(
State(state): State<AppState>,
Extension(ctx): Extension<AuthContext>,
Json(req): Json<CreatePromptRequest>,
) -> SaasResult<Json<PromptTemplateInfo>> {
check_permission(&ctx, "prompt:write")?;
let source = req.source.as_deref().unwrap_or("custom");
let result = service::create_template(
&state.db, &req.name, &req.category, req.description.as_deref(),
source, &req.system_prompt,
req.user_prompt_template.as_deref(),
req.variables.clone(),
req.min_app_version.as_deref(),
).await?;
log_operation(&state.db, &ctx.account_id, "prompt.create", "prompt", &result.id,
Some(serde_json::json!({"name": req.name})), ctx.client_ip.as_deref()).await?;
Ok(Json(result))
}
/// GET /api/v1/prompts/{name} — 获取模板(按名称)
pub async fn get_prompt(
State(state): State<AppState>,
Extension(ctx): Extension<AuthContext>,
Path(name): Path<String>,
) -> SaasResult<Json<PromptTemplateInfo>> {
check_permission(&ctx, "prompt:read")?;
Ok(Json(service::get_template_by_name(&state.db, &name).await?))
}
/// PUT /api/v1/prompts/{name} — 更新模板元数据 + 可选自动创建新版本
pub async fn update_prompt(
State(state): State<AppState>,
Extension(ctx): Extension<AuthContext>,
Path(name): Path<String>,
Json(req): Json<UpdatePromptRequest>,
) -> SaasResult<Json<PromptTemplateInfo>> {
check_permission(&ctx, "prompt:write")?;
let tmpl = service::get_template_by_name(&state.db, &name).await?;
let result = service::update_template(
&state.db, &tmpl.id,
req.description.as_deref(),
req.status.as_deref(),
req.system_prompt.as_deref(),
req.user_prompt_template.as_deref(),
req.variables.clone(),
req.changelog.as_deref(),
req.min_app_version.as_deref(),
).await?;
log_operation(&state.db, &ctx.account_id, "prompt.update", "prompt", &tmpl.id,
Some(serde_json::json!({"name": name})), ctx.client_ip.as_deref()).await?;
Ok(Json(result))
}
/// DELETE /api/v1/prompts/{name} — 归档模板
pub async fn archive_prompt(
State(state): State<AppState>,
Extension(ctx): Extension<AuthContext>,
Path(name): Path<String>,
) -> SaasResult<Json<PromptTemplateInfo>> {
check_permission(&ctx, "prompt:admin")?;
let tmpl = service::get_template_by_name(&state.db, &name).await?;
let result = service::update_template(&state.db, &tmpl.id, None, Some("archived"), None, None, None, None, None).await?;
log_operation(&state.db, &ctx.account_id, "prompt.archive", "prompt", &tmpl.id, None, ctx.client_ip.as_deref()).await?;
Ok(Json(result))
}
/// GET /api/v1/prompts/{name}/versions — 查看版本历史
pub async fn list_versions(
State(state): State<AppState>,
Extension(ctx): Extension<AuthContext>,
Path(name): Path<String>,
) -> SaasResult<Json<Vec<PromptVersionInfo>>> {
check_permission(&ctx, "prompt:read")?;
let tmpl = service::get_template_by_name(&state.db, &name).await?;
Ok(Json(service::list_versions(&state.db, &tmpl.id).await?))
}
/// GET /api/v1/prompts/{name}/versions/{version} — 获取特定版本
pub async fn get_version(
State(state): State<AppState>,
Extension(ctx): Extension<AuthContext>,
Path((name, version)): Path<(String, i32)>,
) -> SaasResult<Json<PromptVersionInfo>> {
check_permission(&ctx, "prompt:read")?;
Ok(Json(service::get_version_by_number(&state.db, &name, version).await?))
}
/// POST /api/v1/prompts/{name}/versions — 发布新版本
pub async fn create_version(
State(state): State<AppState>,
Extension(ctx): Extension<AuthContext>,
Path(name): Path<String>,
Json(req): Json<CreateVersionRequest>,
) -> SaasResult<Json<PromptVersionInfo>> {
check_permission(&ctx, "prompt:write")?;
let tmpl = service::get_template_by_name(&state.db, &name).await?;
let result = service::create_version(
&state.db, &tmpl.id,
&req.system_prompt,
req.user_prompt_template.as_deref(),
req.variables.clone(),
req.changelog.as_deref(),
req.min_app_version.as_deref(),
).await?;
log_operation(&state.db, &ctx.account_id, "prompt.publish_version", "prompt", &tmpl.id,
Some(serde_json::json!({"name": name, "version": result.version})), ctx.client_ip.as_deref()).await?;
Ok(Json(result))
}
/// POST /api/v1/prompts/{name}/rollback/{version} — 回退到指定版本
pub async fn rollback_version(
State(state): State<AppState>,
Extension(ctx): Extension<AuthContext>,
Path((name, version)): Path<(String, i32)>,
) -> SaasResult<Json<PromptTemplateInfo>> {
check_permission(&ctx, "prompt:admin")?;
let tmpl = service::get_template_by_name(&state.db, &name).await?;
let result = service::rollback_version(&state.db, &tmpl.id, version).await?;
log_operation(&state.db, &ctx.account_id, "prompt.rollback", "prompt", &tmpl.id,
Some(serde_json::json!({"name": name, "target_version": version})), ctx.client_ip.as_deref()).await?;
Ok(Json(result))
}