baseline: Clover 独立仓库首次基线提交
将 Clover 从上层产品包旧仓库中独立出来,建立专属版本控制。 当前状态=纵切片端到端已打通(登录→选品→出文出图→审核→下载包), M1文案质量去套路化已验收。此提交作为后续按核销清单逐条修复的基线。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
143
backend/app/api/v1/benchmarks.py
Normal file
143
backend/app/api/v1/benchmarks.py
Normal file
@@ -0,0 +1,143 @@
|
||||
"""
|
||||
app/api/v1/benchmarks.py — 标杆笔记 + 违禁词路由(管理员)
|
||||
主通道:截图+手填亮点(不做原始抓取)。
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import APIRouter, Depends
|
||||
from pydantic import BaseModel
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.core.database import get_db
|
||||
from app.core.response import ok, paginate, raise_not_found
|
||||
from app.middleware.workspace_guard import CurrentUser, require_admin, require_write_permission
|
||||
from app.models.product import BannedWord, BenchmarkNote
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
router = APIRouter(tags=["products"])
|
||||
|
||||
|
||||
class BenchmarkCreate(BaseModel):
|
||||
screenshot_url: str | None = None
|
||||
highlights: str | None = None # 手填亮点(主通道)
|
||||
link_url: str | None = None # 可选,不做自动抓取
|
||||
|
||||
|
||||
class BannedWordCreate(BaseModel):
|
||||
word: str
|
||||
level: str # auto_fix | soft_warn | hard_block
|
||||
replacement: str | None = None
|
||||
updatable: bool = True
|
||||
|
||||
|
||||
def _fmt_benchmark(b: BenchmarkNote) -> dict:
|
||||
return {
|
||||
"id": b.id, "product_id": b.product_id,
|
||||
"screenshot_url": b.screenshot_url,
|
||||
"highlights": b.highlights, "link_url": b.link_url,
|
||||
"created_at": b.created_at.isoformat(),
|
||||
}
|
||||
|
||||
|
||||
def _fmt_banned(bw: BannedWord) -> dict:
|
||||
return {
|
||||
"id": bw.id, "word": bw.word, "level": bw.level,
|
||||
"replacement": bw.replacement, "updatable": bw.updatable,
|
||||
"workspace_id": bw.workspace_id,
|
||||
}
|
||||
|
||||
|
||||
# ── 标杆笔记 ────────────────────────────────────────────────
|
||||
@router.get("/products/{product_id}/benchmarks")
|
||||
def list_benchmarks(
|
||||
product_id: int,
|
||||
current_user: Annotated[CurrentUser, Depends(require_write_permission)] = None,
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
items = (
|
||||
db.query(BenchmarkNote)
|
||||
.filter(
|
||||
BenchmarkNote.product_id == product_id,
|
||||
BenchmarkNote.workspace_id == current_user.workspace_id,
|
||||
)
|
||||
.all()
|
||||
)
|
||||
return ok({"items": [_fmt_benchmark(b) for b in items]})
|
||||
|
||||
|
||||
@router.post("/products/{product_id}/benchmarks")
|
||||
def create_benchmark(
|
||||
product_id: int, body: BenchmarkCreate,
|
||||
current_user: Annotated[CurrentUser, Depends(require_admin)] = None,
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
b = BenchmarkNote(
|
||||
workspace_id=current_user.workspace_id,
|
||||
product_id=product_id,
|
||||
screenshot_url=body.screenshot_url,
|
||||
highlights=body.highlights,
|
||||
link_url=body.link_url,
|
||||
)
|
||||
db.add(b); db.commit(); db.refresh(b)
|
||||
return ok(_fmt_benchmark(b))
|
||||
|
||||
|
||||
# ── 违禁词库 ────────────────────────────────────────────────
|
||||
@router.get("/banned-words")
|
||||
def list_banned_words(
|
||||
page: int = 1, page_size: int = 50,
|
||||
current_user: Annotated[CurrentUser, Depends(require_write_permission)] = None,
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
q = db.query(BannedWord).filter(BannedWord.workspace_id == current_user.workspace_id)
|
||||
total = q.count()
|
||||
items = q.offset((page - 1) * page_size).limit(page_size).all()
|
||||
return ok(paginate([_fmt_banned(bw) for bw in items], total, page, page_size))
|
||||
|
||||
|
||||
@router.post("/banned-words")
|
||||
def create_banned_word(
|
||||
body: BannedWordCreate,
|
||||
current_user: Annotated[CurrentUser, Depends(require_admin)] = None,
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
bw = BannedWord(
|
||||
workspace_id=current_user.workspace_id,
|
||||
word=body.word, level=body.level,
|
||||
replacement=body.replacement, updatable=body.updatable,
|
||||
)
|
||||
db.add(bw); db.commit(); db.refresh(bw)
|
||||
return ok(_fmt_banned(bw))
|
||||
|
||||
|
||||
@router.put("/banned-words/{word_id}")
|
||||
def update_banned_word(
|
||||
word_id: int, body: BannedWordCreate,
|
||||
current_user: Annotated[CurrentUser, Depends(require_admin)] = None,
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
bw = db.query(BannedWord).filter(BannedWord.id == word_id, BannedWord.workspace_id == current_user.workspace_id).first()
|
||||
if not bw:
|
||||
raise_not_found("违禁词不存在")
|
||||
if not bw.updatable:
|
||||
from app.core.response import raise_forbidden
|
||||
raise_forbidden("该违禁词不可修改")
|
||||
bw.word = body.word; bw.level = body.level
|
||||
bw.replacement = body.replacement; bw.updatable = body.updatable
|
||||
db.commit(); db.refresh(bw)
|
||||
return ok(_fmt_banned(bw))
|
||||
|
||||
|
||||
@router.delete("/banned-words/{word_id}")
|
||||
def delete_banned_word(
|
||||
word_id: int,
|
||||
current_user: Annotated[CurrentUser, Depends(require_admin)] = None,
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
bw = db.query(BannedWord).filter(BannedWord.id == word_id, BannedWord.workspace_id == current_user.workspace_id).first()
|
||||
if not bw:
|
||||
raise_not_found("违禁词不存在")
|
||||
db.delete(bw); db.commit()
|
||||
return ok({"deleted": word_id})
|
||||
Reference in New Issue
Block a user