- 产品编辑入口统一走 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>
137 lines
6.0 KiB
Python
137 lines
6.0 KiB
Python
"""
|
||
scripts/seed_data.py — 初始化北哥 workspace 种子数据
|
||
建3账号(admin/supervisor/operator) + workspace_members + 预置素颜霜 + 5条违禁词
|
||
幂等:已存在则跳过,可重复跑。
|
||
用法:cd backend && python3 scripts/seed_data.py
|
||
"""
|
||
import json
|
||
import os
|
||
import sys
|
||
|
||
# 把 backend/ 加入 path
|
||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||
|
||
from app.core.config import get_settings # noqa: E402 — path must be set first
|
||
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 DEFAULT_SEED_PASSWORD, hash_password # noqa: E402
|
||
|
||
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": 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 = {
|
||
"name": "素颜霜(预置样本)",
|
||
"category": "素颜霜",
|
||
"source": "preset",
|
||
"selling_points": json.dumps(["遮瑕提亮", "持妆", "养肤成分"], ensure_ascii=False),
|
||
"style_tone": "素人分享、真实不浮夸",
|
||
"text_angles": json.dumps(["痛点切入", "成分党", "使用场景", "质地肤感", "平价替代"], ensure_ascii=False),
|
||
"custom_prompt": None,
|
||
"is_active": True,
|
||
}
|
||
|
||
BANNED_WORDS = [
|
||
{"word": "美白", "level": "auto_fix", "replacement": "提亮肤色"},
|
||
{"word": "祛斑", "level": "auto_fix", "replacement": "改善暗沉"},
|
||
{"word": "速效", "level": "soft_warn", "replacement": "温和渐进"},
|
||
{"word": "医用", "level": "hard_block", "replacement": None},
|
||
{"word": "药妆", "level": "hard_block", "replacement": None},
|
||
]
|
||
|
||
|
||
def run():
|
||
db = SessionLocal()
|
||
try:
|
||
# ── workspace ──────────────────────────────────────
|
||
ws = db.query(Workspace).filter(Workspace.slug == WORKSPACE_SLUG).first()
|
||
if not ws:
|
||
ws = Workspace(name=WORKSPACE_NAME, slug=WORKSPACE_SLUG, is_active=True)
|
||
db.add(ws)
|
||
db.flush()
|
||
print(f"[+] workspace 已建: {ws.name} (id={ws.id})")
|
||
else:
|
||
print(f"[=] workspace 已存在: id={ws.id}")
|
||
|
||
# ── users + members ────────────────────────────────
|
||
for u_spec in USERS:
|
||
user = db.query(User).filter(User.username == u_spec["username"]).first()
|
||
if not user:
|
||
user = User(
|
||
username=u_spec["username"],
|
||
email=u_spec["email"],
|
||
hashed_password=hash_password(u_spec["password"]),
|
||
is_active=True,
|
||
must_change_password=True,
|
||
)
|
||
db.add(user)
|
||
db.flush()
|
||
print(f"[+] user: {user.username}")
|
||
else:
|
||
print(f"[=] user 已存在: {user.username}")
|
||
|
||
mem = db.query(WorkspaceMember).filter(
|
||
WorkspaceMember.workspace_id == ws.id,
|
||
WorkspaceMember.user_id == user.id,
|
||
).first()
|
||
if not mem:
|
||
db.add(WorkspaceMember(workspace_id=ws.id, user_id=user.id, role=u_spec["role"]))
|
||
print(f" -> 绑定 role={u_spec['role']}")
|
||
|
||
# ── 预置素颜霜 ─────────────────────────────────────
|
||
prod = db.query(Product).filter(
|
||
Product.workspace_id == ws.id, Product.name == PRODUCT["name"]
|
||
).first()
|
||
if not prod:
|
||
prod = Product(workspace_id=ws.id, **PRODUCT)
|
||
db.add(prod)
|
||
print(f"[+] product: {prod.name}")
|
||
else:
|
||
print(f"[=] product 已存在: {prod.name}")
|
||
|
||
# ── 违禁词 ─────────────────────────────────────────
|
||
for bw_spec in BANNED_WORDS:
|
||
existing = db.query(BannedWord).filter(
|
||
BannedWord.workspace_id == ws.id, BannedWord.word == bw_spec["word"]
|
||
).first()
|
||
if not existing:
|
||
db.add(BannedWord(workspace_id=ws.id, **bw_spec))
|
||
print(f"[+] banned_word: {bw_spec['word']} ({bw_spec['level']})")
|
||
else:
|
||
print(f"[=] banned_word 已存在: {bw_spec['word']}")
|
||
|
||
# ── user_api_keys:不再预置(倩倩姐2026-06-12拍板:系统埋的key彻底拔干净)──
|
||
# 每个用户登录后到「设置 → API Key」自录自用,录一次即记住。
|
||
# 种子不再从 APIPORTS_KEY 环境变量批量塞 key,避免"免key可用"。
|
||
|
||
db.commit()
|
||
print("\n种子数据初始化完成。")
|
||
except Exception as e:
|
||
db.rollback()
|
||
print(f"[ERROR] {e}")
|
||
raise
|
||
finally:
|
||
db.close()
|
||
|
||
|
||
if __name__ == "__main__":
|
||
run()
|