import uuid from typing import Annotated from fastapi import APIRouter, Depends, Query, status from sqlalchemy.ext.asyncio import AsyncSession from app.api.deps import require_admin from app.database import get_db from app.models.user import User from app.schemas.chat import ContextFileResponse, UpdateContextRequest from app.schemas.skill import ( CreateSkillRequest, SkillListResponse, SkillResponse, UpdateSkillRequest, ) from app.schemas.admin import ( AdminUserCreateRequest, AdminUserListResponse, AdminUserResponse, AdminUserUpdateRequest, ) from app.schemas.setting import SettingResponse, SettingsListResponse, UpdateSettingRequest from app.schemas.pdf_template import ( CreatePdfTemplateRequest, PdfTemplateListResponse, PdfTemplateResponse, PreviewRequest, PreviewResponse, UpdatePdfTemplateRequest, ) from app.services import context_service, skill_service, setting_service, admin_user_service, pdf_template_service router = APIRouter(prefix="/admin", tags=["admin"]) # --- Context --- @router.get("/context", response_model=ContextFileResponse | None) async def get_primary_context( _admin: Annotated[User, Depends(require_admin)], db: Annotated[AsyncSession, Depends(get_db)], ): ctx = await context_service.get_primary_context(db) if not ctx: return None return ContextFileResponse.model_validate(ctx) @router.put("/context", response_model=ContextFileResponse) async def update_primary_context( data: UpdateContextRequest, admin: Annotated[User, Depends(require_admin)], db: Annotated[AsyncSession, Depends(get_db)], ): ctx = await context_service.upsert_primary_context(db, data.content, admin.id) return ContextFileResponse.model_validate(ctx) # --- Skills --- @router.get("/skills", response_model=SkillListResponse) async def list_general_skills( _admin: Annotated[User, Depends(require_admin)], db: Annotated[AsyncSession, Depends(get_db)], ): skills = await skill_service.get_general_skills(db) return SkillListResponse(skills=[SkillResponse.model_validate(s) for s in skills]) @router.post("/skills", response_model=SkillResponse, status_code=status.HTTP_201_CREATED) async def create_general_skill( data: CreateSkillRequest, _admin: Annotated[User, Depends(require_admin)], db: Annotated[AsyncSession, Depends(get_db)], ): skill = await skill_service.create_general_skill(db, **data.model_dump()) return SkillResponse.model_validate(skill) @router.patch("/skills/{skill_id}", response_model=SkillResponse) async def update_general_skill( skill_id: uuid.UUID, data: UpdateSkillRequest, _admin: Annotated[User, Depends(require_admin)], db: Annotated[AsyncSession, Depends(get_db)], ): skill = await skill_service.update_general_skill(db, skill_id, **data.model_dump(exclude_unset=True)) return SkillResponse.model_validate(skill) @router.delete("/skills/{skill_id}", status_code=status.HTTP_204_NO_CONTENT) async def delete_general_skill( skill_id: uuid.UUID, _admin: Annotated[User, Depends(require_admin)], db: Annotated[AsyncSession, Depends(get_db)], ): await skill_service.delete_general_skill(db, skill_id) # --- Users --- @router.get("/users", response_model=AdminUserListResponse) async def list_users( _admin: Annotated[User, Depends(require_admin)], db: Annotated[AsyncSession, Depends(get_db)], limit: int = Query(default=50, le=200), offset: int = Query(default=0), ): users, total = await admin_user_service.list_users(db, limit, offset) return AdminUserListResponse( users=[AdminUserResponse.model_validate(u) for u in users], total=total, ) @router.post("/users", response_model=AdminUserResponse, status_code=status.HTTP_201_CREATED) async def create_user( data: AdminUserCreateRequest, _admin: Annotated[User, Depends(require_admin)], db: Annotated[AsyncSession, Depends(get_db)], ): user = await admin_user_service.create_user( db, data.email, data.username, data.password, data.full_name, data.role, data.max_chats, ) return AdminUserResponse.model_validate(user) @router.patch("/users/{user_id}", response_model=AdminUserResponse) async def update_user( user_id: uuid.UUID, data: AdminUserUpdateRequest, _admin: Annotated[User, Depends(require_admin)], db: Annotated[AsyncSession, Depends(get_db)], ): user = await admin_user_service.update_user(db, user_id, **data.model_dump(exclude_unset=True)) return AdminUserResponse.model_validate(user) # --- Settings --- @router.get("/settings", response_model=SettingsListResponse) async def get_settings( _admin: Annotated[User, Depends(require_admin)], db: Annotated[AsyncSession, Depends(get_db)], ): settings = await setting_service.get_all_settings(db) return SettingsListResponse(settings=[SettingResponse.model_validate(s) for s in settings]) @router.patch("/settings/{key}", response_model=SettingResponse) async def update_setting( key: str, data: UpdateSettingRequest, admin: Annotated[User, Depends(require_admin)], db: Annotated[AsyncSession, Depends(get_db)], ): setting = await setting_service.upsert_setting(db, key, data.value, admin.id) return SettingResponse.model_validate(setting) # --- PDF Templates --- @router.get("/pdf-templates", response_model=PdfTemplateListResponse) async def list_pdf_templates( _admin: Annotated[User, Depends(require_admin)], db: Annotated[AsyncSession, Depends(get_db)], ): templates = await pdf_template_service.list_templates(db, active_only=False) return PdfTemplateListResponse(templates=[PdfTemplateResponse.model_validate(t) for t in templates]) @router.post("/pdf-templates", response_model=PdfTemplateResponse, status_code=status.HTTP_201_CREATED) async def create_pdf_template( data: CreatePdfTemplateRequest, _admin: Annotated[User, Depends(require_admin)], db: Annotated[AsyncSession, Depends(get_db)], ): template = await pdf_template_service.create_template(db, **data.model_dump()) return PdfTemplateResponse.model_validate(template) @router.get("/pdf-templates/{template_id}", response_model=PdfTemplateResponse) async def get_pdf_template( template_id: uuid.UUID, _admin: Annotated[User, Depends(require_admin)], db: Annotated[AsyncSession, Depends(get_db)], ): template = await pdf_template_service.get_template(db, template_id) return PdfTemplateResponse.model_validate(template) @router.patch("/pdf-templates/{template_id}", response_model=PdfTemplateResponse) async def update_pdf_template( template_id: uuid.UUID, data: UpdatePdfTemplateRequest, _admin: Annotated[User, Depends(require_admin)], db: Annotated[AsyncSession, Depends(get_db)], ): template = await pdf_template_service.update_template(db, template_id, **data.model_dump(exclude_unset=True)) return PdfTemplateResponse.model_validate(template) @router.delete("/pdf-templates/{template_id}", status_code=status.HTTP_204_NO_CONTENT) async def delete_pdf_template( template_id: uuid.UUID, _admin: Annotated[User, Depends(require_admin)], db: Annotated[AsyncSession, Depends(get_db)], ): await pdf_template_service.delete_template(db, template_id) @router.post("/pdf-templates/preview", response_model=PreviewResponse) async def preview_pdf_template( data: PreviewRequest, _admin: Annotated[User, Depends(require_admin)], ): html = await pdf_template_service.render_preview(data.html_content) return PreviewResponse(html=html) # --- Usage Stats --- @router.get("/usage") async def get_usage_stats( _admin: Annotated[User, Depends(require_admin)], db: Annotated[AsyncSession, Depends(get_db)], ): from sqlalchemy import select, func from app.models.user import User as UserModel from app.services.usage_service import get_user_message_count_today, get_user_token_count_today, get_user_daily_limits result = await db.execute(select(UserModel).where(UserModel.is_active == True).order_by(UserModel.username)) # noqa: E712 users = result.scalars().all() stats = [] for u in users: limits = await get_user_daily_limits(db, u) stats.append({ "user_id": str(u.id), "username": u.username, "messages_today": await get_user_message_count_today(db, u.id), "message_limit": limits["message_limit"], "tokens_today": await get_user_token_count_today(db, u.id), "token_limit": limits["token_limit"], }) return {"users": stats}