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)