from fastapi import APIRouter, Depends, HTTPException, Query, status from sqlalchemy import or_ from sqlalchemy.orm import Session from app.api.deps import require_roles from app.db.session import get_db from app.models import AdminUser, Learner, LearnerNameHistory from app.schemas.learner import LearnerCreate, LearnerOut, LearnerUpdate from app.services.logs import diff_values, log_action, mask_phone router = APIRouter() @router.get("", response_model=list[LearnerOut]) def list_learners( keyword: str | None = Query(default=None), db: Session = Depends(get_db), _: AdminUser = Depends(require_roles("system_admin", "certificate_admin", "readonly")), ) -> list[Learner]: query = db.query(Learner).filter(Learner.status != "deleted").order_by(Learner.id.desc()) if keyword: like = f"%{keyword}%" query = query.filter(or_(Learner.current_name.like(like), Learner.phone.like(like), Learner.student_no.like(like))) return query.limit(100).all() @router.post("", response_model=LearnerOut, status_code=status.HTTP_201_CREATED) def create_learner( payload: LearnerCreate, db: Session = Depends(get_db), admin: AdminUser = Depends(require_roles("system_admin", "certificate_admin")), ) -> Learner: existing = db.query(Learner).filter(Learner.phone == payload.phone).first() if existing: if existing.current_name != payload.current_name: db.add(LearnerNameHistory(learner_id=existing.id, name=payload.current_name, source="manual")) existing.current_name = payload.current_name existing.student_no = payload.student_no existing.remark = payload.remark log_action(db, admin, "update_learner", "learner", existing.id, {"name": existing.current_name, "phone": mask_phone(existing.phone)}) db.commit() db.refresh(existing) return existing learner = Learner( phone=payload.phone, current_name=payload.current_name, student_no=payload.student_no, remark=payload.remark, ) db.add(learner) db.commit() db.refresh(learner) db.add(LearnerNameHistory(learner_id=learner.id, name=learner.current_name, source="manual")) log_action(db, admin, "create_learner", "learner", learner.id, {"name": learner.current_name, "phone": mask_phone(learner.phone), "status": learner.status}) db.commit() return learner @router.get("/{learner_id}", response_model=LearnerOut) def get_learner( learner_id: int, db: Session = Depends(get_db), _: AdminUser = Depends(require_roles("system_admin", "certificate_admin", "readonly")), ) -> Learner: learner = db.get(Learner, learner_id) if not learner: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Learner not found") return learner @router.put("/{learner_id}", response_model=LearnerOut) def update_learner( learner_id: int, payload: LearnerUpdate, db: Session = Depends(get_db), admin: AdminUser = Depends(require_roles("system_admin", "certificate_admin")), ) -> Learner: learner = db.get(Learner, learner_id) if not learner: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Learner not found") duplicate = db.query(Learner).filter(Learner.phone == payload.phone, Learner.id != learner_id).first() if duplicate: raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="Phone already exists") before = { "phone": learner.phone, "current_name": learner.current_name, "student_no": learner.student_no, "status": learner.status, "remark": learner.remark, } if learner.current_name != payload.current_name: db.add(LearnerNameHistory(learner_id=learner.id, name=payload.current_name, source="manual")) learner.phone = payload.phone learner.current_name = payload.current_name learner.student_no = payload.student_no learner.status = payload.status learner.remark = payload.remark after = { "phone": learner.phone, "current_name": learner.current_name, "student_no": learner.student_no, "status": learner.status, "remark": learner.remark, } changes = diff_values(before, after, list(after.keys())) if "phone" in changes: changes["phone"] = {"before": mask_phone(changes["phone"]["before"]), "after": mask_phone(changes["phone"]["after"])} log_action(db, admin, "update_learner", "learner", learner.id, {"name": learner.current_name, "phone": mask_phone(learner.phone), "changes": changes}) db.commit() db.refresh(learner) return learner @router.delete("/{learner_id}") def delete_learner( learner_id: int, db: Session = Depends(get_db), admin: AdminUser = Depends(require_roles("system_admin", "certificate_admin")), ) -> dict[str, bool]: learner = db.get(Learner, learner_id) if not learner: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Learner not found") learner.status = "deleted" log_action(db, admin, "delete_learner", "learner", learner.id, {"name": learner.current_name, "phone": mask_phone(learner.phone)}) db.commit() return {"ok": True}