"""admin 首页轮播种子管理:列出 / 新增 / 改 / 删 / 批量生成 / 预览(带审计)。 种子用于首页轮播「真实+种子混播」的兜底(见 app/repositories/ops_marquee.py)。 权限:operator 可改,super 恒可;预览为只读(任意已登录 admin)。 """ from __future__ import annotations from typing import Annotated from fastapi import APIRouter, Depends, HTTPException, Query, Request from app.admin.audit import write_audit from app.admin.deps import AdminDb, get_client_ip, get_current_admin, require_role from app.admin.schemas.ops_marquee_seed import ( OpsMarqueeSeedBulkCreate, OpsMarqueeSeedCreate, OpsMarqueeSeedOut, OpsMarqueeSeedUpdate, OpsSavingsFeedPreviewOut, ) from app.models.admin import AdminUser from app.models.ops_marquee_seed import OpsMarqueeSeed from app.repositories import ops_marquee router = APIRouter( prefix="/admin/api/marquee-seeds", tags=["admin-marquee-seeds"], dependencies=[Depends(get_current_admin)], ) def _out(seed: OpsMarqueeSeed) -> OpsMarqueeSeedOut: return OpsMarqueeSeedOut( id=seed.id, masked_user=seed.masked_user, min_cents=seed.min_cents, max_cents=seed.max_cents, enabled=seed.enabled, sort_order=seed.sort_order, created_at=seed.created_at.isoformat() if seed.created_at else None, ) @router.get("", response_model=list[OpsMarqueeSeedOut], summary="轮播种子列表") def list_seeds(db: AdminDb) -> list[OpsMarqueeSeedOut]: return [_out(s) for s in ops_marquee.list_seeds(db)] @router.get("/preview", response_model=OpsSavingsFeedPreviewOut, summary="预览实际混播 feed") def preview_feed( db: AdminDb, limit: Annotated[int, Query(ge=1, le=30)] = 8, ) -> OpsSavingsFeedPreviewOut: """返回客户端实际会看到的 feed(真实记录会插队、种子随机抽取/金额随机/名字合成),供运营对效果。""" return OpsSavingsFeedPreviewOut(items=ops_marquee.get_feed(db, limit=limit)) @router.post("", response_model=OpsMarqueeSeedOut, summary="新增轮播种子(带审计)") def create_seed( body: OpsMarqueeSeedCreate, request: Request, admin: Annotated[AdminUser, Depends(require_role("operator"))], db: AdminDb, ) -> OpsMarqueeSeedOut: try: seed = ops_marquee.create_seed( db, masked_user=body.masked_user, min_cents=body.min_cents, max_cents=body.max_cents, enabled=body.enabled, sort_order=body.sort_order, commit=False, ) except ValueError as e: raise HTTPException(status_code=400, detail=str(e)) from e write_audit( db, admin, action="ops_marquee_seed.create", target_type="ops_marquee_seed", target_id=None, detail=body.model_dump(), ip=get_client_ip(request), commit=False, ) db.commit() db.refresh(seed) return _out(seed) @router.post("/bulk", response_model=list[OpsMarqueeSeedOut], summary="批量生成种子(带审计)") def bulk_generate( body: OpsMarqueeSeedBulkCreate, request: Request, admin: Annotated[AdminUser, Depends(require_role("operator"))], db: AdminDb, ) -> list[OpsMarqueeSeedOut]: try: seeds = ops_marquee.bulk_generate( db, count=body.count, min_cents=body.min_cents, max_cents=body.max_cents, enabled=body.enabled, commit=False, ) except ValueError as e: raise HTTPException(status_code=400, detail=str(e)) from e write_audit( db, admin, action="ops_marquee_seed.bulk_generate", target_type="ops_marquee_seed", target_id=None, detail=body.model_dump(), ip=get_client_ip(request), commit=False, ) db.commit() for s in seeds: db.refresh(s) return [_out(s) for s in seeds] @router.patch("/{seed_id}", response_model=OpsMarqueeSeedOut, summary="改轮播种子(带审计)") def update_seed( seed_id: int, body: OpsMarqueeSeedUpdate, request: Request, admin: Annotated[AdminUser, Depends(require_role("operator"))], db: AdminDb, ) -> OpsMarqueeSeedOut: try: seed = ops_marquee.update_seed( db, seed_id, masked_user=body.masked_user, min_cents=body.min_cents, max_cents=body.max_cents, enabled=body.enabled, sort_order=body.sort_order, commit=False, ) except ValueError as e: raise HTTPException(status_code=404 if "不存在" in str(e) else 400, detail=str(e)) from e write_audit( db, admin, action="ops_marquee_seed.update", target_type="ops_marquee_seed", target_id=seed_id, detail=body.model_dump(exclude_none=True), ip=get_client_ip(request), commit=False, ) db.commit() db.refresh(seed) return _out(seed) @router.delete("/{seed_id}", summary="删轮播种子(带审计)") def delete_seed( seed_id: int, request: Request, admin: Annotated[AdminUser, Depends(require_role("operator"))], db: AdminDb, ) -> dict: ok = ops_marquee.delete_seed(db, seed_id, commit=False) if not ok: raise HTTPException(status_code=404, detail="种子不存在") write_audit( db, admin, action="ops_marquee_seed.delete", target_type="ops_marquee_seed", target_id=seed_id, detail=None, ip=get_client_ip(request), commit=False, ) db.commit() return {"deleted": True}