"""The colleague directory. A member-visible directory any authenticated user may browse: colleagues' `{id, name, role, department}` — no email, no password hash. The same permission-safe, non-admin shape as `ReviewerCandidate` in `api/documents.py`, deliberately separate from the admin-only `/admin/users`. """ import uuid from typing import Annotated from fastapi import APIRouter, Depends from pydantic import BaseModel from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from app.auth.deps import get_current_user from app.db import get_db from app.errors import ApiError from app.models import Department, User, UserRole router = APIRouter(prefix="/people", tags=["people"]) class PersonOut(BaseModel): """A colleague as the directory shows them — never email or credentials.""" id: uuid.UUID name: str role: UserRole department: str | None def _select_people(): return select( User.id, User.name, User.role, Department.name.label("department"), ).join(Department, User.department_id == Department.id, isouter=True) def _person(row) -> PersonOut: return PersonOut( id=row.id, name=row.name, role=row.role, department=row.department, ) @router.get("") async def list_people( user: Annotated[User, Depends(get_current_user)], db: Annotated[AsyncSession, Depends(get_db)], ) -> list[PersonOut]: """Every colleague, ordered by name. Visible to any authenticated user.""" rows = (await db.execute(_select_people().order_by(User.name))).all() return [_person(row) for row in rows] @router.get("/{person_id}") async def get_person( person_id: uuid.UUID, user: Annotated[User, Depends(get_current_user)], db: Annotated[AsyncSession, Depends(get_db)], ) -> PersonOut: """One colleague's profile.""" row = (await db.execute(_select_people().where(User.id == person_id))).first() if row is None: raise ApiError(404, "Person not found.", "not_found") return _person(row)