This commit is contained in:
lz_db
2026-10-25 11:46:44 +08:00
parent 893dfed0ed
commit 494f667719
63 changed files with 5213 additions and 239 deletions
+54 -2
View File
@@ -24,11 +24,13 @@ UPLOAD_RATE_LIMIT = 5
# 注意:/videos/feed、/videos/categories、/videos/search、/videos/upload/* 须先于 /videos/{video_id} 注册
@router.get("/feed")
async def get_feed(cursor: str | None = Query(None), limit: int = Query(10, ge=1, le=20),
category_id: int | None = Query(None),
category_id: int | None = Query(None), source_id: int | None = Query(None),
type: str = Query("recommend", pattern="^(recommend|latest|hot|liked|viewed)$"),
group: str | None = Query(None, pattern="^(series)$"),
db: AsyncSession = Depends(get_db), redis: aioredis.Redis = Depends(get_redis),
identity: Identity = Depends(get_identity)):
return ok(await video_service.get_feed(db, redis, identity, type, cursor, limit, category_id))
return ok(await video_service.get_feed(
db, redis, identity, type, cursor, limit, category_id, source_id, group))
@router.get("/categories")
@@ -36,6 +38,12 @@ async def get_categories(db: AsyncSession = Depends(get_db)):
return ok(await video_service.list_categories(db))
@router.get("/sources")
async def get_sources(db: AsyncSession = Depends(get_db)):
"""内容来源列表(麻豆/91...),供 App 分类页筛选"""
return ok(await video_service.list_sources(db))
@router.get("/search")
async def search_videos(request: Request, keyword: str = Query(min_length=1, max_length=30),
sort: str = Query("hot", pattern="^(hot|latest)$"),
@@ -103,6 +111,38 @@ async def delete_video(video_id: int, db: AsyncSession = Depends(get_db),
return ok()
# 站点管理员(按注册邮箱判定):可在 App 长按弹窗里下架任意视频
SITE_ADMIN_EMAIL = "256kb@gmail.com"
@router.post("/{video_id}/offline")
async def offline_video_app(
video_id: int,
reason: str = Form(default="管理员长按下架"),
db: AsyncSession = Depends(get_db),
redis: aioredis.Redis = Depends(get_redis),
identity: Identity = Depends(require_user),
):
"""App 端长按下架(仅限指定管理员邮箱);与后台 /admin/videos/{id}/offline 复用同一业务"""
from sqlalchemy import select
from app.core import errors
from app.core.response import BizException
from app.deps import AdminIdentity
from app.models.user import User
from app.services import admin_service
user = (await db.execute(
select(User).where(User.id == identity.user_id)
)).scalars().first()
if user is None or (user.email or "").lower() != SITE_ADMIN_EMAIL:
raise BizException(errors.FORBIDDEN, "无权限")
admin = AdminIdentity(admin_id=user.id, role="super")
await admin_service.offline_video(db, redis, admin, video_id, reason, ip="app")
return ok()
@router.post("/{video_id}/watch")
async def report_watch(video_id: int, req: WatchReq, db: AsyncSession = Depends(get_db),
redis: aioredis.Redis = Depends(get_redis),
@@ -141,6 +181,7 @@ async def get_series(series_id: int, db: AsyncSession = Depends(get_db)):
"title": series.title,
"cover_url": series.cover_url,
"description": series.description,
"source_id": series.source_id or 0,
"total_episodes": len(eps),
},
"episodes": [
@@ -181,6 +222,15 @@ async def get_video_series(video_id: int, db: AsyncSession = Depends(get_db)):
prev_ep = all_eps[current_idx - 1] if current_idx > 0 else None
next_ep = all_eps[current_idx + 1] if 0 <= current_idx < len(all_eps) - 1 else None
# 系列所属来源名称
from app.models.video import Source
source_name = ""
if series.source_id:
source_name = (await db.execute(
select(Source.name).where(Source.id == series.source_id)
)).scalar_one_or_none() or ""
return ok({
"has_series": True,
"current_episode_no": video.episode_no,
@@ -188,6 +238,8 @@ async def get_video_series(video_id: int, db: AsyncSession = Depends(get_db)):
"id": series.id,
"title": series.title,
"cover_url": series.cover_url,
"source_id": series.source_id or 0,
"source_name": source_name,
"total_episodes": len(all_eps),
},
"episodes": [