Files
arboris-novel/backend/app/api/routers/admin.py
2025-10-21 09:51:27 +08:00

341 lines
12 KiB
Python

import logging
from typing import List, Optional
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy import func, select
from sqlalchemy.ext.asyncio import AsyncSession
from ...core.dependencies import get_current_admin
from ...db.session import get_session
from ...models import NovelProject, UsageMetric, User
from ...schemas.admin import (
AdminNovelSummary,
DailyRequestLimit,
Statistics,
UpdateLogCreate,
UpdateLogRead,
UpdateLogUpdate,
)
from ...schemas.config import SystemConfigCreate, SystemConfigRead, SystemConfigUpdate
from ...schemas.prompt import PromptCreate, PromptRead, PromptUpdate
from ...schemas.novel import (
Chapter as ChapterSchema,
NovelProject as NovelProjectSchema,
NovelSectionResponse,
NovelSectionType,
)
from ...schemas.user import PasswordChangeRequest, User as UserSchema
from ...services.auth_service import AuthService
from ...services.admin_setting_service import AdminSettingService
from ...services.config_service import ConfigService
from ...services.novel_service import NovelService
from ...services.prompt_service import PromptService
from ...services.update_log_service import UpdateLogService
from ...services.user_service import UserService
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/api/admin", tags=["Admin"])
def get_prompt_service(session: AsyncSession = Depends(get_session)) -> PromptService:
return PromptService(session)
def get_update_log_service(session: AsyncSession = Depends(get_session)) -> UpdateLogService:
return UpdateLogService(session)
def get_admin_setting_service(session: AsyncSession = Depends(get_session)) -> AdminSettingService:
return AdminSettingService(session)
def get_config_service(session: AsyncSession = Depends(get_session)) -> ConfigService:
return ConfigService(session)
def get_novel_service(session: AsyncSession = Depends(get_session)) -> NovelService:
return NovelService(session)
def get_user_service(session: AsyncSession = Depends(get_session)) -> UserService:
return UserService(session)
def get_auth_service(session: AsyncSession = Depends(get_session)) -> AuthService:
return AuthService(session)
@router.get("/stats", response_model=Statistics)
async def read_statistics(
session: AsyncSession = Depends(get_session),
_: None = Depends(get_current_admin),
) -> Statistics:
novel_count = await session.scalar(select(func.count(NovelProject.id))) or 0
user_count = await session.scalar(select(func.count(User.id))) or 0
usage = await session.get(UsageMetric, "api_request_count")
api_request_count = usage.value if usage else 0
logger.info("管理员获取统计数据:小说=%s,用户=%s,请求=%s", novel_count, user_count, api_request_count)
return Statistics(novel_count=novel_count, user_count=user_count, api_request_count=api_request_count)
@router.get("/users", response_model=List[UserSchema])
async def list_users(
service: UserService = Depends(get_user_service),
_: None = Depends(get_current_admin),
) -> List[UserSchema]:
users = await service.list_users()
logger.info("管理员请求用户列表,共 %s", len(users))
return [UserSchema.model_validate(user) for user in users]
@router.get("/novel-projects", response_model=List[AdminNovelSummary])
async def list_novel_projects(
service: NovelService = Depends(get_novel_service),
_: None = Depends(get_current_admin),
) -> List[AdminNovelSummary]:
projects = await service.list_projects_for_admin()
logger.info("管理员查看项目列表,共 %s", len(projects))
return projects
@router.get("/novel-projects/{project_id}", response_model=NovelProjectSchema)
async def get_novel_project(
project_id: str,
service: NovelService = Depends(get_novel_service),
_: None = Depends(get_current_admin),
) -> NovelProjectSchema:
logger.info("管理员查看项目详情:%s", project_id)
return await service.get_project_schema_for_admin(project_id)
@router.get("/novel-projects/{project_id}/sections/{section}", response_model=NovelSectionResponse)
async def get_novel_project_section(
project_id: str,
section: NovelSectionType,
service: NovelService = Depends(get_novel_service),
_: None = Depends(get_current_admin),
) -> NovelSectionResponse:
logger.info("管理员查看项目 %s%s 区段", project_id, section)
return await service.get_section_data_for_admin(project_id, section)
@router.get("/novel-projects/{project_id}/chapters/{chapter_number}", response_model=ChapterSchema)
async def get_novel_project_chapter(
project_id: str,
chapter_number: int,
service: NovelService = Depends(get_novel_service),
_: None = Depends(get_current_admin),
) -> ChapterSchema:
logger.info("管理员查看项目 %s%s 章详情", project_id, chapter_number)
return await service.get_chapter_schema_for_admin(project_id, chapter_number)
@router.get("/prompts", response_model=List[PromptRead])
async def list_prompts(
service: PromptService = Depends(get_prompt_service),
_: None = Depends(get_current_admin),
) -> List[PromptRead]:
prompts = await service.list_prompts()
logger.info("管理员请求提示词列表,共 %s", len(prompts))
return prompts
@router.post("/prompts", response_model=PromptRead, status_code=status.HTTP_201_CREATED)
async def create_prompt(
payload: PromptCreate,
service: PromptService = Depends(get_prompt_service),
_: None = Depends(get_current_admin),
) -> PromptRead:
prompt = await service.create_prompt(payload)
logger.info("管理员创建提示词:%s", prompt.id)
return prompt
@router.get("/prompts/{prompt_id}", response_model=PromptRead)
async def get_prompt(
prompt_id: int,
service: PromptService = Depends(get_prompt_service),
_: None = Depends(get_current_admin),
) -> PromptRead:
prompt = await service.get_prompt_by_id(prompt_id)
if not prompt:
logger.warning("提示词 %s 不存在", prompt_id)
raise HTTPException(status_code=404, detail="提示词不存在")
logger.info("管理员获取提示词:%s", prompt_id)
return prompt
@router.patch("/prompts/{prompt_id}", response_model=PromptRead)
async def update_prompt(
prompt_id: int,
payload: PromptUpdate,
service: PromptService = Depends(get_prompt_service),
_: None = Depends(get_current_admin),
) -> PromptRead:
result = await service.update_prompt(prompt_id, payload)
if not result:
logger.warning("提示词 %s 不存在,无法更新", prompt_id)
raise HTTPException(status_code=404, detail="提示词不存在")
logger.info("管理员更新提示词:%s", prompt_id)
return result
@router.delete("/prompts/{prompt_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_prompt(
prompt_id: int,
service: PromptService = Depends(get_prompt_service),
_: None = Depends(get_current_admin),
) -> None:
deleted = await service.delete_prompt(prompt_id)
if not deleted:
logger.warning("提示词 %s 不存在,无法删除", prompt_id)
raise HTTPException(status_code=404, detail="提示词不存在")
logger.info("管理员删除提示词:%s", prompt_id)
@router.get("/update-logs", response_model=List[UpdateLogRead])
async def list_update_logs(
service: UpdateLogService = Depends(get_update_log_service),
_: None = Depends(get_current_admin),
) -> List[UpdateLogRead]:
logs = await service.list_logs()
logger.info("管理员查看更新日志列表,共 %s", len(logs))
return [UpdateLogRead.model_validate(log) for log in logs]
@router.post("/update-logs", response_model=UpdateLogRead, status_code=status.HTTP_201_CREATED)
async def create_update_log(
payload: UpdateLogCreate,
service: UpdateLogService = Depends(get_update_log_service),
current_admin=Depends(get_current_admin),
) -> UpdateLogRead:
log = await service.create_log(
payload.content,
creator=current_admin.username,
is_pinned=payload.is_pinned or False,
)
logger.info("管理员 %s 创建更新日志:%s", current_admin.username, log.id)
return UpdateLogRead.model_validate(log)
@router.delete("/update-logs/{log_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_update_log(
log_id: int,
service: UpdateLogService = Depends(get_update_log_service),
_: None = Depends(get_current_admin),
) -> None:
await service.delete_log(log_id)
logger.info("管理员删除更新日志:%s", log_id)
@router.patch("/update-logs/{log_id}", response_model=UpdateLogRead)
async def update_update_log(
log_id: int,
payload: UpdateLogUpdate,
service: UpdateLogService = Depends(get_update_log_service),
_: None = Depends(get_current_admin),
) -> UpdateLogRead:
log = await service.update_log(
log_id,
content=payload.content,
is_pinned=payload.is_pinned,
)
logger.info("管理员更新日志 %s", log_id)
return UpdateLogRead.model_validate(log)
@router.get("/settings/daily-request-limit", response_model=DailyRequestLimit)
async def get_daily_limit(
service: AdminSettingService = Depends(get_admin_setting_service),
_: None = Depends(get_current_admin),
) -> DailyRequestLimit:
value = await service.get("daily_request_limit", "100")
logger.info("管理员查询每日请求上限:%s", value)
return DailyRequestLimit(limit=int(value or 100))
@router.put("/settings/daily-request-limit", response_model=DailyRequestLimit)
async def update_daily_limit(
payload: DailyRequestLimit,
service: AdminSettingService = Depends(get_admin_setting_service),
_: None = Depends(get_current_admin),
) -> DailyRequestLimit:
await service.set("daily_request_limit", str(payload.limit))
logger.info("管理员设置每日请求上限为 %s", payload.limit)
return payload
@router.get("/system-configs", response_model=List[SystemConfigRead])
async def list_system_configs(
service: ConfigService = Depends(get_config_service),
_: None = Depends(get_current_admin),
) -> List[SystemConfigRead]:
configs = await service.list_configs()
logger.info("管理员获取系统配置,共 %s", len(configs))
return configs
@router.get("/system-configs/{key}", response_model=SystemConfigRead)
async def get_system_config(
key: str,
service: ConfigService = Depends(get_config_service),
_: None = Depends(get_current_admin),
) -> SystemConfigRead:
config = await service.get_config(key)
if not config:
logger.warning("系统配置 %s 不存在", key)
raise HTTPException(status_code=404, detail="配置项不存在")
logger.info("管理员查询系统配置:%s", key)
return config
@router.put("/system-configs/{key}", response_model=SystemConfigRead)
async def upsert_system_config(
key: str,
payload: SystemConfigCreate,
service: ConfigService = Depends(get_config_service),
_: None = Depends(get_current_admin),
) -> SystemConfigRead:
logger.info("管理员写入系统配置:%s", key)
return await service.upsert_config(
SystemConfigCreate(key=key, value=payload.value, description=payload.description)
)
@router.patch("/system-configs/{key}", response_model=SystemConfigRead)
async def patch_system_config(
key: str,
payload: SystemConfigUpdate,
service: ConfigService = Depends(get_config_service),
_: None = Depends(get_current_admin),
) -> SystemConfigRead:
config = await service.patch_config(key, payload)
if not config:
logger.warning("系统配置 %s 不存在,无法更新", key)
raise HTTPException(status_code=404, detail="配置项不存在")
logger.info("管理员部分更新系统配置:%s", key)
return config
@router.delete("/system-configs/{key}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_system_config(
key: str,
service: ConfigService = Depends(get_config_service),
_: None = Depends(get_current_admin),
) -> None:
deleted = await service.remove_config(key)
if not deleted:
logger.warning("系统配置 %s 不存在,无法删除", key)
raise HTTPException(status_code=404, detail="配置项不存在")
logger.info("管理员删除系统配置:%s", key)
@router.post("/password", status_code=status.HTTP_204_NO_CONTENT)
async def change_password(
payload: PasswordChangeRequest,
current_admin=Depends(get_current_admin),
service: AuthService = Depends(get_auth_service),
) -> None:
await service.change_password(current_admin.username, payload.old_password, payload.new_password)
logger.info("管理员 %s 修改密码", current_admin.username)