上线版: 产品表单统一+form嵌套修复+用户管理+部署+三套叙事

- 产品编辑入口统一走 ProductFormFull(卖点/风格/人群/品牌词全字段);
  修复开任务页 <form> 套 <form> 致"编辑产品"报错、改不了、跳回首个产品
- dashboard 入口卡片对齐实际路由: 系统管理(/config) 与 工作配置(/settings) 分开;
  settings ?tab=products 直达改用挂载后读 URL, 消除 hydration mismatch
- 新增用户管理(users API/admin service/改密页) + alembic 022/023/024
- 上线部署: Dockerfile / docker-compose.prod+https / nginx https / .env.example
- A8 三套正交叙事(痛点/场景/成分背书) + beige 调色去AI化 + 飞轮 text_import 高权重信号

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
yangqianqian
2026-06-30 18:08:13 +08:00
parent a77212781c
commit df1856d793
150 changed files with 8616 additions and 1765 deletions

View File

@@ -16,7 +16,7 @@ from app.core.database import SessionLocal # noqa: E402
from app.models.user import User # noqa: E402
from app.models.workspace import Workspace, WorkspaceMember # noqa: E402
from app.models.product import Product, BannedWord # noqa: E402
from app.services.auth_service import hash_password # noqa: E402
from app.services.auth_service import DEFAULT_SEED_PASSWORD, hash_password # noqa: E402
get_settings() # 触发 .env 校验,缺变量早报错
@@ -24,10 +24,18 @@ get_settings() # 触发 .env 校验,缺变量早报错
WORKSPACE_NAME = "北哥小红书车间"
WORKSPACE_SLUG = "beige-xhs"
SEED_PASSWORDS = {
"admin": os.environ.get("CLOVER_SEED_PASSWORD_ADMIN", os.environ.get("CLOVER_SEED_PASSWORD", DEFAULT_SEED_PASSWORD)),
"supervisor": os.environ.get("CLOVER_SEED_PASSWORD_SUPERVISOR", os.environ.get("CLOVER_SEED_PASSWORD", DEFAULT_SEED_PASSWORD)),
"operator": os.environ.get("CLOVER_SEED_PASSWORD_OPERATOR", os.environ.get("CLOVER_SEED_PASSWORD", DEFAULT_SEED_PASSWORD)),
}
if get_settings().APP_ENV == "production" and any(p == DEFAULT_SEED_PASSWORD for p in SEED_PASSWORDS.values()):
raise RuntimeError("Production seed requires CLOVER_SEED_PASSWORD; default password is forbidden")
USERS = [
{"username": "admin", "email": "admin@clover.local", "password": "Clover2026!", "role": "admin"},
{"username": "supervisor", "email": "supervisor@clover.local", "password": "Clover2026!", "role": "supervisor"},
{"username": "operator", "email": "operator@clover.local", "password": "Clover2026!", "role": "operator"},
{"username": "admin", "email": "admin@clover.local", "password": SEED_PASSWORDS["admin"], "role": "admin"},
{"username": "supervisor", "email": "supervisor@clover.local", "password": SEED_PASSWORDS["supervisor"], "role": "supervisor"},
{"username": "operator", "email": "operator@clover.local", "password": SEED_PASSWORDS["operator"], "role": "operator"},
]
PRODUCT = {
@@ -72,6 +80,7 @@ def run():
email=u_spec["email"],
hashed_password=hash_password(u_spec["password"]),
is_active=True,
must_change_password=True,
)
db.add(user)
db.flush()
@@ -109,38 +118,9 @@ def run():
else:
print(f"[=] banned_word 已存在: {bw_spec['word']}")
# ── user_api_keysR3明文key只在加密瞬间用不打印不落明文)──
apiports_key = os.environ.get("APIPORTS_KEY", "").strip()
if apiports_key:
from app.models.workspace import UserApiKey
from app.utils.fernet_utils import encrypt_key
for u_spec in USERS:
u = db.query(User).filter(User.username == u_spec["username"]).first()
if not u:
continue
existing_key = db.query(UserApiKey).filter(
UserApiKey.user_id == u.id,
UserApiKey.workspace_id == ws.id,
UserApiKey.provider == "apiports",
).first()
encrypted = encrypt_key(apiports_key)
last4 = apiports_key[-4:] if len(apiports_key) >= 4 else "****"
if existing_key:
existing_key.encrypted_key = encrypted
existing_key.key_last4 = last4
print(f"[=] api_key updated: {u.username} apiports ***{last4}")
else:
db.add(UserApiKey(
user_id=u.id,
workspace_id=ws.id,
provider="apiports",
encrypted_key=encrypted,
key_last4=last4,
))
print(f"[+] api_key: {u.username} apiports ***{last4}")
apiports_key = None # 明文用完即清基石B
else:
print("[!] APIPORTS_KEY 未设置,跳过 user_api_keys 录入(部署时需手动录入或重跑种子)")
# ── user_api_keys不再预置倩倩姐2026-06-12拍板系统埋的key彻底拔干净)──
# 每个用户登录后到「设置 → API Key」自录自用录一次即记住。
# 种子不再从 APIPORTS_KEY 环境变量批量塞 key避免"免key可用"。
db.commit()
print("\n种子数据初始化完成。")