Initial certificate system
This commit is contained in:
126
backend/app/api/routes/admin_learners.py
Normal file
126
backend/app/api/routes/admin_learners.py
Normal file
@@ -0,0 +1,126 @@
|
||||
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}
|
||||
Reference in New Issue
Block a user