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
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 测试方法问题
178 lines
6.3 KiB
Rust
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))
|
|
}
|