from fastapi import APIRouter, Depends, HTTPException from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.future import select from database import get_db import models from routers.auth import get_password_hash from pydantic import BaseModel router = APIRouter(prefix="/api/admin", tags=["admin"]) @router.post("/reset-data") async def reset_data(db: AsyncSession = Depends(get_db)): from sqlalchemy import text try: await db.execute(text("DELETE FROM face_embeddings")) await db.execute(text("DELETE FROM alerts")) await db.execute(text("DELETE FROM attendance")) await db.execute(text("DELETE FROM students")) await db.execute(text("DELETE FROM faculty")) await db.execute(text("DELETE FROM hod WHERE email != 'shyamala@gmail.com'")) await db.execute(text("UPDATE subjects SET total_classes = 0, faculty_name = NULL")) # Add new HOD or update existing from models import HOD result = await db.execute(select(HOD).where(HOD.email == 'shyamala@gmail.com')) hod = result.scalars().first() if not hod: hod = HOD(name="Shyamala", email="shyamala@gmail.com", password_hash=get_password_hash("1"), department="CSE") db.add(hod) else: hod.password_hash = get_password_hash("1") await db.commit() return {"message": "Successfully cleared attendance, alerts, reset subjects, and updated HOD shyamala@gmail.com"} except Exception as e: await db.rollback() raise HTTPException(status_code=500, detail=str(e)) @router.post("/reset-attendance") async def reset_attendance(db: AsyncSession = Depends(get_db)): from sqlalchemy import text try: # Delete only attendance and alerts (which depend on attendance/absences) await db.execute(text("DELETE FROM alerts")) await db.execute(text("DELETE FROM attendance")) # Reset classes taken for all subjects, but keep the assigned faculty await db.execute(text("UPDATE subjects SET total_classes = 0")) await db.commit() return {"message": "Successfully cleared all attendance records and reset classes taken to 0."} except Exception as e: await db.rollback() raise HTTPException(status_code=500, detail=str(e)) class StudentCreate(BaseModel): roll_no: str name: str semester: int section: str branch: str = "CSE" student_email: str parent_email: str parent_name: str password: str @router.post("/student") async def create_student(data: StudentCreate, db: AsyncSession = Depends(get_db)): roll_no_upper = data.roll_no.upper() # Check for existing student res = await db.execute(select(models.Student).where(models.Student.roll_no == roll_no_upper)) if res.scalars().first(): raise HTTPException(status_code=400, detail=f"Student with roll number {roll_no_upper} already exists.") student = models.Student( roll_no=roll_no_upper, name=data.name, semester=data.semester, section=data.section.upper(), branch=data.branch, student_email=data.student_email, parent_email=data.parent_email, parent_name=data.parent_name, password_hash=get_password_hash(data.password) ) db.add(student) await db.commit() return {"message": "Student created successfully", "roll_no": student.roll_no} @router.get("/student/{query}") async def get_student_admin(query: str, db: AsyncSession = Depends(get_db)): if "@" in query: res = await db.execute(select(models.Student).where(models.Student.student_email.ilike(query))) else: res = await db.execute(select(models.Student).where(models.Student.roll_no == query.upper())) student = res.scalars().first() if not student: raise HTTPException(404, "Student not found") return { "roll_no": student.roll_no, "name": student.name, "semester": student.semester, "section": student.section, "branch": student.branch, "student_email": student.student_email, "parent_email": student.parent_email, "parent_name": student.parent_name } @router.put("/student/{roll_no}") async def update_student(roll_no: str, data: StudentCreate, db: AsyncSession = Depends(get_db)): res = await db.execute(select(models.Student).where(models.Student.roll_no == roll_no.upper())) student = res.scalars().first() if not student: raise HTTPException(404, "Student not found") student.name = data.name student.semester = data.semester student.section = data.section.upper() student.branch = data.branch student.student_email = data.student_email student.parent_email = data.parent_email student.parent_name = data.parent_name if data.password: from routers.auth import get_password_hash student.password_hash = get_password_hash(data.password) await db.commit() return {"message": "Student updated successfully"} @router.delete("/student/{roll_no}") async def delete_student(roll_no: str, db: AsyncSession = Depends(get_db)): roll_no = roll_no.upper() res = await db.execute(select(models.Student).where(models.Student.roll_no == roll_no)) student = res.scalars().first() if not student: raise HTTPException(404, "Student not found") # Delete related records manually to avoid IntegrityError if cascades are missing await db.execute(models.Alert.__table__.delete().where(models.Alert.roll_no == roll_no)) await db.execute(models.Attendance.__table__.delete().where(models.Attendance.roll_no == roll_no)) await db.execute(models.FaceEmbedding.__table__.delete().where(models.FaceEmbedding.roll_no == roll_no)) await db.execute(models.Student.__table__.delete().where(models.Student.roll_no == roll_no)) await db.commit() # Rebuild FAISS index from services.face_engine import face_engine import faiss, numpy as np from sqlalchemy import text rows = await db.execute(select(models.FaceEmbedding.roll_no, models.FaceEmbedding.embedding)) new_index = faiss.IndexFlatIP(512) new_roll_map = [] for r_no, emb in rows.all(): emb_arr = np.array(emb, dtype='float32') new_index.add(emb_arr.reshape(1,-1)) new_roll_map.append(r_no) face_engine.index = new_index face_engine.roll_map = new_roll_map face_engine._save() for idx, r_no in enumerate(new_roll_map): await db.execute(text("UPDATE students SET faiss_index=:idx WHERE roll_no=:r_no"), {"idx": idx, "r_no": r_no}) await db.commit() return {"message": "Student deleted successfully"} class FacultyCreate(BaseModel): name: str email: str password: str subjects: str # Comma-separated subject names in format "semester:subject_name" (e.g. "5:Deep Learning") @router.post("/faculty") async def create_faculty(data: FacultyCreate, db: AsyncSession = Depends(get_db)): # Check for existing faculty res_fac = await db.execute(select(models.Faculty).where(models.Faculty.email == data.email)) if res_fac.scalars().first(): raise HTTPException(status_code=400, detail=f"Faculty with email {data.email} already exists.") sub_items = [s.strip() for s in data.subjects.split(",") if s.strip()] sub_ids = [] for item in sub_items: sem = 1 s_name = item if ":" in item: parts = item.split(":", 1) try: sem = int(parts[0].strip()) s_name = parts[1].strip() except ValueError: sem = 1 s_name = item.strip() res = await db.execute(select(models.Subject).where(models.Subject.name.ilike(s_name))) sub = res.scalars().first() if not sub: # Create new subject if it doesn't exist sub = models.Subject( name=s_name, code=s_name[:3].upper() + str(sem) + "01", # auto-gen code semester=sem, total_classes=0, faculty_name=data.name ) db.add(sub) await db.flush() sub_ids.append(str(sub.id)) fac = models.Faculty( name=data.name, email=data.email, password_hash=get_password_hash(data.password), subjects=",".join(sub_ids) ) db.add(fac) await db.commit() return {"message": "Faculty created successfully"} @router.get("/faculty/{email}") async def get_faculty_admin(email: str, db: AsyncSession = Depends(get_db)): res = await db.execute(select(models.Faculty).where(models.Faculty.email == email)) fac = res.scalars().first() if not fac: raise HTTPException(404, "Faculty not found") # Get subject names with semester prefix sub_ids = [int(i.strip()) for i in fac.subjects.split(",") if i.strip()] sub_strings = [] if sub_ids: s_res = await db.execute(select(models.Subject).where(models.Subject.id.in_(sub_ids))) subs = s_res.scalars().all() sub_strings = [f"{s.semester}:{s.name}" for s in subs] return { "name": fac.name, "email": fac.email, "subjects": ", ".join(sub_strings) } @router.put("/faculty/{email}") async def update_faculty(email: str, data: FacultyCreate, db: AsyncSession = Depends(get_db)): res = await db.execute(select(models.Faculty).where(models.Faculty.email == email)) fac = res.scalars().first() if not fac: raise HTTPException(404, "Faculty not found") fac.name = data.name if data.password: from routers.auth import get_password_hash fac.password_hash = get_password_hash(data.password) # Re-map subjects sub_items = [s.strip() for s in data.subjects.split(",") if s.strip()] sub_ids = [] for item in sub_items: sem = 1 s_name = item if ":" in item: parts = item.split(":", 1) try: sem = int(parts[0].strip()) s_name = parts[1].strip() except ValueError: sem = 1 s_name = item.strip() s_res = await db.execute(select(models.Subject).where(models.Subject.name.ilike(s_name))) sub = s_res.scalars().first() if not sub: sub = models.Subject( name=s_name, code=s_name[:3].upper() + str(sem) + "01", semester=sem, total_classes=0, faculty_name=data.name ) db.add(sub) await db.flush() sub_ids.append(str(sub.id)) fac.subjects = ",".join(sub_ids) await db.commit() return {"message": "Faculty updated successfully"} @router.delete("/faculty/{email}") async def delete_faculty(email: str, db: AsyncSession = Depends(get_db)): res = await db.execute(select(models.Faculty).where(models.Faculty.email == email)) fac = res.scalars().first() if not fac: raise HTTPException(404, "Faculty not found") await db.execute(models.Faculty.__table__.delete().where(models.Faculty.email == email)) await db.commit() return {"message": "Faculty deleted successfully"}