import uuid from fastapi import APIRouter, Depends, HTTPException, Query from sqlalchemy import func, select from sqlalchemy.ext.asyncio import AsyncSession from app.database import get_db from app.dependencies import get_current_user, require_site_admin from app.models import Room, RoomMembership, User from app.schemas.admin import ( AdminRoomRead, AdminUserRead, AuditLogEntryRead, ResetPasswordRequest, TransferOwnershipRequest, ) from app.schemas.webhook import EventSubscriptionAdminRead, WebhookIncomingAdminRead from app.services.admin_service import ( CannotActOnSelfError, RoomNotFoundError, TargetNotRoomMemberError, UserNotFoundError, list_rooms_admin, list_users, reset_user_password, set_room_archived, set_user_active, set_user_site_admin, transfer_ownership_admin, ) from app.services.audit import list_audit_log from app.services.webhook_service import ( list_all_event_subscriptions_admin, list_all_incoming_webhooks_admin, ) router = APIRouter(prefix="/api/admin", tags=["admin"]) @router.get("/users", response_model=list[AdminUserRead]) async def list_users_endpoint( current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): require_site_admin(current_user) return await list_users(db) @router.post("/users/{user_id}/deactivate", response_model=AdminUserRead) async def deactivate_user_endpoint( user_id: uuid.UUID, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): require_site_admin(current_user) try: return await set_user_active(db, current_user, user_id, active=False) except CannotActOnSelfError: raise HTTPException(status_code=400, detail="Cannot deactivate your own account") except UserNotFoundError: raise HTTPException(status_code=404, detail="User not found") @router.post("/users/{user_id}/reactivate", response_model=AdminUserRead) async def reactivate_user_endpoint( user_id: uuid.UUID, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): require_site_admin(current_user) try: return await set_user_active(db, current_user, user_id, active=True) except CannotActOnSelfError: raise HTTPException(status_code=400, detail="Cannot reactivate your own account") except UserNotFoundError: raise HTTPException(status_code=404, detail="User not found") @router.post("/users/{user_id}/reset-password", status_code=204) async def reset_user_password_endpoint( user_id: uuid.UUID, data: ResetPasswordRequest, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): require_site_admin(current_user) try: await reset_user_password(db, current_user, user_id, data.new_password) except UserNotFoundError: raise HTTPException(status_code=404, detail="User not found") @router.post("/users/{user_id}/promote", response_model=AdminUserRead) async def promote_user_endpoint( user_id: uuid.UUID, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): require_site_admin(current_user) try: return await set_user_site_admin(db, current_user, user_id, is_admin=True) except CannotActOnSelfError: raise HTTPException(status_code=400, detail="Cannot promote your own account") except UserNotFoundError: raise HTTPException(status_code=404, detail="User not found") @router.post("/users/{user_id}/demote", response_model=AdminUserRead) async def demote_user_endpoint( user_id: uuid.UUID, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): require_site_admin(current_user) try: return await set_user_site_admin(db, current_user, user_id, is_admin=False) except CannotActOnSelfError: raise HTTPException(status_code=400, detail="Cannot demote your own account") except UserNotFoundError: raise HTTPException(status_code=404, detail="User not found") @router.get("/rooms", response_model=list[AdminRoomRead]) async def list_rooms_endpoint( current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): require_site_admin(current_user) rooms = await list_rooms_admin(db) return [ AdminRoomRead( id=room.id, name=room.name, description=room.description, is_private=room.is_private, is_archived=room.is_archived, owner_id=room.owner_id, created_at=room.created_at, member_count=member_count, ) for room, member_count in rooms ] @router.post("/rooms/{room_id}/archive", response_model=AdminRoomRead) async def archive_room_endpoint( room_id: uuid.UUID, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): require_site_admin(current_user) try: room = await set_room_archived(db, current_user, room_id, archived=True) except RoomNotFoundError: raise HTTPException(status_code=404, detail="Room not found") return await _to_admin_room_read(db, room) @router.post("/rooms/{room_id}/unarchive", response_model=AdminRoomRead) async def unarchive_room_endpoint( room_id: uuid.UUID, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): require_site_admin(current_user) try: room = await set_room_archived(db, current_user, room_id, archived=False) except RoomNotFoundError: raise HTTPException(status_code=404, detail="Room not found") return await _to_admin_room_read(db, room) @router.post("/rooms/{room_id}/transfer-ownership", response_model=AdminRoomRead) async def transfer_ownership_endpoint( room_id: uuid.UUID, data: TransferOwnershipRequest, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): require_site_admin(current_user) try: room = await transfer_ownership_admin(db, current_user, room_id, data.new_owner_id) except RoomNotFoundError: raise HTTPException(status_code=404, detail="Room not found") except TargetNotRoomMemberError: raise HTTPException( status_code=400, detail="New owner must already be a member of the room" ) return await _to_admin_room_read(db, room) async def _to_admin_room_read(db: AsyncSession, room: Room) -> AdminRoomRead: result = await db.execute( select(func.count()).select_from(RoomMembership).where(RoomMembership.room_id == room.id) ) member_count = result.scalar_one() return AdminRoomRead( id=room.id, name=room.name, description=room.description, is_private=room.is_private, is_archived=room.is_archived, owner_id=room.owner_id, created_at=room.created_at, member_count=member_count, ) @router.get("/audit-log", response_model=list[AuditLogEntryRead]) async def list_audit_log_endpoint( limit: int = Query(default=50, ge=1, le=200), offset: int = Query(default=0, ge=0), current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): require_site_admin(current_user) entries = await list_audit_log(db, limit=limit, offset=offset) return [ AuditLogEntryRead( id=e.id, actor_id=e.actor_id, actor_username=e.actor.username, action=e.action, target_type=e.target_type, target_id=e.target_id, metadata=e.metadata_, created_at=e.created_at, ) for e in entries ] @router.get("/webhooks/incoming", response_model=list[WebhookIncomingAdminRead]) async def list_incoming_webhooks_admin_endpoint( current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): require_site_admin(current_user) webhooks = await list_all_incoming_webhooks_admin(db) return [ WebhookIncomingAdminRead( id=w.id, room_id=w.room_id, token=w.token, created_by=w.created_by, description=w.description, created_at=w.created_at, room_name=w.room.name, created_by_username=w.creator.username, ) for w in webhooks ] @router.get("/event-subscriptions", response_model=list[EventSubscriptionAdminRead]) async def list_event_subscriptions_admin_endpoint( current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): require_site_admin(current_user) subscriptions = await list_all_event_subscriptions_admin(db) return [ EventSubscriptionAdminRead( id=s.id, room_id=s.room_id, event_types=s.event_types, target_url=s.target_url, created_by=s.created_by, created_at=s.created_at, room_name=s.room.name if s.room else None, created_by_username=s.creator.username, ) for s in subscriptions ]