package handlers import ( "net/http" "time" "github.com/breakpilot/consent-service/internal/models" "github.com/breakpilot/consent-service/internal/services" "github.com/gin-gonic/gin" "github.com/google/uuid" ) // SchoolHandlers contains all school-related HTTP handlers type SchoolHandlers struct { schoolService *services.SchoolService attendanceService *services.AttendanceService gradeService *services.GradeService } // NewSchoolHandlers creates new school handlers func NewSchoolHandlers(schoolService *services.SchoolService, attendanceService *services.AttendanceService, gradeService *services.GradeService) *SchoolHandlers { return &SchoolHandlers{ schoolService: schoolService, attendanceService: attendanceService, gradeService: gradeService, } } // ======================================== // School Handlers // ======================================== // CreateSchool creates a new school // POST /api/v1/schools func (h *SchoolHandlers) CreateSchool(c *gin.Context) { var req models.CreateSchoolRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } school, err := h.schoolService.CreateSchool(c.Request.Context(), req) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusCreated, school) } // GetSchool retrieves a school by ID // GET /api/v1/schools/:id func (h *SchoolHandlers) GetSchool(c *gin.Context) { idStr := c.Param("id") id, err := uuid.Parse(idStr) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid school ID"}) return } school, err := h.schoolService.GetSchool(c.Request.Context(), id) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, school) } // ListSchools lists all schools // GET /api/v1/schools func (h *SchoolHandlers) ListSchools(c *gin.Context) { schools, err := h.schoolService.ListSchools(c.Request.Context()) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, schools) } // ======================================== // School Year Handlers // ======================================== // CreateSchoolYear creates a new school year // POST /api/v1/schools/:id/years func (h *SchoolHandlers) CreateSchoolYear(c *gin.Context) { schoolIDStr := c.Param("id") schoolID, err := uuid.Parse(schoolIDStr) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid school ID"}) return } var req struct { Name string `json:"name" binding:"required"` StartDate string `json:"start_date" binding:"required"` EndDate string `json:"end_date" binding:"required"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } startDate, err := time.Parse("2006-01-02", req.StartDate) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid start date format"}) return } endDate, err := time.Parse("2006-01-02", req.EndDate) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid end date format"}) return } schoolYear, err := h.schoolService.CreateSchoolYear(c.Request.Context(), schoolID, req.Name, startDate, endDate) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusCreated, schoolYear) } // SetCurrentSchoolYear sets a school year as current // PUT /api/v1/schools/:id/years/:yearId/current func (h *SchoolHandlers) SetCurrentSchoolYear(c *gin.Context) { schoolIDStr := c.Param("id") schoolID, err := uuid.Parse(schoolIDStr) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid school ID"}) return } yearIDStr := c.Param("yearId") yearID, err := uuid.Parse(yearIDStr) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid school year ID"}) return } if err := h.schoolService.SetCurrentSchoolYear(c.Request.Context(), schoolID, yearID); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"message": "school year set as current"}) } // ======================================== // Class Handlers // ======================================== // CreateClass creates a new class // POST /api/v1/schools/:id/classes func (h *SchoolHandlers) CreateClass(c *gin.Context) { schoolIDStr := c.Param("id") schoolID, err := uuid.Parse(schoolIDStr) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid school ID"}) return } var req models.CreateClassRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } class, err := h.schoolService.CreateClass(c.Request.Context(), schoolID, req) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusCreated, class) } // GetClass retrieves a class by ID // GET /api/v1/classes/:id func (h *SchoolHandlers) GetClass(c *gin.Context) { idStr := c.Param("id") id, err := uuid.Parse(idStr) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid class ID"}) return } class, err := h.schoolService.GetClass(c.Request.Context(), id) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, class) } // ListClasses lists all classes for a school in a school year // GET /api/v1/schools/:id/classes?school_year_id=... func (h *SchoolHandlers) ListClasses(c *gin.Context) { schoolIDStr := c.Param("id") schoolID, err := uuid.Parse(schoolIDStr) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid school ID"}) return } schoolYearIDStr := c.Query("school_year_id") if schoolYearIDStr == "" { // Get current school year schoolYear, err := h.schoolService.GetCurrentSchoolYear(c.Request.Context(), schoolID) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "no current school year set"}) return } schoolYearIDStr = schoolYear.ID.String() } schoolYearID, err := uuid.Parse(schoolYearIDStr) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid school year ID"}) return } classes, err := h.schoolService.ListClasses(c.Request.Context(), schoolID, schoolYearID) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, classes) } // ======================================== // Student Handlers // ======================================== // CreateStudent creates a new student // POST /api/v1/schools/:id/students func (h *SchoolHandlers) CreateStudent(c *gin.Context) { schoolIDStr := c.Param("id") schoolID, err := uuid.Parse(schoolIDStr) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid school ID"}) return } var req models.CreateStudentRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } student, err := h.schoolService.CreateStudent(c.Request.Context(), schoolID, req) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusCreated, student) } // GetStudent retrieves a student by ID // GET /api/v1/students/:id func (h *SchoolHandlers) GetStudent(c *gin.Context) { idStr := c.Param("id") id, err := uuid.Parse(idStr) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid student ID"}) return } student, err := h.schoolService.GetStudent(c.Request.Context(), id) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, student) } // ListStudentsByClass lists all students in a class // GET /api/v1/classes/:id/students func (h *SchoolHandlers) ListStudentsByClass(c *gin.Context) { classIDStr := c.Param("id") classID, err := uuid.Parse(classIDStr) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid class ID"}) return } students, err := h.schoolService.ListStudentsByClass(c.Request.Context(), classID) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, students) } // ======================================== // Subject Handlers // ======================================== // CreateSubject creates a new subject // POST /api/v1/schools/:id/subjects func (h *SchoolHandlers) CreateSubject(c *gin.Context) { schoolIDStr := c.Param("id") schoolID, err := uuid.Parse(schoolIDStr) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid school ID"}) return } var req struct { Name string `json:"name" binding:"required"` ShortName string `json:"short_name" binding:"required"` Color *string `json:"color"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } subject, err := h.schoolService.CreateSubject(c.Request.Context(), schoolID, req.Name, req.ShortName, req.Color) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusCreated, subject) } // ListSubjects lists all subjects for a school // GET /api/v1/schools/:id/subjects func (h *SchoolHandlers) ListSubjects(c *gin.Context) { schoolIDStr := c.Param("id") schoolID, err := uuid.Parse(schoolIDStr) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid school ID"}) return } subjects, err := h.schoolService.ListSubjects(c.Request.Context(), schoolID) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, subjects) } // ======================================== // Attendance Handlers // ======================================== // RecordAttendance records attendance for a student // POST /api/v1/attendance func (h *SchoolHandlers) RecordAttendance(c *gin.Context) { userID, exists := c.Get("user_id") if !exists { c.JSON(http.StatusUnauthorized, gin.H{"error": "user not authenticated"}) return } var req models.RecordAttendanceRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } record, err := h.attendanceService.RecordAttendance(c.Request.Context(), req, userID.(uuid.UUID)) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusCreated, record) } // RecordBulkAttendance records attendance for multiple students // POST /api/v1/classes/:id/attendance func (h *SchoolHandlers) RecordBulkAttendance(c *gin.Context) { userID, exists := c.Get("user_id") if !exists { c.JSON(http.StatusUnauthorized, gin.H{"error": "user not authenticated"}) return } classIDStr := c.Param("id") classID, err := uuid.Parse(classIDStr) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid class ID"}) return } var req struct { Date string `json:"date" binding:"required"` SlotID string `json:"slot_id" binding:"required"` Records []struct { StudentID string `json:"student_id"` Status string `json:"status"` Note *string `json:"note"` } `json:"records" binding:"required"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } slotID, err := uuid.Parse(req.SlotID) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid slot ID"}) return } // Convert to the expected type (without JSON tags) records := make([]struct { StudentID string Status string Note *string }, len(req.Records)) for i, r := range req.Records { records[i] = struct { StudentID string Status string Note *string }{ StudentID: r.StudentID, Status: r.Status, Note: r.Note, } } err = h.attendanceService.RecordBulkAttendance(c.Request.Context(), classID, req.Date, slotID, records, userID.(uuid.UUID)) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"message": "attendance recorded"}) } // GetClassAttendance gets attendance for a class on a specific date // GET /api/v1/classes/:id/attendance?date=... func (h *SchoolHandlers) GetClassAttendance(c *gin.Context) { classIDStr := c.Param("id") classID, err := uuid.Parse(classIDStr) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid class ID"}) return } date := c.Query("date") if date == "" { date = time.Now().Format("2006-01-02") } overview, err := h.attendanceService.GetAttendanceByClass(c.Request.Context(), classID, date) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, overview) } // GetStudentAttendance gets attendance history for a student // GET /api/v1/students/:id/attendance?start_date=...&end_date=... func (h *SchoolHandlers) GetStudentAttendance(c *gin.Context) { studentIDStr := c.Param("id") studentID, err := uuid.Parse(studentIDStr) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid student ID"}) return } startDateStr := c.Query("start_date") endDateStr := c.Query("end_date") var startDate, endDate time.Time if startDateStr == "" { startDate = time.Now().AddDate(0, -1, 0) // Last month } else { startDate, _ = time.Parse("2006-01-02", startDateStr) } if endDateStr == "" { endDate = time.Now() } else { endDate, _ = time.Parse("2006-01-02", endDateStr) } records, err := h.attendanceService.GetStudentAttendance(c.Request.Context(), studentID, startDate, endDate) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, records) } // ======================================== // Absence Report Handlers // ======================================== // ReportAbsence allows parents to report absence // POST /api/v1/absence/report func (h *SchoolHandlers) ReportAbsence(c *gin.Context) { userID, exists := c.Get("user_id") if !exists { c.JSON(http.StatusUnauthorized, gin.H{"error": "user not authenticated"}) return } var req models.ReportAbsenceRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } report, err := h.attendanceService.ReportAbsence(c.Request.Context(), req, userID.(uuid.UUID)) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusCreated, report) } // ConfirmAbsence allows teachers to confirm absence // PUT /api/v1/absence/:id/confirm func (h *SchoolHandlers) ConfirmAbsence(c *gin.Context) { userID, exists := c.Get("user_id") if !exists { c.JSON(http.StatusUnauthorized, gin.H{"error": "user not authenticated"}) return } reportIDStr := c.Param("id") reportID, err := uuid.Parse(reportIDStr) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid report ID"}) return } var req struct { Status string `json:"status" binding:"required"` // "excused" or "unexcused" } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } err = h.attendanceService.ConfirmAbsence(c.Request.Context(), reportID, userID.(uuid.UUID), req.Status) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"message": "absence confirmed"}) } // GetPendingAbsenceReports gets pending absence reports for a class // GET /api/v1/classes/:id/absence/pending func (h *SchoolHandlers) GetPendingAbsenceReports(c *gin.Context) { classIDStr := c.Param("id") classID, err := uuid.Parse(classIDStr) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid class ID"}) return } reports, err := h.attendanceService.GetPendingAbsenceReports(c.Request.Context(), classID) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, reports) } // ======================================== // Grade Handlers // ======================================== // CreateGrade creates a new grade // POST /api/v1/grades func (h *SchoolHandlers) CreateGrade(c *gin.Context) { userID, exists := c.Get("user_id") if !exists { c.JSON(http.StatusUnauthorized, gin.H{"error": "user not authenticated"}) return } var req models.CreateGradeRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } // Get teacher ID from user ID teacher, err := h.schoolService.GetTeacherByUserID(c.Request.Context(), userID.(uuid.UUID)) if err != nil { c.JSON(http.StatusForbidden, gin.H{"error": "user is not a teacher"}) return } grade, err := h.gradeService.CreateGrade(c.Request.Context(), req, teacher.ID) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusCreated, grade) } // GetStudentGrades gets all grades for a student // GET /api/v1/students/:id/grades?school_year_id=... func (h *SchoolHandlers) GetStudentGrades(c *gin.Context) { studentIDStr := c.Param("id") studentID, err := uuid.Parse(studentIDStr) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid student ID"}) return } schoolYearIDStr := c.Query("school_year_id") if schoolYearIDStr == "" { c.JSON(http.StatusBadRequest, gin.H{"error": "school_year_id is required"}) return } schoolYearID, err := uuid.Parse(schoolYearIDStr) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid school year ID"}) return } grades, err := h.gradeService.GetStudentGrades(c.Request.Context(), studentID, schoolYearID) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, grades) } // GetClassGrades gets grades for all students in a class for a subject (Notenspiegel) // GET /api/v1/classes/:id/grades/:subjectId?school_year_id=...&semester=... func (h *SchoolHandlers) GetClassGrades(c *gin.Context) { classIDStr := c.Param("id") classID, err := uuid.Parse(classIDStr) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid class ID"}) return } subjectIDStr := c.Param("subjectId") subjectID, err := uuid.Parse(subjectIDStr) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid subject ID"}) return } schoolYearIDStr := c.Query("school_year_id") if schoolYearIDStr == "" { c.JSON(http.StatusBadRequest, gin.H{"error": "school_year_id is required"}) return } schoolYearID, err := uuid.Parse(schoolYearIDStr) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid school year ID"}) return } semesterStr := c.DefaultQuery("semester", "1") var semester int if semesterStr == "1" { semester = 1 } else { semester = 2 } overviews, err := h.gradeService.GetClassGradesBySubject(c.Request.Context(), classID, subjectID, schoolYearID, semester) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, overviews) } // GetGradeStatistics gets grade statistics for a class/subject // GET /api/v1/classes/:id/grades/:subjectId/stats?school_year_id=...&semester=... func (h *SchoolHandlers) GetGradeStatistics(c *gin.Context) { classIDStr := c.Param("id") classID, err := uuid.Parse(classIDStr) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid class ID"}) return } subjectIDStr := c.Param("subjectId") subjectID, err := uuid.Parse(subjectIDStr) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid subject ID"}) return } schoolYearIDStr := c.Query("school_year_id") if schoolYearIDStr == "" { c.JSON(http.StatusBadRequest, gin.H{"error": "school_year_id is required"}) return } schoolYearID, err := uuid.Parse(schoolYearIDStr) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid school year ID"}) return } semesterStr := c.DefaultQuery("semester", "1") var semester int if semesterStr == "1" { semester = 1 } else { semester = 2 } stats, err := h.gradeService.GetSubjectGradeStatistics(c.Request.Context(), classID, subjectID, schoolYearID, semester) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, stats) } // ======================================== // Parent Onboarding Handlers // ======================================== // GenerateOnboardingToken generates a QR code token for parent onboarding // POST /api/v1/onboarding/tokens func (h *SchoolHandlers) GenerateOnboardingToken(c *gin.Context) { userID, exists := c.Get("user_id") if !exists { c.JSON(http.StatusUnauthorized, gin.H{"error": "user not authenticated"}) return } var req struct { SchoolID string `json:"school_id" binding:"required"` ClassID string `json:"class_id" binding:"required"` StudentID string `json:"student_id" binding:"required"` Role string `json:"role"` // "parent" or "parent_representative" } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } schoolID, err := uuid.Parse(req.SchoolID) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid school ID"}) return } classID, err := uuid.Parse(req.ClassID) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid class ID"}) return } studentID, err := uuid.Parse(req.StudentID) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid student ID"}) return } role := req.Role if role == "" { role = "parent" } token, err := h.schoolService.GenerateParentOnboardingToken(c.Request.Context(), schoolID, classID, studentID, userID.(uuid.UUID), role) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } // Generate QR code URL qrURL := "/onboard-parent?token=" + token.Token c.JSON(http.StatusCreated, gin.H{ "token": token.Token, "qr_url": qrURL, "expires_at": token.ExpiresAt, }) } // ValidateOnboardingToken validates an onboarding token // GET /api/v1/onboarding/validate?token=... func (h *SchoolHandlers) ValidateOnboardingToken(c *gin.Context) { token := c.Query("token") if token == "" { c.JSON(http.StatusBadRequest, gin.H{"error": "token is required"}) return } onboardingToken, err := h.schoolService.ValidateOnboardingToken(c.Request.Context(), token) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "invalid or expired token"}) return } // Get student and school info student, err := h.schoolService.GetStudent(c.Request.Context(), onboardingToken.StudentID) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } class, err := h.schoolService.GetClass(c.Request.Context(), onboardingToken.ClassID) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } school, err := h.schoolService.GetSchool(c.Request.Context(), onboardingToken.SchoolID) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{ "valid": true, "role": onboardingToken.Role, "student_name": student.FirstName + " " + student.LastName, "class_name": class.Name, "school_name": school.Name, "expires_at": onboardingToken.ExpiresAt, }) } // RedeemOnboardingToken redeems a token and creates parent account // POST /api/v1/onboarding/redeem func (h *SchoolHandlers) RedeemOnboardingToken(c *gin.Context) { userID, exists := c.Get("user_id") if !exists { c.JSON(http.StatusUnauthorized, gin.H{"error": "user not authenticated"}) return } var req struct { Token string `json:"token" binding:"required"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } err := h.schoolService.RedeemOnboardingToken(c.Request.Context(), req.Token, userID.(uuid.UUID)) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"message": "token redeemed successfully"}) } // ======================================== // Register Routes // ======================================== // RegisterRoutes registers all school-related routes func (h *SchoolHandlers) RegisterRoutes(r *gin.RouterGroup, authMiddleware gin.HandlerFunc) { // Public routes (for onboarding) r.GET("/onboarding/validate", h.ValidateOnboardingToken) // Protected routes protected := r.Group("") protected.Use(authMiddleware) // Schools protected.POST("/schools", h.CreateSchool) protected.GET("/schools", h.ListSchools) protected.GET("/schools/:id", h.GetSchool) protected.POST("/schools/:id/years", h.CreateSchoolYear) protected.PUT("/schools/:id/years/:yearId/current", h.SetCurrentSchoolYear) protected.POST("/schools/:id/classes", h.CreateClass) protected.GET("/schools/:id/classes", h.ListClasses) protected.POST("/schools/:id/students", h.CreateStudent) protected.POST("/schools/:id/subjects", h.CreateSubject) protected.GET("/schools/:id/subjects", h.ListSubjects) // Classes protected.GET("/classes/:id", h.GetClass) protected.GET("/classes/:id/students", h.ListStudentsByClass) protected.GET("/classes/:id/attendance", h.GetClassAttendance) protected.POST("/classes/:id/attendance", h.RecordBulkAttendance) protected.GET("/classes/:id/absence/pending", h.GetPendingAbsenceReports) protected.GET("/classes/:id/grades/:subjectId", h.GetClassGrades) protected.GET("/classes/:id/grades/:subjectId/stats", h.GetGradeStatistics) // Students protected.GET("/students/:id", h.GetStudent) protected.GET("/students/:id/attendance", h.GetStudentAttendance) protected.GET("/students/:id/grades", h.GetStudentGrades) // Attendance & Absence protected.POST("/attendance", h.RecordAttendance) protected.POST("/absence/report", h.ReportAbsence) protected.PUT("/absence/:id/confirm", h.ConfirmAbsence) // Grades protected.POST("/grades", h.CreateGrade) // Onboarding protected.POST("/onboarding/tokens", h.GenerateOnboardingToken) protected.POST("/onboarding/redeem", h.RedeemOnboardingToken) }