1
This commit is contained in:
@@ -12,7 +12,8 @@ from fastapi import APIRouter, Depends, Query, Request
|
||||
from pydantic import BaseModel, BaseModel as _BM, Field as _F, Field as _Field
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.core.response import ok
|
||||
from app.core import errors
|
||||
from app.core.response import ok, BizException
|
||||
from app.database import get_db
|
||||
from app.deps import AdminIdentity, require_roles
|
||||
from app.redis_client import get_redis
|
||||
@@ -620,6 +621,77 @@ class _VideoSeriesBind(BaseModel):
|
||||
episode_no: int = _Field(ge=1)
|
||||
|
||||
|
||||
# ---------- 批量操作(必须在 /series/{series_id} 路由之前注册,否则被参数路由抢先匹配)----------
|
||||
|
||||
class _SeriesRebuildResp(BaseModel):
|
||||
total: int = 0
|
||||
fixed: int = 0
|
||||
|
||||
|
||||
@router.post("/series/rebuild-counts")
|
||||
async def rebuild_series_counts(db: AsyncSession = Depends(get_db),
|
||||
admin: AdminIdentity = Depends(require_super)):
|
||||
"""重算所有系列的 total_episodes(按 status=2 的视频真实计数)"""
|
||||
from app.models.video import Series, Video
|
||||
from sqlalchemy import select, func
|
||||
|
||||
rows = (await db.execute(select(Series.id, Series.total_episodes))).all()
|
||||
total = len(rows)
|
||||
fixed = 0
|
||||
for sid, old_cnt in rows:
|
||||
new_cnt = (await db.execute(
|
||||
select(func.count(Video.id)).where(Video.series_id == sid, Video.status == 2)
|
||||
)).scalar_one()
|
||||
if old_cnt != new_cnt:
|
||||
s = await db.get(Series, sid)
|
||||
s.total_episodes = new_cnt
|
||||
fixed += 1
|
||||
await db.commit()
|
||||
return ok(_SeriesRebuildResp(total=total, fixed=fixed))
|
||||
|
||||
|
||||
class _SeriesBatchUpdate(BaseModel):
|
||||
series_ids: list[int] = _Field(min_length=1, max_length=200)
|
||||
status: int | None = _Field(None, ge=0, le=1)
|
||||
category_id: int | None = _Field(None, ge=0)
|
||||
|
||||
|
||||
@router.put("/series/batch")
|
||||
async def batch_update_series(req: _SeriesBatchUpdate, request: Request,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
admin: AdminIdentity = Depends(require_super)):
|
||||
"""批量编辑系列属性(当前支持 status / category_id)"""
|
||||
from app.models.admin import AdminLog
|
||||
from app.models.video import Series
|
||||
from sqlalchemy import select
|
||||
|
||||
if req.status is None and req.category_id is None:
|
||||
raise BizException(errors.PARAM_ERROR, "至少要传一个要修改的字段")
|
||||
|
||||
stmt = select(Series).where(Series.id.in_(req.series_ids))
|
||||
rows = (await db.execute(stmt)).scalars().all()
|
||||
affected = 0
|
||||
for s in rows:
|
||||
changed = False
|
||||
if req.status is not None and s.status != req.status:
|
||||
s.status = req.status
|
||||
changed = True
|
||||
if req.category_id is not None and s.category_id != req.category_id:
|
||||
s.category_id = req.category_id
|
||||
changed = True
|
||||
if changed:
|
||||
affected += 1
|
||||
|
||||
db.add(AdminLog(
|
||||
admin_id=admin.admin_id, action="series_batch_update",
|
||||
target=f"series:{','.join(str(i) for i in req.series_ids)}",
|
||||
detail=f"status={req.status} category_id={req.category_id} affected={affected}",
|
||||
ip=_ip(request),
|
||||
))
|
||||
await db.commit()
|
||||
return ok({"affected": affected})
|
||||
|
||||
|
||||
@router.post("/series")
|
||||
async def create_series(req: _SeriesCreate, db: AsyncSession = Depends(get_db),
|
||||
admin: AdminIdentity = Depends(require_super)):
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -28,7 +28,7 @@ from app.services import config_service, invite_service, membership_service, sen
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
REFRESH_TTL = 30 * 24 * 3600
|
||||
REFRESH_TTL = settings.refresh_token_ttl # 与 .env 的 REFRESH_TOKEN_TTL 同步
|
||||
DEACTIVATE_FREEZE_DAYS = 7
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user