Initial commit: breakpilot-lehrer - Lehrer KI Platform
Services: Admin-Lehrer, Backend-Lehrer, Studio v2, Website, Klausur-Service, School-Service, Voice-Service, Geo-Service, BreakPilot Drive, Agent-Core Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
199
school-service/internal/handlers/certificate_handlers.go
Normal file
199
school-service/internal/handlers/certificate_handlers.go
Normal file
@@ -0,0 +1,199 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/breakpilot/school-service/internal/models"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// GetCertificateTemplates returns available certificate templates
|
||||
func (h *Handler) GetCertificateTemplates(c *gin.Context) {
|
||||
templates := h.certificateService.GetAvailableTemplates()
|
||||
respondSuccess(c, templates)
|
||||
}
|
||||
|
||||
// GenerateCertificate generates a certificate for a student
|
||||
func (h *Handler) GenerateCertificate(c *gin.Context) {
|
||||
var req models.GenerateCertificateRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
respondError(c, http.StatusBadRequest, "Invalid request: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
certificate, err := h.certificateService.GenerateCertificate(c.Request.Context(), &req)
|
||||
if err != nil {
|
||||
respondError(c, http.StatusInternalServerError, "Failed to generate certificate: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
respondCreated(c, certificate)
|
||||
}
|
||||
|
||||
// GetClassCertificates returns certificates for a class
|
||||
func (h *Handler) GetClassCertificates(c *gin.Context) {
|
||||
classID := c.Param("classId")
|
||||
semesterStr := c.DefaultQuery("semester", "1")
|
||||
|
||||
semester, err := strconv.Atoi(semesterStr)
|
||||
if err != nil || semester < 1 || semester > 2 {
|
||||
semester = 1
|
||||
}
|
||||
|
||||
certificates, err := h.certificateService.GetCertificates(c.Request.Context(), classID, semester)
|
||||
if err != nil {
|
||||
respondError(c, http.StatusInternalServerError, "Failed to get certificates: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
respondSuccess(c, certificates)
|
||||
}
|
||||
|
||||
// GetCertificate returns a single certificate
|
||||
func (h *Handler) GetCertificate(c *gin.Context) {
|
||||
certificateID := c.Param("id")
|
||||
|
||||
certificate, err := h.certificateService.GetCertificate(c.Request.Context(), certificateID)
|
||||
if err != nil {
|
||||
respondError(c, http.StatusNotFound, "Certificate not found")
|
||||
return
|
||||
}
|
||||
|
||||
respondSuccess(c, certificate)
|
||||
}
|
||||
|
||||
// UpdateCertificate updates a certificate's remarks
|
||||
func (h *Handler) UpdateCertificate(c *gin.Context) {
|
||||
certificateID := c.Param("id")
|
||||
|
||||
var req struct {
|
||||
Remarks string `json:"remarks"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
respondError(c, http.StatusBadRequest, "Invalid request: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
certificate, err := h.certificateService.UpdateCertificate(c.Request.Context(), certificateID, req.Remarks)
|
||||
if err != nil {
|
||||
respondError(c, http.StatusInternalServerError, "Failed to update certificate: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
respondSuccess(c, certificate)
|
||||
}
|
||||
|
||||
// FinalizeCertificate finalizes a certificate
|
||||
func (h *Handler) FinalizeCertificate(c *gin.Context) {
|
||||
certificateID := c.Param("id")
|
||||
|
||||
if err := h.certificateService.FinalizeCertificate(c.Request.Context(), certificateID); err != nil {
|
||||
respondError(c, http.StatusInternalServerError, "Failed to finalize certificate: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Certificate finalized"})
|
||||
}
|
||||
|
||||
// GetCertificatePDF returns the PDF for a certificate
|
||||
func (h *Handler) GetCertificatePDF(c *gin.Context) {
|
||||
certificateID := c.Param("id")
|
||||
|
||||
pdf, err := h.certificateService.GeneratePDF(c.Request.Context(), certificateID)
|
||||
if err != nil {
|
||||
respondError(c, http.StatusInternalServerError, "Failed to generate PDF: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Header("Content-Type", "application/pdf")
|
||||
c.Header("Content-Disposition", "attachment; filename=zeugnis.pdf")
|
||||
c.Data(http.StatusOK, "application/pdf", pdf)
|
||||
}
|
||||
|
||||
// DeleteCertificate deletes a certificate
|
||||
func (h *Handler) DeleteCertificate(c *gin.Context) {
|
||||
certificateID := c.Param("id")
|
||||
|
||||
if err := h.certificateService.DeleteCertificate(c.Request.Context(), certificateID); err != nil {
|
||||
respondError(c, http.StatusInternalServerError, "Failed to delete certificate: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Certificate deleted"})
|
||||
}
|
||||
|
||||
// BulkGenerateCertificates generates certificates for all students in a class
|
||||
func (h *Handler) BulkGenerateCertificates(c *gin.Context) {
|
||||
var req struct {
|
||||
ClassID string `json:"class_id" binding:"required"`
|
||||
SchoolYearID string `json:"school_year_id" binding:"required"`
|
||||
Semester int `json:"semester" binding:"required,min=1,max=2"`
|
||||
CertificateType models.CertificateType `json:"certificate_type" binding:"required"`
|
||||
TemplateName string `json:"template_name"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
respondError(c, http.StatusBadRequest, "Invalid request: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
certificates, err := h.certificateService.BulkGenerateCertificates(c.Request.Context(), req.ClassID, req.SchoolYearID, req.Semester, req.CertificateType, req.TemplateName)
|
||||
if err != nil {
|
||||
respondError(c, http.StatusInternalServerError, "Failed to generate certificates: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
respondSuccess(c, gin.H{
|
||||
"message": "Certificates generated",
|
||||
"count": len(certificates),
|
||||
"certificates": certificates,
|
||||
})
|
||||
}
|
||||
|
||||
// GenerateGradeFeedback generates AI feedback for a student
|
||||
func (h *Handler) GenerateGradeFeedback(c *gin.Context) {
|
||||
studentID := c.Param("studentId")
|
||||
semesterStr := c.DefaultQuery("semester", "1")
|
||||
|
||||
semester, err := strconv.Atoi(semesterStr)
|
||||
if err != nil || semester < 1 || semester > 2 {
|
||||
semester = 1
|
||||
}
|
||||
|
||||
// Get student grades
|
||||
grades, err := h.gradeService.GetStudentGrades(c.Request.Context(), studentID)
|
||||
if err != nil {
|
||||
respondError(c, http.StatusInternalServerError, "Failed to get student grades: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// Build grades map for the requested semester
|
||||
gradeMap := make(map[string]float64)
|
||||
var studentName string
|
||||
for _, g := range grades {
|
||||
if g.Semester == semester && g.FinalGrade != nil {
|
||||
gradeMap[g.SubjectName] = *g.FinalGrade
|
||||
studentName = g.StudentName
|
||||
}
|
||||
}
|
||||
|
||||
if len(gradeMap) == 0 {
|
||||
respondError(c, http.StatusNotFound, "No grades found for this semester")
|
||||
return
|
||||
}
|
||||
|
||||
// Generate feedback using AI
|
||||
feedback, err := h.aiService.GenerateGradeFeedback(c.Request.Context(), studentName, gradeMap, semester)
|
||||
if err != nil {
|
||||
respondError(c, http.StatusInternalServerError, "Failed to generate feedback: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
respondSuccess(c, gin.H{
|
||||
"student_id": studentID,
|
||||
"student_name": studentName,
|
||||
"semester": semester,
|
||||
"feedback": feedback,
|
||||
})
|
||||
}
|
||||
242
school-service/internal/handlers/class_handlers.go
Normal file
242
school-service/internal/handlers/class_handlers.go
Normal file
@@ -0,0 +1,242 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/breakpilot/school-service/internal/models"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// School Year Handlers
|
||||
|
||||
// CreateSchoolYear creates a new school year
|
||||
func (h *Handler) CreateSchoolYear(c *gin.Context) {
|
||||
teacherID := getUserID(c)
|
||||
if teacherID == "" {
|
||||
respondError(c, http.StatusUnauthorized, "User not authenticated")
|
||||
return
|
||||
}
|
||||
|
||||
var req models.CreateSchoolYearRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
respondError(c, http.StatusBadRequest, "Invalid request: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
year, err := h.classService.CreateSchoolYear(c.Request.Context(), teacherID, &req)
|
||||
if err != nil {
|
||||
respondError(c, http.StatusInternalServerError, "Failed to create school year: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
respondCreated(c, year)
|
||||
}
|
||||
|
||||
// GetSchoolYears returns all school years for the current teacher
|
||||
func (h *Handler) GetSchoolYears(c *gin.Context) {
|
||||
teacherID := getUserID(c)
|
||||
if teacherID == "" {
|
||||
respondError(c, http.StatusUnauthorized, "User not authenticated")
|
||||
return
|
||||
}
|
||||
|
||||
years, err := h.classService.GetSchoolYears(c.Request.Context(), teacherID)
|
||||
if err != nil {
|
||||
respondError(c, http.StatusInternalServerError, "Failed to get school years: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
respondSuccess(c, years)
|
||||
}
|
||||
|
||||
// Class Handlers
|
||||
|
||||
// CreateClass creates a new class
|
||||
func (h *Handler) CreateClass(c *gin.Context) {
|
||||
teacherID := getUserID(c)
|
||||
if teacherID == "" {
|
||||
respondError(c, http.StatusUnauthorized, "User not authenticated")
|
||||
return
|
||||
}
|
||||
|
||||
var req models.CreateClassRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
respondError(c, http.StatusBadRequest, "Invalid request: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
class, err := h.classService.CreateClass(c.Request.Context(), teacherID, &req)
|
||||
if err != nil {
|
||||
respondError(c, http.StatusInternalServerError, "Failed to create class: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
respondCreated(c, class)
|
||||
}
|
||||
|
||||
// GetClasses returns all classes for the current teacher
|
||||
func (h *Handler) GetClasses(c *gin.Context) {
|
||||
teacherID := getUserID(c)
|
||||
if teacherID == "" {
|
||||
respondError(c, http.StatusUnauthorized, "User not authenticated")
|
||||
return
|
||||
}
|
||||
|
||||
classes, err := h.classService.GetClasses(c.Request.Context(), teacherID)
|
||||
if err != nil {
|
||||
respondError(c, http.StatusInternalServerError, "Failed to get classes: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
respondSuccess(c, classes)
|
||||
}
|
||||
|
||||
// GetClass returns a single class
|
||||
func (h *Handler) GetClass(c *gin.Context) {
|
||||
teacherID := getUserID(c)
|
||||
classID := c.Param("id")
|
||||
|
||||
if teacherID == "" {
|
||||
respondError(c, http.StatusUnauthorized, "User not authenticated")
|
||||
return
|
||||
}
|
||||
|
||||
class, err := h.classService.GetClass(c.Request.Context(), classID, teacherID)
|
||||
if err != nil {
|
||||
respondError(c, http.StatusNotFound, "Class not found")
|
||||
return
|
||||
}
|
||||
|
||||
respondSuccess(c, class)
|
||||
}
|
||||
|
||||
// DeleteClass deletes a class
|
||||
func (h *Handler) DeleteClass(c *gin.Context) {
|
||||
teacherID := getUserID(c)
|
||||
classID := c.Param("id")
|
||||
|
||||
if teacherID == "" {
|
||||
respondError(c, http.StatusUnauthorized, "User not authenticated")
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.classService.DeleteClass(c.Request.Context(), classID, teacherID); err != nil {
|
||||
respondError(c, http.StatusInternalServerError, "Failed to delete class: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Class deleted"})
|
||||
}
|
||||
|
||||
// Student Handlers
|
||||
|
||||
// CreateStudent creates a new student in a class
|
||||
func (h *Handler) CreateStudent(c *gin.Context) {
|
||||
classID := c.Param("id")
|
||||
|
||||
var req models.CreateStudentRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
respondError(c, http.StatusBadRequest, "Invalid request: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
student, err := h.classService.CreateStudent(c.Request.Context(), classID, &req)
|
||||
if err != nil {
|
||||
respondError(c, http.StatusInternalServerError, "Failed to create student: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
respondCreated(c, student)
|
||||
}
|
||||
|
||||
// GetStudents returns all students in a class
|
||||
func (h *Handler) GetStudents(c *gin.Context) {
|
||||
classID := c.Param("id")
|
||||
|
||||
students, err := h.classService.GetStudents(c.Request.Context(), classID)
|
||||
if err != nil {
|
||||
respondError(c, http.StatusInternalServerError, "Failed to get students: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
respondSuccess(c, students)
|
||||
}
|
||||
|
||||
// DeleteStudent deletes a student
|
||||
func (h *Handler) DeleteStudent(c *gin.Context) {
|
||||
studentID := c.Param("studentId")
|
||||
|
||||
if err := h.classService.DeleteStudent(c.Request.Context(), studentID); err != nil {
|
||||
respondError(c, http.StatusInternalServerError, "Failed to delete student: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Student deleted"})
|
||||
}
|
||||
|
||||
// ImportStudents imports students from CSV (placeholder)
|
||||
func (h *Handler) ImportStudents(c *gin.Context) {
|
||||
// TODO: Implement CSV import
|
||||
// For now, return a not implemented response
|
||||
respondError(c, http.StatusNotImplemented, "CSV import not yet implemented")
|
||||
}
|
||||
|
||||
// Subject Handlers
|
||||
|
||||
// CreateSubject creates a new subject
|
||||
func (h *Handler) CreateSubject(c *gin.Context) {
|
||||
teacherID := getUserID(c)
|
||||
if teacherID == "" {
|
||||
respondError(c, http.StatusUnauthorized, "User not authenticated")
|
||||
return
|
||||
}
|
||||
|
||||
var req models.CreateSubjectRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
respondError(c, http.StatusBadRequest, "Invalid request: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
subject, err := h.classService.CreateSubject(c.Request.Context(), teacherID, &req)
|
||||
if err != nil {
|
||||
respondError(c, http.StatusInternalServerError, "Failed to create subject: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
respondCreated(c, subject)
|
||||
}
|
||||
|
||||
// GetSubjects returns all subjects for the current teacher
|
||||
func (h *Handler) GetSubjects(c *gin.Context) {
|
||||
teacherID := getUserID(c)
|
||||
if teacherID == "" {
|
||||
respondError(c, http.StatusUnauthorized, "User not authenticated")
|
||||
return
|
||||
}
|
||||
|
||||
subjects, err := h.classService.GetSubjects(c.Request.Context(), teacherID)
|
||||
if err != nil {
|
||||
respondError(c, http.StatusInternalServerError, "Failed to get subjects: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
respondSuccess(c, subjects)
|
||||
}
|
||||
|
||||
// DeleteSubject deletes a subject
|
||||
func (h *Handler) DeleteSubject(c *gin.Context) {
|
||||
teacherID := getUserID(c)
|
||||
subjectID := c.Param("id")
|
||||
|
||||
if teacherID == "" {
|
||||
respondError(c, http.StatusUnauthorized, "User not authenticated")
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.classService.DeleteSubject(c.Request.Context(), subjectID, teacherID); err != nil {
|
||||
respondError(c, http.StatusInternalServerError, "Failed to delete subject: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Subject deleted"})
|
||||
}
|
||||
208
school-service/internal/handlers/exam_handlers.go
Normal file
208
school-service/internal/handlers/exam_handlers.go
Normal file
@@ -0,0 +1,208 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/breakpilot/school-service/internal/models"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// CreateExam creates a new exam
|
||||
func (h *Handler) CreateExam(c *gin.Context) {
|
||||
teacherID := getUserID(c)
|
||||
if teacherID == "" {
|
||||
respondError(c, http.StatusUnauthorized, "User not authenticated")
|
||||
return
|
||||
}
|
||||
|
||||
var req models.CreateExamRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
respondError(c, http.StatusBadRequest, "Invalid request: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
exam, err := h.examService.CreateExam(c.Request.Context(), teacherID, &req)
|
||||
if err != nil {
|
||||
respondError(c, http.StatusInternalServerError, "Failed to create exam: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
respondCreated(c, exam)
|
||||
}
|
||||
|
||||
// GetExams returns all exams for the current teacher
|
||||
func (h *Handler) GetExams(c *gin.Context) {
|
||||
teacherID := getUserID(c)
|
||||
if teacherID == "" {
|
||||
respondError(c, http.StatusUnauthorized, "User not authenticated")
|
||||
return
|
||||
}
|
||||
|
||||
exams, err := h.examService.GetExams(c.Request.Context(), teacherID)
|
||||
if err != nil {
|
||||
respondError(c, http.StatusInternalServerError, "Failed to get exams: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
respondSuccess(c, exams)
|
||||
}
|
||||
|
||||
// GetExam returns a single exam
|
||||
func (h *Handler) GetExam(c *gin.Context) {
|
||||
teacherID := getUserID(c)
|
||||
examID := c.Param("id")
|
||||
|
||||
if teacherID == "" {
|
||||
respondError(c, http.StatusUnauthorized, "User not authenticated")
|
||||
return
|
||||
}
|
||||
|
||||
exam, err := h.examService.GetExam(c.Request.Context(), examID, teacherID)
|
||||
if err != nil {
|
||||
respondError(c, http.StatusNotFound, "Exam not found")
|
||||
return
|
||||
}
|
||||
|
||||
respondSuccess(c, exam)
|
||||
}
|
||||
|
||||
// UpdateExam updates an exam
|
||||
func (h *Handler) UpdateExam(c *gin.Context) {
|
||||
teacherID := getUserID(c)
|
||||
examID := c.Param("id")
|
||||
|
||||
if teacherID == "" {
|
||||
respondError(c, http.StatusUnauthorized, "User not authenticated")
|
||||
return
|
||||
}
|
||||
|
||||
var req models.CreateExamRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
respondError(c, http.StatusBadRequest, "Invalid request: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
exam, err := h.examService.UpdateExam(c.Request.Context(), examID, teacherID, &req)
|
||||
if err != nil {
|
||||
respondError(c, http.StatusInternalServerError, "Failed to update exam: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
respondSuccess(c, exam)
|
||||
}
|
||||
|
||||
// DeleteExam deletes an exam
|
||||
func (h *Handler) DeleteExam(c *gin.Context) {
|
||||
teacherID := getUserID(c)
|
||||
examID := c.Param("id")
|
||||
|
||||
if teacherID == "" {
|
||||
respondError(c, http.StatusUnauthorized, "User not authenticated")
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.examService.DeleteExam(c.Request.Context(), examID, teacherID); err != nil {
|
||||
respondError(c, http.StatusInternalServerError, "Failed to delete exam: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Exam deleted"})
|
||||
}
|
||||
|
||||
// GenerateExamVariant generates a variant of an exam using AI
|
||||
func (h *Handler) GenerateExamVariant(c *gin.Context) {
|
||||
teacherID := getUserID(c)
|
||||
examID := c.Param("id")
|
||||
|
||||
if teacherID == "" {
|
||||
respondError(c, http.StatusUnauthorized, "User not authenticated")
|
||||
return
|
||||
}
|
||||
|
||||
var req models.GenerateExamVariantRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
respondError(c, http.StatusBadRequest, "Invalid request: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// Get the original exam
|
||||
originalExam, err := h.examService.GetExam(c.Request.Context(), examID, teacherID)
|
||||
if err != nil {
|
||||
respondError(c, http.StatusNotFound, "Original exam not found")
|
||||
return
|
||||
}
|
||||
|
||||
// Generate variant content using AI
|
||||
newContent, err := h.aiService.GenerateExamVariant(c.Request.Context(), originalExam.Content, req.VariationType)
|
||||
if err != nil {
|
||||
respondError(c, http.StatusInternalServerError, "Failed to generate variant: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// Create the variant exam
|
||||
variant, err := h.examService.CreateExamVariant(c.Request.Context(), examID, teacherID, newContent, req.VariationType)
|
||||
if err != nil {
|
||||
respondError(c, http.StatusInternalServerError, "Failed to save variant: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
respondCreated(c, variant)
|
||||
}
|
||||
|
||||
// SaveExamResults saves results for students
|
||||
func (h *Handler) SaveExamResults(c *gin.Context) {
|
||||
examID := c.Param("id")
|
||||
|
||||
var req models.UpdateExamResultRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
respondError(c, http.StatusBadRequest, "Invalid request: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
result, err := h.examService.SaveExamResult(c.Request.Context(), examID, &req)
|
||||
if err != nil {
|
||||
respondError(c, http.StatusInternalServerError, "Failed to save result: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
respondSuccess(c, result)
|
||||
}
|
||||
|
||||
// GetExamResults returns all results for an exam
|
||||
func (h *Handler) GetExamResults(c *gin.Context) {
|
||||
examID := c.Param("id")
|
||||
|
||||
results, err := h.examService.GetExamResults(c.Request.Context(), examID)
|
||||
if err != nil {
|
||||
respondError(c, http.StatusInternalServerError, "Failed to get results: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
respondSuccess(c, results)
|
||||
}
|
||||
|
||||
// ApproveExamResult approves a result for grade transfer
|
||||
func (h *Handler) ApproveExamResult(c *gin.Context) {
|
||||
examID := c.Param("id")
|
||||
studentID := c.Param("studentId")
|
||||
|
||||
if err := h.examService.ApproveExamResult(c.Request.Context(), examID, studentID); err != nil {
|
||||
respondError(c, http.StatusInternalServerError, "Failed to approve result: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Result approved"})
|
||||
}
|
||||
|
||||
// GetStudentsNeedingRewrite returns students who need to rewrite
|
||||
func (h *Handler) GetStudentsNeedingRewrite(c *gin.Context) {
|
||||
examID := c.Param("id")
|
||||
|
||||
students, err := h.examService.GetStudentsNeedingRewrite(c.Request.Context(), examID)
|
||||
if err != nil {
|
||||
respondError(c, http.StatusInternalServerError, "Failed to get students: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
respondSuccess(c, students)
|
||||
}
|
||||
216
school-service/internal/handlers/grade_handlers.go
Normal file
216
school-service/internal/handlers/grade_handlers.go
Normal file
@@ -0,0 +1,216 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/breakpilot/school-service/internal/models"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// GetClassGrades returns grades for a class
|
||||
func (h *Handler) GetClassGrades(c *gin.Context) {
|
||||
classID := c.Param("classId")
|
||||
semesterStr := c.DefaultQuery("semester", "1")
|
||||
|
||||
semester, err := strconv.Atoi(semesterStr)
|
||||
if err != nil || semester < 1 || semester > 2 {
|
||||
semester = 1
|
||||
}
|
||||
|
||||
grades, err := h.gradeService.GetGradeOverview(c.Request.Context(), classID, semester)
|
||||
if err != nil {
|
||||
respondError(c, http.StatusInternalServerError, "Failed to get grades: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
respondSuccess(c, grades)
|
||||
}
|
||||
|
||||
// GetStudentGrades returns all grades for a student
|
||||
func (h *Handler) GetStudentGrades(c *gin.Context) {
|
||||
studentID := c.Param("studentId")
|
||||
|
||||
grades, err := h.gradeService.GetStudentGrades(c.Request.Context(), studentID)
|
||||
if err != nil {
|
||||
respondError(c, http.StatusInternalServerError, "Failed to get grades: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
respondSuccess(c, grades)
|
||||
}
|
||||
|
||||
// UpdateOralGrade updates the oral grade for a student
|
||||
func (h *Handler) UpdateOralGrade(c *gin.Context) {
|
||||
studentID := c.Param("studentId")
|
||||
subjectID := c.Param("subjectId")
|
||||
|
||||
var req models.UpdateOralGradeRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
respondError(c, http.StatusBadRequest, "Invalid request: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
grade, err := h.gradeService.UpdateOralGrade(c.Request.Context(), studentID, subjectID, &req)
|
||||
if err != nil {
|
||||
respondError(c, http.StatusInternalServerError, "Failed to update oral grade: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
respondSuccess(c, grade)
|
||||
}
|
||||
|
||||
// CalculateFinalGrades calculates final grades for a class
|
||||
func (h *Handler) CalculateFinalGrades(c *gin.Context) {
|
||||
var req struct {
|
||||
ClassID string `json:"class_id" binding:"required"`
|
||||
Semester int `json:"semester" binding:"required,min=1,max=2"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
respondError(c, http.StatusBadRequest, "Invalid request: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.gradeService.CalculateFinalGrades(c.Request.Context(), req.ClassID, req.Semester); err != nil {
|
||||
respondError(c, http.StatusInternalServerError, "Failed to calculate grades: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Grades calculated successfully"})
|
||||
}
|
||||
|
||||
// TransferApprovedGrades transfers approved exam results to grade overview
|
||||
func (h *Handler) TransferApprovedGrades(c *gin.Context) {
|
||||
teacherID := getUserID(c)
|
||||
if teacherID == "" {
|
||||
respondError(c, http.StatusUnauthorized, "User not authenticated")
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.gradeService.TransferApprovedGrades(c.Request.Context(), teacherID); err != nil {
|
||||
respondError(c, http.StatusInternalServerError, "Failed to transfer grades: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Grades transferred successfully"})
|
||||
}
|
||||
|
||||
// LockFinalGrade locks a final grade
|
||||
func (h *Handler) LockFinalGrade(c *gin.Context) {
|
||||
studentID := c.Param("studentId")
|
||||
subjectID := c.Param("subjectId")
|
||||
semesterStr := c.DefaultQuery("semester", "1")
|
||||
|
||||
semester, err := strconv.Atoi(semesterStr)
|
||||
if err != nil || semester < 1 || semester > 2 {
|
||||
semester = 1
|
||||
}
|
||||
|
||||
if err := h.gradeService.LockFinalGrade(c.Request.Context(), studentID, subjectID, semester); err != nil {
|
||||
respondError(c, http.StatusInternalServerError, "Failed to lock grade: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Grade locked"})
|
||||
}
|
||||
|
||||
// UpdateGradeWeights updates the grade weights
|
||||
func (h *Handler) UpdateGradeWeights(c *gin.Context) {
|
||||
studentID := c.Param("studentId")
|
||||
subjectID := c.Param("subjectId")
|
||||
|
||||
var req struct {
|
||||
WrittenWeight int `json:"written_weight" binding:"required,min=0,max=100"`
|
||||
OralWeight int `json:"oral_weight" binding:"required,min=0,max=100"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
respondError(c, http.StatusBadRequest, "Invalid request: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if req.WrittenWeight+req.OralWeight != 100 {
|
||||
respondError(c, http.StatusBadRequest, "Weights must sum to 100")
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.gradeService.UpdateGradeWeights(c.Request.Context(), studentID, subjectID, req.WrittenWeight, req.OralWeight); err != nil {
|
||||
respondError(c, http.StatusInternalServerError, "Failed to update weights: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Weights updated"})
|
||||
}
|
||||
|
||||
// =============================================
|
||||
// STATISTICS ENDPOINTS
|
||||
// =============================================
|
||||
|
||||
// GetClassStatistics returns grade statistics for a class
|
||||
// GET /api/v1/school/statistics/:classId
|
||||
func (h *Handler) GetClassStatistics(c *gin.Context) {
|
||||
classID := c.Param("classId")
|
||||
semesterStr := c.DefaultQuery("semester", "0") // 0 = both semesters
|
||||
|
||||
semester, _ := strconv.Atoi(semesterStr)
|
||||
|
||||
stats, err := h.gradeService.GetClassStatistics(c.Request.Context(), classID, semester)
|
||||
if err != nil {
|
||||
respondError(c, http.StatusInternalServerError, "Failed to get statistics: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
respondSuccess(c, stats)
|
||||
}
|
||||
|
||||
// GetSubjectStatistics returns grade statistics for a specific subject in a class
|
||||
// GET /api/v1/school/statistics/:classId/subject/:subjectId
|
||||
func (h *Handler) GetSubjectStatistics(c *gin.Context) {
|
||||
classID := c.Param("classId")
|
||||
subjectID := c.Param("subjectId")
|
||||
semesterStr := c.DefaultQuery("semester", "0")
|
||||
|
||||
semester, _ := strconv.Atoi(semesterStr)
|
||||
|
||||
stats, err := h.gradeService.GetSubjectStatistics(c.Request.Context(), classID, subjectID, semester)
|
||||
if err != nil {
|
||||
respondError(c, http.StatusInternalServerError, "Failed to get statistics: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
respondSuccess(c, stats)
|
||||
}
|
||||
|
||||
// GetStudentStatistics returns grade statistics for a specific student
|
||||
// GET /api/v1/school/statistics/student/:studentId
|
||||
func (h *Handler) GetStudentStatistics(c *gin.Context) {
|
||||
studentID := c.Param("studentId")
|
||||
|
||||
stats, err := h.gradeService.GetStudentStatistics(c.Request.Context(), studentID)
|
||||
if err != nil {
|
||||
respondError(c, http.StatusInternalServerError, "Failed to get statistics: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
respondSuccess(c, stats)
|
||||
}
|
||||
|
||||
// GetNotenspiegel returns the grade distribution for a class or exam
|
||||
// GET /api/v1/school/statistics/:classId/notenspiegel
|
||||
func (h *Handler) GetNotenspiegel(c *gin.Context) {
|
||||
classID := c.Param("classId")
|
||||
subjectID := c.Query("subject_id")
|
||||
examID := c.Query("exam_id")
|
||||
semesterStr := c.DefaultQuery("semester", "0")
|
||||
|
||||
semester, _ := strconv.Atoi(semesterStr)
|
||||
|
||||
notenspiegel, err := h.gradeService.GetNotenspiegel(c.Request.Context(), classID, subjectID, examID, semester)
|
||||
if err != nil {
|
||||
respondError(c, http.StatusInternalServerError, "Failed to get Notenspiegel: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
respondSuccess(c, notenspiegel)
|
||||
}
|
||||
209
school-service/internal/handlers/gradebook_handlers.go
Normal file
209
school-service/internal/handlers/gradebook_handlers.go
Normal file
@@ -0,0 +1,209 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/breakpilot/school-service/internal/models"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// Attendance Handlers
|
||||
|
||||
// CreateAttendance creates an attendance record
|
||||
func (h *Handler) CreateAttendance(c *gin.Context) {
|
||||
var req models.CreateAttendanceRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
respondError(c, http.StatusBadRequest, "Invalid request: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
attendance, err := h.gradebookService.CreateAttendance(c.Request.Context(), &req)
|
||||
if err != nil {
|
||||
respondError(c, http.StatusInternalServerError, "Failed to create attendance: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
respondCreated(c, attendance)
|
||||
}
|
||||
|
||||
// GetClassAttendance returns attendance for a class
|
||||
func (h *Handler) GetClassAttendance(c *gin.Context) {
|
||||
classID := c.Param("classId")
|
||||
startDateStr := c.Query("start_date")
|
||||
endDateStr := c.Query("end_date")
|
||||
|
||||
var startDate, endDate *time.Time
|
||||
if startDateStr != "" {
|
||||
t, err := time.Parse("2006-01-02", startDateStr)
|
||||
if err == nil {
|
||||
startDate = &t
|
||||
}
|
||||
}
|
||||
if endDateStr != "" {
|
||||
t, err := time.Parse("2006-01-02", endDateStr)
|
||||
if err == nil {
|
||||
endDate = &t
|
||||
}
|
||||
}
|
||||
|
||||
attendance, err := h.gradebookService.GetClassAttendance(c.Request.Context(), classID, startDate, endDate)
|
||||
if err != nil {
|
||||
respondError(c, http.StatusInternalServerError, "Failed to get attendance: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
respondSuccess(c, attendance)
|
||||
}
|
||||
|
||||
// GetStudentAttendance returns attendance for a student
|
||||
func (h *Handler) GetStudentAttendance(c *gin.Context) {
|
||||
studentID := c.Param("studentId")
|
||||
|
||||
attendance, err := h.gradebookService.GetStudentAttendance(c.Request.Context(), studentID)
|
||||
if err != nil {
|
||||
respondError(c, http.StatusInternalServerError, "Failed to get attendance: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
respondSuccess(c, attendance)
|
||||
}
|
||||
|
||||
// DeleteAttendance deletes an attendance record
|
||||
func (h *Handler) DeleteAttendance(c *gin.Context) {
|
||||
attendanceID := c.Param("id")
|
||||
|
||||
if err := h.gradebookService.DeleteAttendance(c.Request.Context(), attendanceID); err != nil {
|
||||
respondError(c, http.StatusInternalServerError, "Failed to delete attendance: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Attendance deleted"})
|
||||
}
|
||||
|
||||
// BulkCreateAttendance creates attendance for multiple students
|
||||
func (h *Handler) BulkCreateAttendance(c *gin.Context) {
|
||||
classID := c.Param("classId")
|
||||
|
||||
var req struct {
|
||||
Date string `json:"date" binding:"required"`
|
||||
Records []struct {
|
||||
StudentID string `json:"student_id" binding:"required"`
|
||||
Status models.AttendanceStatus `json:"status" binding:"required"`
|
||||
Periods int `json:"periods"`
|
||||
Reason string `json:"reason"`
|
||||
} `json:"records" binding:"required"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
respondError(c, http.StatusBadRequest, "Invalid request: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
records := make([]struct {
|
||||
StudentID string
|
||||
Status models.AttendanceStatus
|
||||
Periods int
|
||||
Reason string
|
||||
}, len(req.Records))
|
||||
|
||||
for i, r := range req.Records {
|
||||
records[i] = struct {
|
||||
StudentID string
|
||||
Status models.AttendanceStatus
|
||||
Periods int
|
||||
Reason string
|
||||
}{
|
||||
StudentID: r.StudentID,
|
||||
Status: r.Status,
|
||||
Periods: r.Periods,
|
||||
Reason: r.Reason,
|
||||
}
|
||||
}
|
||||
|
||||
if err := h.gradebookService.BulkCreateAttendance(c.Request.Context(), classID, req.Date, records); err != nil {
|
||||
respondError(c, http.StatusInternalServerError, "Failed to create attendance: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Attendance created"})
|
||||
}
|
||||
|
||||
// Gradebook Entry Handlers
|
||||
|
||||
// CreateGradebookEntry creates a gradebook entry
|
||||
func (h *Handler) CreateGradebookEntry(c *gin.Context) {
|
||||
var req models.CreateGradebookEntryRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
respondError(c, http.StatusBadRequest, "Invalid request: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
entry, err := h.gradebookService.CreateGradebookEntry(c.Request.Context(), &req)
|
||||
if err != nil {
|
||||
respondError(c, http.StatusInternalServerError, "Failed to create entry: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
respondCreated(c, entry)
|
||||
}
|
||||
|
||||
// GetGradebookEntries returns entries for a class
|
||||
func (h *Handler) GetGradebookEntries(c *gin.Context) {
|
||||
classID := c.Param("classId")
|
||||
entryTypeStr := c.Query("entry_type")
|
||||
startDateStr := c.Query("start_date")
|
||||
endDateStr := c.Query("end_date")
|
||||
|
||||
var entryType *string
|
||||
if entryTypeStr != "" {
|
||||
entryType = &entryTypeStr
|
||||
}
|
||||
|
||||
var startDate, endDate *time.Time
|
||||
if startDateStr != "" {
|
||||
t, err := time.Parse("2006-01-02", startDateStr)
|
||||
if err == nil {
|
||||
startDate = &t
|
||||
}
|
||||
}
|
||||
if endDateStr != "" {
|
||||
t, err := time.Parse("2006-01-02", endDateStr)
|
||||
if err == nil {
|
||||
endDate = &t
|
||||
}
|
||||
}
|
||||
|
||||
entries, err := h.gradebookService.GetGradebookEntries(c.Request.Context(), classID, entryType, startDate, endDate)
|
||||
if err != nil {
|
||||
respondError(c, http.StatusInternalServerError, "Failed to get entries: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
respondSuccess(c, entries)
|
||||
}
|
||||
|
||||
// GetStudentEntries returns gradebook entries for a student
|
||||
func (h *Handler) GetStudentEntries(c *gin.Context) {
|
||||
studentID := c.Param("studentId")
|
||||
|
||||
entries, err := h.gradebookService.GetStudentEntries(c.Request.Context(), studentID)
|
||||
if err != nil {
|
||||
respondError(c, http.StatusInternalServerError, "Failed to get entries: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
respondSuccess(c, entries)
|
||||
}
|
||||
|
||||
// DeleteGradebookEntry deletes a gradebook entry
|
||||
func (h *Handler) DeleteGradebookEntry(c *gin.Context) {
|
||||
entryID := c.Param("id")
|
||||
|
||||
if err := h.gradebookService.DeleteGradebookEntry(c.Request.Context(), entryID); err != nil {
|
||||
respondError(c, http.StatusInternalServerError, "Failed to delete entry: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Entry deleted"})
|
||||
}
|
||||
66
school-service/internal/handlers/handlers.go
Normal file
66
school-service/internal/handlers/handlers.go
Normal file
@@ -0,0 +1,66 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/breakpilot/school-service/internal/services"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
)
|
||||
|
||||
// Handler holds all the service dependencies
|
||||
type Handler struct {
|
||||
classService *services.ClassService
|
||||
examService *services.ExamService
|
||||
gradeService *services.GradeService
|
||||
gradebookService *services.GradebookService
|
||||
certificateService *services.CertificateService
|
||||
aiService *services.AIService
|
||||
}
|
||||
|
||||
// NewHandler creates a new Handler with all services
|
||||
func NewHandler(db *pgxpool.Pool, llmGatewayURL string) *Handler {
|
||||
classService := services.NewClassService(db)
|
||||
examService := services.NewExamService(db)
|
||||
gradeService := services.NewGradeService(db)
|
||||
gradebookService := services.NewGradebookService(db)
|
||||
certificateService := services.NewCertificateService(db, gradeService, gradebookService)
|
||||
aiService := services.NewAIService(llmGatewayURL)
|
||||
|
||||
return &Handler{
|
||||
classService: classService,
|
||||
examService: examService,
|
||||
gradeService: gradeService,
|
||||
gradebookService: gradebookService,
|
||||
certificateService: certificateService,
|
||||
aiService: aiService,
|
||||
}
|
||||
}
|
||||
|
||||
// Health returns the service health status
|
||||
func (h *Handler) Health(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": "healthy",
|
||||
"service": "school-service",
|
||||
})
|
||||
}
|
||||
|
||||
// getUserID extracts the user ID from the context (set by auth middleware)
|
||||
func getUserID(c *gin.Context) string {
|
||||
return c.GetString("user_id")
|
||||
}
|
||||
|
||||
// respondError sends an error response
|
||||
func respondError(c *gin.Context, status int, message string) {
|
||||
c.JSON(status, gin.H{"error": message})
|
||||
}
|
||||
|
||||
// respondSuccess sends a success response with data
|
||||
func respondSuccess(c *gin.Context, data interface{}) {
|
||||
c.JSON(http.StatusOK, data)
|
||||
}
|
||||
|
||||
// respondCreated sends a created response with data
|
||||
func respondCreated(c *gin.Context, data interface{}) {
|
||||
c.JSON(http.StatusCreated, data)
|
||||
}
|
||||
Reference in New Issue
Block a user