feat(industry): Phase 1 行业配置基础 — 数据模型 + 四行业内置配置 + ButlerRouter 动态关键词
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

- 新增 SaaS industry 模块 (types/service/handlers/mod/builtin)
- 4 行业内置配置: healthcare/education/garment/ecommerce
- 数据库迁移: industries + account_industries 表
- 8 个 API 端点 (CRUD + 用户行业关联)
- ButlerRouter 改造: 支持 IndustryKeywordConfig 动态注入
- 12 个测试全通过 (含动态行业分类测试)
This commit is contained in:
iven
2026-04-12 15:42:35 +08:00
parent 5599cefc41
commit 5d1050bf6f
13 changed files with 886 additions and 32 deletions

View File

@@ -0,0 +1,111 @@
//! 行业配置 API handlers
use axum::extract::{Path, Query, State};
use axum::Extension;
use axum::Json;
use crate::error::SaasResult;
use crate::state::AppState;
use crate::auth::types::AuthContext;
use super::types::*;
use super::service;
/// GET /api/v1/industries — 行业列表(公开,已认证用户可访问)
pub async fn list_industries(
State(state): State<AppState>,
Query(query): Query<ListIndustriesQuery>,
) -> SaasResult<Json<crate::common::PaginatedResponse<IndustryListItem>>> {
let result = service::list_industries(&state.db, &query).await?;
Ok(Json(result))
}
/// GET /api/v1/industries/:id — 行业详情(公开)
pub async fn get_industry(
State(state): State<AppState>,
Path(id): Path<String>,
) -> SaasResult<Json<Industry>> {
let industry = service::get_industry(&state.db, &id).await?;
Ok(Json(industry))
}
/// POST /api/v1/industries — 创建行业 (admin: config:write)
pub async fn create_industry(
State(state): State<AppState>,
Extension(ctx): Extension<AuthContext>,
Json(body): Json<CreateIndustryRequest>,
) -> SaasResult<Json<Industry>> {
require_config_write(&ctx)?;
let industry = service::create_industry(&state.db, &body).await?;
Ok(Json(industry))
}
/// PATCH /api/v1/industries/:id — 更新行业 (admin: config:write)
pub async fn update_industry(
State(state): State<AppState>,
Extension(ctx): Extension<AuthContext>,
Path(id): Path<String>,
Json(body): Json<UpdateIndustryRequest>,
) -> SaasResult<Json<Industry>> {
require_config_write(&ctx)?;
let industry = service::update_industry(&state.db, &id, &body).await?;
Ok(Json(industry))
}
/// GET /api/v1/industries/:id/full-config — 完整配置含关键词、prompt等
pub async fn get_industry_full_config(
State(state): State<AppState>,
Path(id): Path<String>,
) -> SaasResult<Json<IndustryFullConfig>> {
let config = service::get_industry_full_config(&state.db, &id).await?;
Ok(Json(config))
}
/// GET /api/v1/accounts/:id/industries — 用户授权行业列表
pub async fn list_account_industries(
State(state): State<AppState>,
Path(account_id): Path<String>,
) -> SaasResult<Json<Vec<AccountIndustryItem>>> {
let items = service::list_account_industries(&state.db, &account_id).await?;
Ok(Json(items))
}
/// PUT /api/v1/accounts/:id/industries — 设置用户行业 (admin: account:admin)
pub async fn set_account_industries(
State(state): State<AppState>,
Extension(ctx): Extension<AuthContext>,
Path(account_id): Path<String>,
Json(body): Json<SetAccountIndustriesRequest>,
) -> SaasResult<Json<Vec<AccountIndustryItem>>> {
require_account_admin(&ctx)?;
let items = service::set_account_industries(&state.db, &account_id, &body).await?;
Ok(Json(items))
}
/// GET /api/v1/accounts/me/industries — 当前用户行业
pub async fn list_my_industries(
State(state): State<AppState>,
Extension(ctx): Extension<AuthContext>,
) -> SaasResult<Json<Vec<AccountIndustryItem>>> {
let account_id = &ctx.account_id;
let items = service::list_account_industries(&state.db, account_id).await?;
Ok(Json(items))
}
// ============ Helpers ============
fn require_config_write(ctx: &AuthContext) -> SaasResult<()> {
if !ctx.permissions.contains(&"config:write".to_string())
&& !ctx.permissions.contains(&"admin:full".to_string())
{
return Err(crate::error::SaasError::Forbidden("需要 config:write 权限".to_string()));
}
Ok(())
}
fn require_account_admin(ctx: &AuthContext) -> SaasResult<()> {
if !ctx.permissions.contains(&"account:admin".to_string())
&& !ctx.permissions.contains(&"admin:full".to_string())
{
return Err(crate::error::SaasError::Forbidden("需要 account:admin 权限".to_string()));
}
Ok(())
}