980a2db6d9
- 基础设施层 async:SQLAlchemy 异步引擎/会话、33 Repository async 化、models/security/seed 迁入 infrastructure、新增 cache.py(redis.asyncio) 与 oss.py(aioboto3) - 接口层:routers 迁 api/routers 并全 async,dependencies 迁 api/dependencies(get_db/get_current_user async) - 依赖:sqlalchemy[asyncio]/aiosqlite/asyncmy/redis/aioboto3;config 异步 URL + Redis/OSS 配置 - 删除废弃:旧同步 db/dependencies/repositories/storage - 验证:平台 19 路由 + 培训 48 路由全注册;/health /auth/login /auth/me /admin/tasks /notifications 等接口 async 可用
213 lines
7.4 KiB
Python
213 lines
7.4 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""运营端业务管理端点:任务 / 服务商 / 内容 / 系统配置 / 数据统计。
|
|
|
|
全部要求 operator 角色(内部子角色再按权限细分),并写审计日志。
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException, Request
|
|
from pydantic import BaseModel
|
|
|
|
from ..dependencies import get_db
|
|
from ...rbac import require_permission, require_roles, write_audit
|
|
from ...infrastructure.repositories import Database
|
|
|
|
router = APIRouter(prefix="/admin", tags=["admin-op"])
|
|
|
|
|
|
class TaskCreateRequest(BaseModel):
|
|
title: str
|
|
category: str = ""
|
|
sub_category: str = ""
|
|
description: str = ""
|
|
mode: str = "grab"
|
|
budget_min: int = 0
|
|
budget_max: int = 0
|
|
deadline: str = ""
|
|
publisher_name: str = ""
|
|
|
|
|
|
class TaskStatusRequest(BaseModel):
|
|
status: str # review/published/completed/cancelled
|
|
|
|
|
|
class ProviderCreateRequest(BaseModel):
|
|
name: str
|
|
category: str = ""
|
|
level: str = "certified"
|
|
status: str = "pending"
|
|
contact: str = ""
|
|
|
|
|
|
class ProviderUpdateRequest(BaseModel):
|
|
level: str | None = None
|
|
status: str | None = None
|
|
contact: str | None = None
|
|
|
|
|
|
class ContentCreateRequest(BaseModel):
|
|
type: str = "news"
|
|
title: str
|
|
summary: str = ""
|
|
body: str = ""
|
|
status: str = "draft"
|
|
|
|
|
|
class ContentStatusRequest(BaseModel):
|
|
status: str # published/offline/draft
|
|
|
|
|
|
class ConfigUpdateRequest(BaseModel):
|
|
value: str
|
|
description: str = ""
|
|
|
|
|
|
# ── 任务管理 ──────────────────────────────────────────────────────────────
|
|
@router.get("/tasks", summary="任务列表")
|
|
async def list_tasks(
|
|
status: str | None = None,
|
|
db: Database = Depends(get_db),
|
|
_u: dict = Depends(require_roles("operator")),
|
|
):
|
|
return await db.tasks.list(status=status)
|
|
|
|
|
|
@router.post("/tasks", summary="创建任务")
|
|
async def create_task(
|
|
req: TaskCreateRequest,
|
|
request: Request,
|
|
db: Database = Depends(get_db),
|
|
actor: dict = Depends(require_permission("action:task.manage")),
|
|
):
|
|
task = await db.tasks.create(req.model_dump(exclude_none=True))
|
|
await write_audit(db, action="task.create", resource="task", resource_id=task["id"],
|
|
detail=task["title"], user=actor, request=request)
|
|
return task
|
|
|
|
|
|
@router.post("/tasks/{task_id}/status", summary="更新任务状态")
|
|
async def set_task_status(
|
|
task_id: str,
|
|
req: TaskStatusRequest,
|
|
request: Request,
|
|
db: Database = Depends(get_db),
|
|
actor: dict = Depends(require_permission("action:task.manage")),
|
|
):
|
|
if await db.tasks.get(task_id) is None:
|
|
raise HTTPException(status_code=404, detail="Task not found")
|
|
task = await db.tasks.set_status(task_id, req.status)
|
|
await write_audit(db, action="task.status", resource="task", resource_id=task_id,
|
|
detail=req.status, user=actor, request=request)
|
|
return task
|
|
|
|
|
|
# ── 服务商管理 ────────────────────────────────────────────────────────────
|
|
@router.get("/providers", summary="服务商列表")
|
|
async def list_providers(
|
|
status: str | None = None,
|
|
db: Database = Depends(get_db),
|
|
_u: dict = Depends(require_roles("operator")),
|
|
):
|
|
return await db.providers.list(status=status)
|
|
|
|
|
|
@router.post("/providers", summary="创建服务商")
|
|
async def create_provider(
|
|
req: ProviderCreateRequest,
|
|
request: Request,
|
|
db: Database = Depends(get_db),
|
|
actor: dict = Depends(require_permission("action:provider.manage")),
|
|
):
|
|
prov = await db.providers.create(req.model_dump(exclude_none=True))
|
|
await write_audit(db, action="provider.create", resource="provider", resource_id=prov["id"],
|
|
detail=prov["name"], user=actor, request=request)
|
|
return prov
|
|
|
|
|
|
@router.post("/providers/{provider_id}/update", summary="更新服务商(评级/状态)")
|
|
async def update_provider(
|
|
provider_id: str,
|
|
req: ProviderUpdateRequest,
|
|
request: Request,
|
|
db: Database = Depends(get_db),
|
|
actor: dict = Depends(require_permission("action:provider.manage")),
|
|
):
|
|
updated = await db.providers.update(provider_id, req.model_dump(exclude_none=True))
|
|
if updated is None:
|
|
raise HTTPException(status_code=404, detail="Provider not found")
|
|
await write_audit(db, action="provider.update", resource="provider", resource_id=provider_id,
|
|
detail=str(req.model_dump(exclude_none=True)), user=actor, request=request)
|
|
return updated
|
|
|
|
|
|
# ── 内容管理 ──────────────────────────────────────────────────────────────
|
|
@router.get("/content", summary="内容列表")
|
|
async def list_content(
|
|
ctype: str | None = None,
|
|
status: str | None = None,
|
|
db: Database = Depends(get_db),
|
|
_u: dict = Depends(require_roles("operator")),
|
|
):
|
|
return await db.content.list(ctype=ctype, status=status)
|
|
|
|
|
|
@router.post("/content", summary="创建内容")
|
|
async def create_content(
|
|
req: ContentCreateRequest,
|
|
request: Request,
|
|
db: Database = Depends(get_db),
|
|
actor: dict = Depends(require_permission("action:content.manage")),
|
|
):
|
|
item = await db.content.create(req.model_dump(exclude_none=True))
|
|
await write_audit(db, action="content.create", resource="content", resource_id=item["id"],
|
|
detail=item["title"], user=actor, request=request)
|
|
return item
|
|
|
|
|
|
@router.post("/content/{content_id}/status", summary="发布/下架内容")
|
|
async def set_content_status(
|
|
content_id: str,
|
|
req: ContentStatusRequest,
|
|
request: Request,
|
|
db: Database = Depends(get_db),
|
|
actor: dict = Depends(require_permission("action:content.manage")),
|
|
):
|
|
item = await db.content.set_status(content_id, req.status)
|
|
if item is None:
|
|
raise HTTPException(status_code=404, detail="Content not found")
|
|
await write_audit(db, action="content.status", resource="content", resource_id=content_id,
|
|
detail=req.status, user=actor, request=request)
|
|
return item
|
|
|
|
|
|
# ── 系统配置 ──────────────────────────────────────────────────────────────
|
|
@router.get("/config", summary="系统配置列表")
|
|
async def list_config(
|
|
db: Database = Depends(get_db),
|
|
_u: dict = Depends(require_roles("operator")),
|
|
):
|
|
return await db.config.all()
|
|
|
|
|
|
@router.put("/config/{key}", summary="更新系统配置")
|
|
async def set_config(
|
|
key: str,
|
|
req: ConfigUpdateRequest,
|
|
request: Request,
|
|
db: Database = Depends(get_db),
|
|
actor: dict = Depends(require_permission("action:config.manage")),
|
|
):
|
|
cfg = await db.config.set(key, req.value, req.description)
|
|
await write_audit(db, action="config.update", resource="config", resource_id=key,
|
|
detail=req.value, user=actor, request=request)
|
|
return cfg
|
|
|
|
|
|
# ── 数据统计 ──────────────────────────────────────────────────────────────
|
|
@router.get("/stats/overview", summary="运营数据总览")
|
|
async def stats_overview(
|
|
db: Database = Depends(get_db),
|
|
_u: dict = Depends(require_roles("operator")),
|
|
):
|
|
return await db.stats.overview()
|