"""管理后台路由(/admin/*,对应《03-接口文档》第 11 章) 角色矩阵(super 拥有全部权限): - reviewer:仅审核 → 待审核列表 / 审核视频 - operator:套餐、活动、看板 → 仪表盘 / 套餐 CRUD / 赠送 - 其余接口仅 super """ import redis.asyncio as aioredis from fastapi import APIRouter, Depends, Query, Request from sqlalchemy.ext.asyncio import AsyncSession from app.core.response import ok from app.database import get_db from app.deps import AdminIdentity, require_roles from app.redis_client import get_redis from app.schemas import admin as schema from app.services import admin_service router = APIRouter(prefix="/admin", tags=["管理后台"]) # 角色依赖:super 自动放行 require_reviewer = require_roles("reviewer") require_operator = require_roles("operator") require_super = require_roles() def _ip(request: Request) -> str: return request.client.host if request.client else "" # ---------- 认证 / 看板 ---------- @router.post("/auth/login") async def login(req: schema.AdminLoginReq, request: Request, db: AsyncSession = Depends(get_db)): return ok(await admin_service.login(db, req, _ip(request))) @router.get("/dashboard") async def dashboard(db: AsyncSession = Depends(get_db), admin: AdminIdentity = Depends(require_operator)): return ok(await admin_service.dashboard(db)) # ---------- 视频 ---------- @router.get("/videos/pending") async def list_pending_videos(page: int = Query(1, ge=1), page_size: int = Query(20, ge=1, le=100), db: AsyncSession = Depends(get_db), admin: AdminIdentity = Depends(require_reviewer)): return ok(await admin_service.list_pending_videos(db, page, page_size)) @router.get("/videos") async def list_videos(keyword: str | None = Query(None), status: int | None = Query(None), author_id: int | None = Query(None), page: int = Query(1, ge=1), page_size: int = Query(20, ge=1, le=100), db: AsyncSession = Depends(get_db), admin: AdminIdentity = Depends(require_super)): return ok(await admin_service.list_videos(db, keyword, status, author_id, page, page_size)) @router.post("/videos/{video_id}/review") async def review_video(video_id: int, req: schema.VideoReviewReq, request: Request, db: AsyncSession = Depends(get_db), redis: aioredis.Redis = Depends(get_redis), admin: AdminIdentity = Depends(require_reviewer)): return ok(await admin_service.review_video(db, redis, admin, video_id, req, _ip(request))) @router.post("/videos/{video_id}/offline") async def offline_video(video_id: int, req: schema.ReasonReq, request: Request, db: AsyncSession = Depends(get_db), redis: aioredis.Redis = Depends(get_redis), admin: AdminIdentity = Depends(require_super)): await admin_service.offline_video(db, redis, admin, video_id, req.reason, _ip(request)) return ok() # ---------- 用户 / 积分 ---------- @router.get("/users") async def list_users(keyword: str | None = Query(None), status: int | None = Query(None), page: int = Query(1, ge=1), page_size: int = Query(20, ge=1, le=100), db: AsyncSession = Depends(get_db), admin: AdminIdentity = Depends(require_super)): return ok(await admin_service.list_users(db, keyword, status, page, page_size)) @router.get("/users/{user_id}") async def get_user_detail(user_id: int, db: AsyncSession = Depends(get_db), admin: AdminIdentity = Depends(require_super)): return ok(await admin_service.get_user_detail(db, user_id)) @router.put("/users/{user_id}/status") async def set_user_status(user_id: int, req: schema.UserStatusReq, request: Request, db: AsyncSession = Depends(get_db), redis: aioredis.Redis = Depends(get_redis), admin: AdminIdentity = Depends(require_super)): return ok(await admin_service.set_user_status(db, redis, admin, user_id, req, _ip(request))) @router.post("/points/adjust") async def adjust_points(req: schema.PointsAdjustReq, request: Request, db: AsyncSession = Depends(get_db), admin: AdminIdentity = Depends(require_super)): return ok(await admin_service.adjust_points(db, admin, req, _ip(request))) @router.get("/points/records") async def list_point_records(user_id: int | None = Query(None), biz_type: int | None = Query(None), page: int = Query(1, ge=1), page_size: int = Query(20, ge=1, le=100), db: AsyncSession = Depends(get_db), admin: AdminIdentity = Depends(require_super)): return ok(await admin_service.list_point_records(db, user_id, biz_type, page, page_size)) # ---------- 订单 / 套餐 ---------- @router.get("/orders") async def list_orders(order_no: str | None = Query(None), user_id: int | None = Query(None), pay_method: int | None = Query(None), status: int | None = Query(None), start_date: str | None = Query(None), end_date: str | None = Query(None), page: int = Query(1, ge=1), page_size: int = Query(20, ge=1, le=100), db: AsyncSession = Depends(get_db), admin: AdminIdentity = Depends(require_super)): return ok(await admin_service.list_orders( db, order_no, user_id, pay_method, status, start_date, end_date, page, page_size )) @router.post("/orders/{order_no}/resettle") async def resettle_order(order_no: str, req: schema.ResettleReq, request: Request, db: AsyncSession = Depends(get_db), admin: AdminIdentity = Depends(require_super)): return ok(await admin_service.resettle_order(db, admin, order_no, req, _ip(request))) @router.post("/orders/{order_no}/refund") async def refund_order(order_no: str, req: schema.RefundReq, request: Request, db: AsyncSession = Depends(get_db), admin: AdminIdentity = Depends(require_super)): return ok(await admin_service.refund_order(db, admin, order_no, req, _ip(request))) @router.get("/packages") async def list_packages(db: AsyncSession = Depends(get_db), admin: AdminIdentity = Depends(require_operator)): return ok(await admin_service.list_packages(db)) @router.post("/packages") async def create_package(req: schema.PackageCreateReq, request: Request, db: AsyncSession = Depends(get_db), admin: AdminIdentity = Depends(require_operator)): return ok(await admin_service.create_package(db, admin, req, _ip(request))) @router.put("/packages/{package_id}") async def update_package(package_id: int, req: schema.PackageUpdateReq, request: Request, db: AsyncSession = Depends(get_db), admin: AdminIdentity = Depends(require_operator)): return ok(await admin_service.update_package(db, admin, package_id, req, _ip(request))) @router.delete("/packages/{package_id}") async def delete_package(package_id: int, request: Request, db: AsyncSession = Depends(get_db), admin: AdminIdentity = Depends(require_operator)): return ok(await admin_service.delete_package(db, admin, package_id, _ip(request))) # ---------- 赠送 / 日志 ---------- @router.post("/gift") async def gift(req: schema.GiftReq, request: Request, db: AsyncSession = Depends(get_db), admin: AdminIdentity = Depends(require_operator)): return ok(await admin_service.gift(db, admin, req, _ip(request))) @router.get("/logs") async def list_logs(admin_id: int | None = Query(None), action: str | None = Query(None), page: int = Query(1, ge=1), page_size: int = Query(20, ge=1, le=100), db: AsyncSession = Depends(get_db), admin: AdminIdentity = Depends(require_super)): return ok(await admin_service.list_logs(db, admin_id, action, page, page_size)) # ---------- 提现 ---------- @router.get("/withdrawals") async def list_withdrawals(status: int | None = Query(None), user_id: int | None = Query(None), page: int = Query(1, ge=1), page_size: int = Query(20, ge=1, le=100), db: AsyncSession = Depends(get_db), admin: AdminIdentity = Depends(require_super)): return ok(await admin_service.list_withdrawals(db, status, user_id, page, page_size)) @router.post("/withdrawals/{withdraw_no}/review") async def review_withdrawal(withdraw_no: str, req: schema.WithdrawReviewReq, request: Request, db: AsyncSession = Depends(get_db), redis: aioredis.Redis = Depends(get_redis), admin: AdminIdentity = Depends(require_super)): return ok(await admin_service.review_withdrawal(db, redis, admin, withdraw_no, req, _ip(request))) # ---------- 推荐 ---------- @router.get("/referrals") async def list_referrals(inviter_id: int | None = Query(None), status: int | None = Query(None), risk_flag: str | None = Query(None), page: int = Query(1, ge=1), page_size: int = Query(20, ge=1, le=100), db: AsyncSession = Depends(get_db), admin: AdminIdentity = Depends(require_super)): return ok(await admin_service.list_referrals(db, inviter_id, status, risk_flag, page, page_size)) @router.post("/referrals/{referral_id}/review") async def review_referral(referral_id: int, req: schema.ReferralReviewReq, request: Request, db: AsyncSession = Depends(get_db), admin: AdminIdentity = Depends(require_super)): return ok(await admin_service.review_referral(db, admin, referral_id, req, _ip(request))) # ---------- 配置 ---------- @router.get("/configs") async def list_configs(db: AsyncSession = Depends(get_db), admin: AdminIdentity = Depends(require_super)): return ok(await admin_service.list_configs(db)) @router.put("/configs/{config_key}") async def update_config(config_key: str, req: schema.ConfigUpdateReq, request: Request, db: AsyncSession = Depends(get_db), redis: aioredis.Redis = Depends(get_redis), admin: AdminIdentity = Depends(require_super)): return ok(await admin_service.update_config(db, redis, admin, config_key, req.value, _ip(request))) # ---------- 评论 / 举报 ---------- @router.get("/comments") async def list_comments(video_id: int | None = Query(None), user_id: int | None = Query(None), keyword: str | None = Query(None), page: int = Query(1, ge=1), page_size: int = Query(20, ge=1, le=100), db: AsyncSession = Depends(get_db), admin: AdminIdentity = Depends(require_super)): return ok(await admin_service.list_comments(db, video_id, user_id, keyword, page, page_size)) @router.delete("/comments/{comment_id}") async def delete_comment(comment_id: int, req: schema.ReasonReq, request: Request, db: AsyncSession = Depends(get_db), redis: aioredis.Redis = Depends(get_redis), admin: AdminIdentity = Depends(require_super)): await admin_service.delete_comment(db, redis, admin, comment_id, req.reason, _ip(request)) return ok() @router.get("/reports") async def list_reports(status: int | None = Query(None), target_type: int | None = Query(None), page: int = Query(1, ge=1), page_size: int = Query(20, ge=1, le=100), db: AsyncSession = Depends(get_db), admin: AdminIdentity = Depends(require_super)): return ok(await admin_service.list_reports(db, status, target_type, page, page_size)) @router.post("/reports/{report_id}/review") async def review_report(report_id: int, req: schema.ReportReviewReq, request: Request, db: AsyncSession = Depends(get_db), redis: aioredis.Redis = Depends(get_redis), admin: AdminIdentity = Depends(require_super)): return ok(await admin_service.review_report(db, redis, admin, report_id, req, _ip(request))) # ---------- 敏感词 ---------- @router.get("/sensitive-words") async def list_sensitive_words(keyword: str | None = Query(None), page: int = Query(1, ge=1), page_size: int = Query(20, ge=1, le=100), db: AsyncSession = Depends(get_db), admin: AdminIdentity = Depends(require_super)): return ok(await admin_service.list_sensitive_words(db, keyword, page, page_size)) @router.post("/sensitive-words") async def add_sensitive_word(req: schema.SensitiveWordAddReq, request: Request, db: AsyncSession = Depends(get_db), redis: aioredis.Redis = Depends(get_redis), admin: AdminIdentity = Depends(require_super)): return ok(await admin_service.add_sensitive_word(db, redis, admin, req.word, _ip(request))) @router.delete("/sensitive-words/{word_id}") async def delete_sensitive_word(word_id: int, request: Request, db: AsyncSession = Depends(get_db), redis: aioredis.Redis = Depends(get_redis), admin: AdminIdentity = Depends(require_super)): await admin_service.delete_sensitive_word(db, redis, admin, word_id, _ip(request)) return ok()