""" Admin router — plugin management and user CRUD. All endpoints require admin role. """ from fastapi import APIRouter, Depends, HTTPException, status from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from app.dependencies import get_db, require_admin from app.models.plugin import Plugin from app.models.user import User from app.plugins.loader import BUILTIN_ACTIONS from app.plugins.registry import action_registry from app.schemas.admin import PluginRead, PluginUpdate, UserCreate, UserRead, UserUpdate from app.services.auth import hash_password router = APIRouter(prefix="/api/v1/admin", tags=["admin"]) # ── Plugins ──────────────────────────────────────────────────────────────────── @router.get("/plugins", response_model=list[PluginRead]) async def list_plugins( db: AsyncSession = Depends(get_db), _: User = Depends(require_admin), ): result = await db.execute(select(Plugin).order_by(Plugin.name)) plugins = result.scalars().all() out = [] for p in plugins: action = BUILTIN_ACTIONS.get(p.name) out.append( PluginRead( name=p.name, display_name=p.display_name, description=p.description, enabled=p.enabled, registered_at=p.registered_at, compatible_categories=action.compatible_categories if action else [], ) ) return out @router.patch("/plugins/{name}", response_model=PluginRead) async def update_plugin( name: str, body: PluginUpdate, db: AsyncSession = Depends(get_db), _: User = Depends(require_admin), ): plugin = await db.get(Plugin, name) if plugin is None: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Plugin not found") plugin.enabled = body.enabled # Hot-reload the registry immediately if body.enabled: action = BUILTIN_ACTIONS.get(name) if action: action_registry.register(action) else: action_registry.unregister(name) await db.flush() action = BUILTIN_ACTIONS.get(name) return PluginRead( name=plugin.name, display_name=plugin.display_name, description=plugin.description, enabled=plugin.enabled, registered_at=plugin.registered_at, compatible_categories=action.compatible_categories if action else [], ) # ── Users ────────────────────────────────────────────────────────────────────── @router.get("/users", response_model=list[UserRead]) async def list_users( db: AsyncSession = Depends(get_db), _: User = Depends(require_admin), ): result = await db.execute(select(User).order_by(User.username)) return result.scalars().all() @router.post("/users", response_model=UserRead, status_code=status.HTTP_201_CREATED) async def create_user( body: UserCreate, db: AsyncSession = Depends(get_db), _: User = Depends(require_admin), ): # Check username uniqueness existing = await db.execute(select(User).where(User.username == body.username)) if existing.scalar_one_or_none() is not None: raise HTTPException( status_code=status.HTTP_409_CONFLICT, detail=f"Username '{body.username}' is already taken", ) user = User( username=body.username, email=body.email, full_name=body.full_name, password_hash=hash_password(body.password), role=body.role, is_active=True, ) db.add(user) await db.flush() await db.refresh(user) return user @router.patch("/users/{user_id}", response_model=UserRead) async def update_user( user_id: int, body: UserUpdate, db: AsyncSession = Depends(get_db), current_admin: User = Depends(require_admin), ): user = await db.get(User, user_id) if user is None: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found") if body.email is not None: user.email = body.email if body.full_name is not None: user.full_name = body.full_name if body.role is not None: user.role = body.role if body.is_active is not None: user.is_active = body.is_active if body.password: user.password_hash = hash_password(body.password) await db.flush() await db.refresh(user) return user @router.delete("/users/{user_id}", status_code=status.HTTP_204_NO_CONTENT) async def delete_user( user_id: int, db: AsyncSession = Depends(get_db), current_admin: User = Depends(require_admin), ): if user_id == current_admin.id: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Cannot delete your own account", ) user = await db.get(User, user_id) if user is None: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found") await db.delete(user)