feat: 配置API(分组校验管线/整份版本/回滚/重置出厂/试金石/平台设置)
- config_validation.validate_group: 九组校验管线(类型/键 allowlist/数值 范围/prompts 大小上限 50KB·100KB/wall 全量交 compile_wall/push.prefix 跨租户查重 spec E1),错误清单中文带字段路径 - config_routes: GET/PUT config(整组替换原样入版本,MAX+1,热 reload)/ reset(按 Tenant.profile 取出厂档案组)/versions 列表·详情/rollback (旧版复制为新版本,历史不可变)/wall/test 试金石(逐句判定,不落库 不 bump)/settings 平台设置 CRUD(仅超管,resolver 接线归 Phase 3) - db.write_config_version: 版本写入公共路径(创建向导与配置 API 单一 事实源);Tenant 落 profile 列(重置出厂按它取对应档案) - _profile_snapshot/_blankify 迁 tenant_source(两路由模块共用); db_source_app/super_client 夹具收拢 conftest(两测试文件共用) - 测试: 519 → 537 全绿 Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -23,6 +23,7 @@ registry 只认本模块的 TenantSource 协议(async load_all / load_tenant),
|
||||
- 文件源数据坏:坏 JSON/slug 与目录不一致/零站点 → ValueError 带路径,
|
||||
wall/prompts 文件缺失 → FileNotFoundError 带路径(read_text 原样)。
|
||||
"""
|
||||
import copy
|
||||
import json
|
||||
import pathlib
|
||||
from dataclasses import dataclass
|
||||
@@ -33,6 +34,7 @@ from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession
|
||||
|
||||
from app.engine.services.ai_service import PROMPTS_KEYS
|
||||
from app.platform.db import Site, SiteDomain, Tenant, TenantConfigVersion
|
||||
from app.platform.defaults import DEFAULT_CONFIG
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@@ -224,3 +226,45 @@ class DbTenantSource:
|
||||
sites=sites,
|
||||
snapshot=snapshot,
|
||||
seed_dir=None)
|
||||
|
||||
|
||||
# ---- 出厂档案快照(Task 5 创建向导/Task 6 重置出厂的公共路径) ----------------------
|
||||
# 自 tenant_routes 迁入(Task 6 微审注记:配置 API 的 reset 也要用,挪公共位置
|
||||
# 供两个路由模块消费——此处已是文件源的地盘,零新增依赖)。
|
||||
|
||||
# 出厂档案目录:与 app.main.SEED_DIR 同一锚定规则(仓库根/seed_profiles),
|
||||
# 但不 import app.main——main 模块级挂路由,反向导入制造循环依赖。
|
||||
SEED_DIR = pathlib.Path(__file__).resolve().parents[2] / "seed_profiles"
|
||||
|
||||
# blank 身份组清理清单(brand 的这几个键置出厂默认;其余 brand 字段保留)
|
||||
_IDENTITY_BRAND_KEYS = ("title", "whatsapp", "email", "privacy_url")
|
||||
|
||||
|
||||
def _blankify(snapshot: dict) -> dict:
|
||||
"""出厂档案快照 → blank 形态:身份组置出厂默认,其余(含 wall/prompts)
|
||||
原样保留。深拷贝后改——纯函数纪律,绝不动入参。"""
|
||||
out = copy.deepcopy(snapshot)
|
||||
brand = dict(out.get("brand") or {})
|
||||
for key in _IDENTITY_BRAND_KEYS:
|
||||
brand[key] = DEFAULT_CONFIG["brand"][key]
|
||||
out["brand"] = brand
|
||||
push = dict(out.get("push") or {})
|
||||
push["prefix"] = DEFAULT_CONFIG["push"]["prefix"]
|
||||
out["push"] = push
|
||||
engine = dict(out.get("engine") or {})
|
||||
engine["context_seed_keys"] = dict(
|
||||
DEFAULT_CONFIG["engine"]["context_seed_keys"])
|
||||
out["engine"] = engine
|
||||
return out
|
||||
|
||||
|
||||
def _profile_snapshot(profile: str) -> dict:
|
||||
"""profile → 首版快照:auto/jiancai 读对应出厂档案原样;blank 读 jiancai
|
||||
档案再去身份组(wall/prompts 保留——见 Task 5 模块 docstring)。
|
||||
|
||||
重置出厂(spec §5.6"代码库当前模板,随平台升级演进")与创建向导共用
|
||||
本函数:每次调用现读 seed 目录,不缓存。未知 profile → LookupError
|
||||
(read_tenant 的文件源契约,调用方映射 400)。"""
|
||||
base_slug = "jiancai" if profile == "blank" else profile
|
||||
snapshot = FileTenantSource(SEED_DIR).read_tenant(base_slug).snapshot
|
||||
return _blankify(snapshot) if profile == "blank" else snapshot
|
||||
|
||||
Reference in New Issue
Block a user