hku-class/backend/app/api/directory.py

97 lines
3.5 KiB
Python

from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.deps import ensure_class_access, ensure_class_module_enabled, get_current_user, resolve_class_id_for_user
from app.db.database import get_db
from app.db.models import User
from app.schemas.user import UserPublic
from app.schemas.common import PageResponse
from app.services.directory_service import get_directory_role_counts, search_directory, user_to_public
from app.services.user_service import get_user_by_id
router = APIRouter(prefix="/api/directory", tags=["directory"])
@router.get("/stats")
async def get_directory_stats(
class_id: int | None = None,
user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
):
effective_class_id = resolve_class_id_for_user(user, class_id)
if effective_class_id is None:
return {"student_count": 0, "teacher_count": 0, "total": 0}
ensure_class_access(user, effective_class_id)
await ensure_class_module_enabled(db, effective_class_id, "directory")
counts = await get_directory_role_counts(db, effective_class_id)
return {
"student_count": counts["student"],
"teacher_count": counts["teacher"],
"total": counts["total"],
}
@router.get("/", response_model=PageResponse[UserPublic])
async def search_members(
search: str | None = None,
industry: str | None = None,
company: str | None = None,
class_id: int | None = None,
page: int = 1,
page_size: int = 20,
user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
):
effective_class_id = resolve_class_id_for_user(user, class_id)
if effective_class_id is None:
return PageResponse(items=[], total=0, page=page, page_size=page_size, total_pages=0)
ensure_class_access(user, effective_class_id)
await ensure_class_module_enabled(db, effective_class_id, "directory")
users, total = await search_directory(
db, effective_class_id, search, industry, company, page, page_size
)
total_pages = (total + page_size - 1) // page_size
include_contact = True # Same class, active members can see contact
return PageResponse(
items=[
user_to_public(u, effective_class_id, include_contact=include_contact)
for u in users
],
total=total,
page=page,
page_size=page_size,
total_pages=total_pages,
)
@router.get("/{user_id}", response_model=UserPublic)
async def get_member_detail(
user_id: int,
user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
):
target = await get_user_by_id(db, user_id)
if target is None or target.status != "approved":
raise HTTPException(status_code=404, detail="User not found")
# Privacy: only show contact info to same-class members
shared_class_ids = {
membership.class_id for membership in user.memberships
} & {
membership.class_id for membership in target.memberships
}
module_enabled = False
for shared_class_id in shared_class_ids:
try:
await ensure_class_module_enabled(db, shared_class_id, "directory")
module_enabled = True
break
except HTTPException:
continue
if not module_enabled:
raise HTTPException(status_code=403, detail="该功能当前未开放")
include_contact = bool(shared_class_ids)
scoped_class_id = next(iter(shared_class_ids), None)
return user_to_public(target, scoped_class_id, include_contact=include_contact)