Add timetable scheduler Phases 1 + 2 to school-service

Phase 1 — Stammdaten (7 tables):
  tt_class, tt_period, tt_room, tt_subject, tt_teacher,
  tt_curriculum, tt_assignment with CRUD endpoints.

Phase 2 — Constraints (15 typed tables):
  Teacher (6): unavailable_day, unavailable_window, max_hours_day,
    max_hours_week, excluded_subject, excluded_room
  Subject (5): min_day_gap, max_consecutive, contiguous_when_repeated,
    preferred_period, double_lesson
  Class (2): max_hours_day, no_gaps
  Room (2): requires_type, unavailable

Each constraint row carries is_hard / weight / active / note /
created_by_user_id; ownership enforced via WHERE EXISTS against the
parent tt_teacher/tt_class/tt_subject/tt_room row.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-05-21 22:12:23 +02:00
parent a1488b2fec
commit e958f88a2d
19 changed files with 3276 additions and 0 deletions
@@ -16,6 +16,7 @@ type Handler struct {
gradebookService *services.GradebookService
certificateService *services.CertificateService
aiService *services.AIService
timetableService *services.TimetableService
}
// NewHandler creates a new Handler with all services
@@ -26,6 +27,7 @@ func NewHandler(db *pgxpool.Pool, llmGatewayURL string) *Handler {
gradebookService := services.NewGradebookService(db)
certificateService := services.NewCertificateService(db, gradeService, gradebookService)
aiService := services.NewAIService(llmGatewayURL)
timetableService := services.NewTimetableService(db)
return &Handler{
classService: classService,
@@ -34,6 +36,7 @@ func NewHandler(db *pgxpool.Pool, llmGatewayURL string) *Handler {
gradebookService: gradebookService,
certificateService: certificateService,
aiService: aiService,
timetableService: timetableService,
}
}
@@ -0,0 +1,202 @@
package handlers
import (
"net/http"
"github.com/breakpilot/school-service/internal/models"
"github.com/gin-gonic/gin"
)
// Class- and Room-constraint HTTP handlers.
// ---------- Class Max Hours / Day ----------
func (h *Handler) CreateClassMaxHoursDay(c *gin.Context) {
uid := getUserID(c)
if uid == "" {
respondError(c, http.StatusUnauthorized, "User not authenticated")
return
}
var req models.CreateClassMaxHoursDayRequest
if err := c.ShouldBindJSON(&req); err != nil {
respondError(c, http.StatusBadRequest, "Invalid request: "+err.Error())
return
}
out, err := h.timetableService.CreateClassMaxHoursDay(c.Request.Context(), uid, &req)
if err != nil {
respondError(c, http.StatusInternalServerError, "Failed to create constraint: "+err.Error())
return
}
respondCreated(c, out)
}
func (h *Handler) ListClassMaxHoursDay(c *gin.Context) {
uid := getUserID(c)
if uid == "" {
respondError(c, http.StatusUnauthorized, "User not authenticated")
return
}
out, err := h.timetableService.ListClassMaxHoursDay(c.Request.Context(), uid)
if err != nil {
respondError(c, http.StatusInternalServerError, "Failed to list constraints: "+err.Error())
return
}
respondSuccess(c, out)
}
func (h *Handler) DeleteClassMaxHoursDay(c *gin.Context) {
uid := getUserID(c)
if uid == "" {
respondError(c, http.StatusUnauthorized, "User not authenticated")
return
}
if err := h.timetableService.DeleteClassMaxHoursDay(c.Request.Context(), c.Param("id"), uid); err != nil {
respondError(c, http.StatusInternalServerError, "Failed to delete constraint: "+err.Error())
return
}
c.JSON(http.StatusOK, gin.H{"message": "Constraint deleted"})
}
// ---------- Class No Gaps ----------
func (h *Handler) CreateClassNoGaps(c *gin.Context) {
uid := getUserID(c)
if uid == "" {
respondError(c, http.StatusUnauthorized, "User not authenticated")
return
}
var req models.CreateClassNoGapsRequest
if err := c.ShouldBindJSON(&req); err != nil {
respondError(c, http.StatusBadRequest, "Invalid request: "+err.Error())
return
}
out, err := h.timetableService.CreateClassNoGaps(c.Request.Context(), uid, &req)
if err != nil {
respondError(c, http.StatusInternalServerError, "Failed to create constraint: "+err.Error())
return
}
respondCreated(c, out)
}
func (h *Handler) ListClassNoGaps(c *gin.Context) {
uid := getUserID(c)
if uid == "" {
respondError(c, http.StatusUnauthorized, "User not authenticated")
return
}
out, err := h.timetableService.ListClassNoGaps(c.Request.Context(), uid)
if err != nil {
respondError(c, http.StatusInternalServerError, "Failed to list constraints: "+err.Error())
return
}
respondSuccess(c, out)
}
func (h *Handler) DeleteClassNoGaps(c *gin.Context) {
uid := getUserID(c)
if uid == "" {
respondError(c, http.StatusUnauthorized, "User not authenticated")
return
}
if err := h.timetableService.DeleteClassNoGaps(c.Request.Context(), c.Param("id"), uid); err != nil {
respondError(c, http.StatusInternalServerError, "Failed to delete constraint: "+err.Error())
return
}
c.JSON(http.StatusOK, gin.H{"message": "Constraint deleted"})
}
// ---------- Room Requires Type ----------
func (h *Handler) CreateRoomRequiresType(c *gin.Context) {
uid := getUserID(c)
if uid == "" {
respondError(c, http.StatusUnauthorized, "User not authenticated")
return
}
var req models.CreateRoomRequiresTypeRequest
if err := c.ShouldBindJSON(&req); err != nil {
respondError(c, http.StatusBadRequest, "Invalid request: "+err.Error())
return
}
out, err := h.timetableService.CreateRoomRequiresType(c.Request.Context(), uid, &req)
if err != nil {
respondError(c, http.StatusInternalServerError, "Failed to create constraint: "+err.Error())
return
}
respondCreated(c, out)
}
func (h *Handler) ListRoomRequiresTypes(c *gin.Context) {
uid := getUserID(c)
if uid == "" {
respondError(c, http.StatusUnauthorized, "User not authenticated")
return
}
out, err := h.timetableService.ListRoomRequiresTypes(c.Request.Context(), uid)
if err != nil {
respondError(c, http.StatusInternalServerError, "Failed to list constraints: "+err.Error())
return
}
respondSuccess(c, out)
}
func (h *Handler) DeleteRoomRequiresType(c *gin.Context) {
uid := getUserID(c)
if uid == "" {
respondError(c, http.StatusUnauthorized, "User not authenticated")
return
}
if err := h.timetableService.DeleteRoomRequiresType(c.Request.Context(), c.Param("id"), uid); err != nil {
respondError(c, http.StatusInternalServerError, "Failed to delete constraint: "+err.Error())
return
}
c.JSON(http.StatusOK, gin.H{"message": "Constraint deleted"})
}
// ---------- Room Unavailable ----------
func (h *Handler) CreateRoomUnavailable(c *gin.Context) {
uid := getUserID(c)
if uid == "" {
respondError(c, http.StatusUnauthorized, "User not authenticated")
return
}
var req models.CreateRoomUnavailableRequest
if err := c.ShouldBindJSON(&req); err != nil {
respondError(c, http.StatusBadRequest, "Invalid request: "+err.Error())
return
}
out, err := h.timetableService.CreateRoomUnavailable(c.Request.Context(), uid, &req)
if err != nil {
respondError(c, http.StatusInternalServerError, "Failed to create constraint: "+err.Error())
return
}
respondCreated(c, out)
}
func (h *Handler) ListRoomUnavailable(c *gin.Context) {
uid := getUserID(c)
if uid == "" {
respondError(c, http.StatusUnauthorized, "User not authenticated")
return
}
out, err := h.timetableService.ListRoomUnavailable(c.Request.Context(), uid)
if err != nil {
respondError(c, http.StatusInternalServerError, "Failed to list constraints: "+err.Error())
return
}
respondSuccess(c, out)
}
func (h *Handler) DeleteRoomUnavailable(c *gin.Context) {
uid := getUserID(c)
if uid == "" {
respondError(c, http.StatusUnauthorized, "User not authenticated")
return
}
if err := h.timetableService.DeleteRoomUnavailable(c.Request.Context(), c.Param("id"), uid); err != nil {
respondError(c, http.StatusInternalServerError, "Failed to delete constraint: "+err.Error())
return
}
c.JSON(http.StatusOK, gin.H{"message": "Constraint deleted"})
}
@@ -0,0 +1,250 @@
package handlers
import (
"net/http"
"github.com/breakpilot/school-service/internal/models"
"github.com/gin-gonic/gin"
)
// Subject-constraint HTTP handlers.
// ---------- Subject Min Day Gap ----------
func (h *Handler) CreateSubjectMinDayGap(c *gin.Context) {
uid := getUserID(c)
if uid == "" {
respondError(c, http.StatusUnauthorized, "User not authenticated")
return
}
var req models.CreateSubjectMinDayGapRequest
if err := c.ShouldBindJSON(&req); err != nil {
respondError(c, http.StatusBadRequest, "Invalid request: "+err.Error())
return
}
out, err := h.timetableService.CreateSubjectMinDayGap(c.Request.Context(), uid, &req)
if err != nil {
respondError(c, http.StatusInternalServerError, "Failed to create constraint: "+err.Error())
return
}
respondCreated(c, out)
}
func (h *Handler) ListSubjectMinDayGaps(c *gin.Context) {
uid := getUserID(c)
if uid == "" {
respondError(c, http.StatusUnauthorized, "User not authenticated")
return
}
out, err := h.timetableService.ListSubjectMinDayGaps(c.Request.Context(), uid)
if err != nil {
respondError(c, http.StatusInternalServerError, "Failed to list constraints: "+err.Error())
return
}
respondSuccess(c, out)
}
func (h *Handler) DeleteSubjectMinDayGap(c *gin.Context) {
uid := getUserID(c)
if uid == "" {
respondError(c, http.StatusUnauthorized, "User not authenticated")
return
}
if err := h.timetableService.DeleteSubjectMinDayGap(c.Request.Context(), c.Param("id"), uid); err != nil {
respondError(c, http.StatusInternalServerError, "Failed to delete constraint: "+err.Error())
return
}
c.JSON(http.StatusOK, gin.H{"message": "Constraint deleted"})
}
// ---------- Subject Max Consecutive ----------
func (h *Handler) CreateSubjectMaxConsecutive(c *gin.Context) {
uid := getUserID(c)
if uid == "" {
respondError(c, http.StatusUnauthorized, "User not authenticated")
return
}
var req models.CreateSubjectMaxConsecutiveRequest
if err := c.ShouldBindJSON(&req); err != nil {
respondError(c, http.StatusBadRequest, "Invalid request: "+err.Error())
return
}
out, err := h.timetableService.CreateSubjectMaxConsecutive(c.Request.Context(), uid, &req)
if err != nil {
respondError(c, http.StatusInternalServerError, "Failed to create constraint: "+err.Error())
return
}
respondCreated(c, out)
}
func (h *Handler) ListSubjectMaxConsecutives(c *gin.Context) {
uid := getUserID(c)
if uid == "" {
respondError(c, http.StatusUnauthorized, "User not authenticated")
return
}
out, err := h.timetableService.ListSubjectMaxConsecutives(c.Request.Context(), uid)
if err != nil {
respondError(c, http.StatusInternalServerError, "Failed to list constraints: "+err.Error())
return
}
respondSuccess(c, out)
}
func (h *Handler) DeleteSubjectMaxConsecutive(c *gin.Context) {
uid := getUserID(c)
if uid == "" {
respondError(c, http.StatusUnauthorized, "User not authenticated")
return
}
if err := h.timetableService.DeleteSubjectMaxConsecutive(c.Request.Context(), c.Param("id"), uid); err != nil {
respondError(c, http.StatusInternalServerError, "Failed to delete constraint: "+err.Error())
return
}
c.JSON(http.StatusOK, gin.H{"message": "Constraint deleted"})
}
// ---------- Subject Contiguous When Repeated ----------
func (h *Handler) CreateSubjectContiguousWhenRepeated(c *gin.Context) {
uid := getUserID(c)
if uid == "" {
respondError(c, http.StatusUnauthorized, "User not authenticated")
return
}
var req models.CreateSubjectContiguousWhenRepeatedRequest
if err := c.ShouldBindJSON(&req); err != nil {
respondError(c, http.StatusBadRequest, "Invalid request: "+err.Error())
return
}
out, err := h.timetableService.CreateSubjectContiguousWhenRepeated(c.Request.Context(), uid, &req)
if err != nil {
respondError(c, http.StatusInternalServerError, "Failed to create constraint: "+err.Error())
return
}
respondCreated(c, out)
}
func (h *Handler) ListSubjectContiguousWhenRepeated(c *gin.Context) {
uid := getUserID(c)
if uid == "" {
respondError(c, http.StatusUnauthorized, "User not authenticated")
return
}
out, err := h.timetableService.ListSubjectContiguousWhenRepeated(c.Request.Context(), uid)
if err != nil {
respondError(c, http.StatusInternalServerError, "Failed to list constraints: "+err.Error())
return
}
respondSuccess(c, out)
}
func (h *Handler) DeleteSubjectContiguousWhenRepeated(c *gin.Context) {
uid := getUserID(c)
if uid == "" {
respondError(c, http.StatusUnauthorized, "User not authenticated")
return
}
if err := h.timetableService.DeleteSubjectContiguousWhenRepeated(c.Request.Context(), c.Param("id"), uid); err != nil {
respondError(c, http.StatusInternalServerError, "Failed to delete constraint: "+err.Error())
return
}
c.JSON(http.StatusOK, gin.H{"message": "Constraint deleted"})
}
// ---------- Subject Preferred Period ----------
func (h *Handler) CreateSubjectPreferredPeriod(c *gin.Context) {
uid := getUserID(c)
if uid == "" {
respondError(c, http.StatusUnauthorized, "User not authenticated")
return
}
var req models.CreateSubjectPreferredPeriodRequest
if err := c.ShouldBindJSON(&req); err != nil {
respondError(c, http.StatusBadRequest, "Invalid request: "+err.Error())
return
}
out, err := h.timetableService.CreateSubjectPreferredPeriod(c.Request.Context(), uid, &req)
if err != nil {
respondError(c, http.StatusInternalServerError, "Failed to create constraint: "+err.Error())
return
}
respondCreated(c, out)
}
func (h *Handler) ListSubjectPreferredPeriods(c *gin.Context) {
uid := getUserID(c)
if uid == "" {
respondError(c, http.StatusUnauthorized, "User not authenticated")
return
}
out, err := h.timetableService.ListSubjectPreferredPeriods(c.Request.Context(), uid)
if err != nil {
respondError(c, http.StatusInternalServerError, "Failed to list constraints: "+err.Error())
return
}
respondSuccess(c, out)
}
func (h *Handler) DeleteSubjectPreferredPeriod(c *gin.Context) {
uid := getUserID(c)
if uid == "" {
respondError(c, http.StatusUnauthorized, "User not authenticated")
return
}
if err := h.timetableService.DeleteSubjectPreferredPeriod(c.Request.Context(), c.Param("id"), uid); err != nil {
respondError(c, http.StatusInternalServerError, "Failed to delete constraint: "+err.Error())
return
}
c.JSON(http.StatusOK, gin.H{"message": "Constraint deleted"})
}
// ---------- Subject Double Lesson ----------
func (h *Handler) CreateSubjectDoubleLesson(c *gin.Context) {
uid := getUserID(c)
if uid == "" {
respondError(c, http.StatusUnauthorized, "User not authenticated")
return
}
var req models.CreateSubjectDoubleLessonRequest
if err := c.ShouldBindJSON(&req); err != nil {
respondError(c, http.StatusBadRequest, "Invalid request: "+err.Error())
return
}
out, err := h.timetableService.CreateSubjectDoubleLesson(c.Request.Context(), uid, &req)
if err != nil {
respondError(c, http.StatusInternalServerError, "Failed to create constraint: "+err.Error())
return
}
respondCreated(c, out)
}
func (h *Handler) ListSubjectDoubleLessons(c *gin.Context) {
uid := getUserID(c)
if uid == "" {
respondError(c, http.StatusUnauthorized, "User not authenticated")
return
}
out, err := h.timetableService.ListSubjectDoubleLessons(c.Request.Context(), uid)
if err != nil {
respondError(c, http.StatusInternalServerError, "Failed to list constraints: "+err.Error())
return
}
respondSuccess(c, out)
}
func (h *Handler) DeleteSubjectDoubleLesson(c *gin.Context) {
uid := getUserID(c)
if uid == "" {
respondError(c, http.StatusUnauthorized, "User not authenticated")
return
}
if err := h.timetableService.DeleteSubjectDoubleLesson(c.Request.Context(), c.Param("id"), uid); err != nil {
respondError(c, http.StatusInternalServerError, "Failed to delete constraint: "+err.Error())
return
}
c.JSON(http.StatusOK, gin.H{"message": "Constraint deleted"})
}
@@ -0,0 +1,300 @@
package handlers
import (
"net/http"
"github.com/breakpilot/school-service/internal/models"
"github.com/gin-gonic/gin"
)
// Teacher-constraint HTTP handlers. They share the same auth + JSON-bind
// shape as the existing timetable handlers; per-table the only thing that
// differs is the request DTO type and the service method invoked.
// ---------- Teacher Unavailable Day ----------
func (h *Handler) CreateTeacherUnavailableDay(c *gin.Context) {
uid := getUserID(c)
if uid == "" {
respondError(c, http.StatusUnauthorized, "User not authenticated")
return
}
var req models.CreateTeacherUnavailableDayRequest
if err := c.ShouldBindJSON(&req); err != nil {
respondError(c, http.StatusBadRequest, "Invalid request: "+err.Error())
return
}
out, err := h.timetableService.CreateTeacherUnavailableDay(c.Request.Context(), uid, &req)
if err != nil {
respondError(c, http.StatusInternalServerError, "Failed to create constraint: "+err.Error())
return
}
respondCreated(c, out)
}
func (h *Handler) ListTeacherUnavailableDays(c *gin.Context) {
uid := getUserID(c)
if uid == "" {
respondError(c, http.StatusUnauthorized, "User not authenticated")
return
}
out, err := h.timetableService.ListTeacherUnavailableDays(c.Request.Context(), uid)
if err != nil {
respondError(c, http.StatusInternalServerError, "Failed to list constraints: "+err.Error())
return
}
respondSuccess(c, out)
}
func (h *Handler) DeleteTeacherUnavailableDay(c *gin.Context) {
uid := getUserID(c)
if uid == "" {
respondError(c, http.StatusUnauthorized, "User not authenticated")
return
}
if err := h.timetableService.DeleteTeacherUnavailableDay(c.Request.Context(), c.Param("id"), uid); err != nil {
respondError(c, http.StatusInternalServerError, "Failed to delete constraint: "+err.Error())
return
}
c.JSON(http.StatusOK, gin.H{"message": "Constraint deleted"})
}
// ---------- Teacher Unavailable Window ----------
func (h *Handler) CreateTeacherUnavailableWindow(c *gin.Context) {
uid := getUserID(c)
if uid == "" {
respondError(c, http.StatusUnauthorized, "User not authenticated")
return
}
var req models.CreateTeacherUnavailableWindowRequest
if err := c.ShouldBindJSON(&req); err != nil {
respondError(c, http.StatusBadRequest, "Invalid request: "+err.Error())
return
}
out, err := h.timetableService.CreateTeacherUnavailableWindow(c.Request.Context(), uid, &req)
if err != nil {
respondError(c, http.StatusInternalServerError, "Failed to create constraint: "+err.Error())
return
}
respondCreated(c, out)
}
func (h *Handler) ListTeacherUnavailableWindows(c *gin.Context) {
uid := getUserID(c)
if uid == "" {
respondError(c, http.StatusUnauthorized, "User not authenticated")
return
}
out, err := h.timetableService.ListTeacherUnavailableWindows(c.Request.Context(), uid)
if err != nil {
respondError(c, http.StatusInternalServerError, "Failed to list constraints: "+err.Error())
return
}
respondSuccess(c, out)
}
func (h *Handler) DeleteTeacherUnavailableWindow(c *gin.Context) {
uid := getUserID(c)
if uid == "" {
respondError(c, http.StatusUnauthorized, "User not authenticated")
return
}
if err := h.timetableService.DeleteTeacherUnavailableWindow(c.Request.Context(), c.Param("id"), uid); err != nil {
respondError(c, http.StatusInternalServerError, "Failed to delete constraint: "+err.Error())
return
}
c.JSON(http.StatusOK, gin.H{"message": "Constraint deleted"})
}
// ---------- Teacher Max Hours / Day ----------
func (h *Handler) CreateTeacherMaxHoursDay(c *gin.Context) {
uid := getUserID(c)
if uid == "" {
respondError(c, http.StatusUnauthorized, "User not authenticated")
return
}
var req models.CreateTeacherMaxHoursDayRequest
if err := c.ShouldBindJSON(&req); err != nil {
respondError(c, http.StatusBadRequest, "Invalid request: "+err.Error())
return
}
out, err := h.timetableService.CreateTeacherMaxHoursDay(c.Request.Context(), uid, &req)
if err != nil {
respondError(c, http.StatusInternalServerError, "Failed to create constraint: "+err.Error())
return
}
respondCreated(c, out)
}
func (h *Handler) ListTeacherMaxHoursDay(c *gin.Context) {
uid := getUserID(c)
if uid == "" {
respondError(c, http.StatusUnauthorized, "User not authenticated")
return
}
out, err := h.timetableService.ListTeacherMaxHoursDay(c.Request.Context(), uid)
if err != nil {
respondError(c, http.StatusInternalServerError, "Failed to list constraints: "+err.Error())
return
}
respondSuccess(c, out)
}
func (h *Handler) DeleteTeacherMaxHoursDay(c *gin.Context) {
uid := getUserID(c)
if uid == "" {
respondError(c, http.StatusUnauthorized, "User not authenticated")
return
}
if err := h.timetableService.DeleteTeacherMaxHoursDay(c.Request.Context(), c.Param("id"), uid); err != nil {
respondError(c, http.StatusInternalServerError, "Failed to delete constraint: "+err.Error())
return
}
c.JSON(http.StatusOK, gin.H{"message": "Constraint deleted"})
}
// ---------- Teacher Max Hours / Week ----------
func (h *Handler) CreateTeacherMaxHoursWeek(c *gin.Context) {
uid := getUserID(c)
if uid == "" {
respondError(c, http.StatusUnauthorized, "User not authenticated")
return
}
var req models.CreateTeacherMaxHoursWeekRequest
if err := c.ShouldBindJSON(&req); err != nil {
respondError(c, http.StatusBadRequest, "Invalid request: "+err.Error())
return
}
out, err := h.timetableService.CreateTeacherMaxHoursWeek(c.Request.Context(), uid, &req)
if err != nil {
respondError(c, http.StatusInternalServerError, "Failed to create constraint: "+err.Error())
return
}
respondCreated(c, out)
}
func (h *Handler) ListTeacherMaxHoursWeek(c *gin.Context) {
uid := getUserID(c)
if uid == "" {
respondError(c, http.StatusUnauthorized, "User not authenticated")
return
}
out, err := h.timetableService.ListTeacherMaxHoursWeek(c.Request.Context(), uid)
if err != nil {
respondError(c, http.StatusInternalServerError, "Failed to list constraints: "+err.Error())
return
}
respondSuccess(c, out)
}
func (h *Handler) DeleteTeacherMaxHoursWeek(c *gin.Context) {
uid := getUserID(c)
if uid == "" {
respondError(c, http.StatusUnauthorized, "User not authenticated")
return
}
if err := h.timetableService.DeleteTeacherMaxHoursWeek(c.Request.Context(), c.Param("id"), uid); err != nil {
respondError(c, http.StatusInternalServerError, "Failed to delete constraint: "+err.Error())
return
}
c.JSON(http.StatusOK, gin.H{"message": "Constraint deleted"})
}
// ---------- Teacher Excluded Subject ----------
func (h *Handler) CreateTeacherExcludedSubject(c *gin.Context) {
uid := getUserID(c)
if uid == "" {
respondError(c, http.StatusUnauthorized, "User not authenticated")
return
}
var req models.CreateTeacherExcludedSubjectRequest
if err := c.ShouldBindJSON(&req); err != nil {
respondError(c, http.StatusBadRequest, "Invalid request: "+err.Error())
return
}
out, err := h.timetableService.CreateTeacherExcludedSubject(c.Request.Context(), uid, &req)
if err != nil {
respondError(c, http.StatusInternalServerError, "Failed to create constraint: "+err.Error())
return
}
respondCreated(c, out)
}
func (h *Handler) ListTeacherExcludedSubjects(c *gin.Context) {
uid := getUserID(c)
if uid == "" {
respondError(c, http.StatusUnauthorized, "User not authenticated")
return
}
out, err := h.timetableService.ListTeacherExcludedSubjects(c.Request.Context(), uid)
if err != nil {
respondError(c, http.StatusInternalServerError, "Failed to list constraints: "+err.Error())
return
}
respondSuccess(c, out)
}
func (h *Handler) DeleteTeacherExcludedSubject(c *gin.Context) {
uid := getUserID(c)
if uid == "" {
respondError(c, http.StatusUnauthorized, "User not authenticated")
return
}
if err := h.timetableService.DeleteTeacherExcludedSubject(c.Request.Context(), c.Param("id"), uid); err != nil {
respondError(c, http.StatusInternalServerError, "Failed to delete constraint: "+err.Error())
return
}
c.JSON(http.StatusOK, gin.H{"message": "Constraint deleted"})
}
// ---------- Teacher Excluded Room ----------
func (h *Handler) CreateTeacherExcludedRoom(c *gin.Context) {
uid := getUserID(c)
if uid == "" {
respondError(c, http.StatusUnauthorized, "User not authenticated")
return
}
var req models.CreateTeacherExcludedRoomRequest
if err := c.ShouldBindJSON(&req); err != nil {
respondError(c, http.StatusBadRequest, "Invalid request: "+err.Error())
return
}
out, err := h.timetableService.CreateTeacherExcludedRoom(c.Request.Context(), uid, &req)
if err != nil {
respondError(c, http.StatusInternalServerError, "Failed to create constraint: "+err.Error())
return
}
respondCreated(c, out)
}
func (h *Handler) ListTeacherExcludedRooms(c *gin.Context) {
uid := getUserID(c)
if uid == "" {
respondError(c, http.StatusUnauthorized, "User not authenticated")
return
}
out, err := h.timetableService.ListTeacherExcludedRooms(c.Request.Context(), uid)
if err != nil {
respondError(c, http.StatusInternalServerError, "Failed to list constraints: "+err.Error())
return
}
respondSuccess(c, out)
}
func (h *Handler) DeleteTeacherExcludedRoom(c *gin.Context) {
uid := getUserID(c)
if uid == "" {
respondError(c, http.StatusUnauthorized, "User not authenticated")
return
}
if err := h.timetableService.DeleteTeacherExcludedRoom(c.Request.Context(), c.Param("id"), uid); err != nil {
respondError(c, http.StatusInternalServerError, "Failed to delete constraint: "+err.Error())
return
}
c.JSON(http.StatusOK, gin.H{"message": "Constraint deleted"})
}
@@ -0,0 +1,248 @@
package handlers
import (
"net/http"
"github.com/breakpilot/school-service/internal/models"
"github.com/gin-gonic/gin"
)
// ---------- Classes ----------
func (h *Handler) CreateTimetableClass(c *gin.Context) {
uid := getUserID(c)
if uid == "" {
respondError(c, http.StatusUnauthorized, "User not authenticated")
return
}
var req models.CreateTimetableClassRequest
if err := c.ShouldBindJSON(&req); err != nil {
respondError(c, http.StatusBadRequest, "Invalid request: "+err.Error())
return
}
out, err := h.timetableService.CreateClass(c.Request.Context(), uid, &req)
if err != nil {
respondError(c, http.StatusInternalServerError, "Failed to create class: "+err.Error())
return
}
respondCreated(c, out)
}
func (h *Handler) ListTimetableClasses(c *gin.Context) {
uid := getUserID(c)
if uid == "" {
respondError(c, http.StatusUnauthorized, "User not authenticated")
return
}
out, err := h.timetableService.ListClasses(c.Request.Context(), uid)
if err != nil {
respondError(c, http.StatusInternalServerError, "Failed to list classes: "+err.Error())
return
}
respondSuccess(c, out)
}
func (h *Handler) DeleteTimetableClass(c *gin.Context) {
uid := getUserID(c)
if uid == "" {
respondError(c, http.StatusUnauthorized, "User not authenticated")
return
}
if err := h.timetableService.DeleteClass(c.Request.Context(), c.Param("id"), uid); err != nil {
respondError(c, http.StatusInternalServerError, "Failed to delete class: "+err.Error())
return
}
c.JSON(http.StatusOK, gin.H{"message": "Class deleted"})
}
// ---------- Periods ----------
func (h *Handler) CreateTimetablePeriod(c *gin.Context) {
uid := getUserID(c)
if uid == "" {
respondError(c, http.StatusUnauthorized, "User not authenticated")
return
}
var req models.CreateTimetablePeriodRequest
if err := c.ShouldBindJSON(&req); err != nil {
respondError(c, http.StatusBadRequest, "Invalid request: "+err.Error())
return
}
out, err := h.timetableService.CreatePeriod(c.Request.Context(), uid, &req)
if err != nil {
respondError(c, http.StatusInternalServerError, "Failed to create period: "+err.Error())
return
}
respondCreated(c, out)
}
func (h *Handler) ListTimetablePeriods(c *gin.Context) {
uid := getUserID(c)
if uid == "" {
respondError(c, http.StatusUnauthorized, "User not authenticated")
return
}
out, err := h.timetableService.ListPeriods(c.Request.Context(), uid)
if err != nil {
respondError(c, http.StatusInternalServerError, "Failed to list periods: "+err.Error())
return
}
respondSuccess(c, out)
}
func (h *Handler) DeleteTimetablePeriod(c *gin.Context) {
uid := getUserID(c)
if uid == "" {
respondError(c, http.StatusUnauthorized, "User not authenticated")
return
}
if err := h.timetableService.DeletePeriod(c.Request.Context(), c.Param("id"), uid); err != nil {
respondError(c, http.StatusInternalServerError, "Failed to delete period: "+err.Error())
return
}
c.JSON(http.StatusOK, gin.H{"message": "Period deleted"})
}
// ---------- Rooms ----------
func (h *Handler) CreateTimetableRoom(c *gin.Context) {
uid := getUserID(c)
if uid == "" {
respondError(c, http.StatusUnauthorized, "User not authenticated")
return
}
var req models.CreateTimetableRoomRequest
if err := c.ShouldBindJSON(&req); err != nil {
respondError(c, http.StatusBadRequest, "Invalid request: "+err.Error())
return
}
out, err := h.timetableService.CreateRoom(c.Request.Context(), uid, &req)
if err != nil {
respondError(c, http.StatusInternalServerError, "Failed to create room: "+err.Error())
return
}
respondCreated(c, out)
}
func (h *Handler) ListTimetableRooms(c *gin.Context) {
uid := getUserID(c)
if uid == "" {
respondError(c, http.StatusUnauthorized, "User not authenticated")
return
}
out, err := h.timetableService.ListRooms(c.Request.Context(), uid)
if err != nil {
respondError(c, http.StatusInternalServerError, "Failed to list rooms: "+err.Error())
return
}
respondSuccess(c, out)
}
func (h *Handler) DeleteTimetableRoom(c *gin.Context) {
uid := getUserID(c)
if uid == "" {
respondError(c, http.StatusUnauthorized, "User not authenticated")
return
}
if err := h.timetableService.DeleteRoom(c.Request.Context(), c.Param("id"), uid); err != nil {
respondError(c, http.StatusInternalServerError, "Failed to delete room: "+err.Error())
return
}
c.JSON(http.StatusOK, gin.H{"message": "Room deleted"})
}
// ---------- Subjects ----------
func (h *Handler) CreateTimetableSubject(c *gin.Context) {
uid := getUserID(c)
if uid == "" {
respondError(c, http.StatusUnauthorized, "User not authenticated")
return
}
var req models.CreateTimetableSubjectRequest
if err := c.ShouldBindJSON(&req); err != nil {
respondError(c, http.StatusBadRequest, "Invalid request: "+err.Error())
return
}
out, err := h.timetableService.CreateSubject(c.Request.Context(), uid, &req)
if err != nil {
respondError(c, http.StatusInternalServerError, "Failed to create subject: "+err.Error())
return
}
respondCreated(c, out)
}
func (h *Handler) ListTimetableSubjects(c *gin.Context) {
uid := getUserID(c)
if uid == "" {
respondError(c, http.StatusUnauthorized, "User not authenticated")
return
}
out, err := h.timetableService.ListSubjects(c.Request.Context(), uid)
if err != nil {
respondError(c, http.StatusInternalServerError, "Failed to list subjects: "+err.Error())
return
}
respondSuccess(c, out)
}
func (h *Handler) DeleteTimetableSubject(c *gin.Context) {
uid := getUserID(c)
if uid == "" {
respondError(c, http.StatusUnauthorized, "User not authenticated")
return
}
if err := h.timetableService.DeleteSubject(c.Request.Context(), c.Param("id"), uid); err != nil {
respondError(c, http.StatusInternalServerError, "Failed to delete subject: "+err.Error())
return
}
c.JSON(http.StatusOK, gin.H{"message": "Subject deleted"})
}
// ---------- Teachers ----------
func (h *Handler) CreateTimetableTeacher(c *gin.Context) {
uid := getUserID(c)
if uid == "" {
respondError(c, http.StatusUnauthorized, "User not authenticated")
return
}
var req models.CreateTimetableTeacherRequest
if err := c.ShouldBindJSON(&req); err != nil {
respondError(c, http.StatusBadRequest, "Invalid request: "+err.Error())
return
}
out, err := h.timetableService.CreateTeacher(c.Request.Context(), uid, &req)
if err != nil {
respondError(c, http.StatusInternalServerError, "Failed to create teacher: "+err.Error())
return
}
respondCreated(c, out)
}
func (h *Handler) ListTimetableTeachers(c *gin.Context) {
uid := getUserID(c)
if uid == "" {
respondError(c, http.StatusUnauthorized, "User not authenticated")
return
}
out, err := h.timetableService.ListTeachers(c.Request.Context(), uid)
if err != nil {
respondError(c, http.StatusInternalServerError, "Failed to list teachers: "+err.Error())
return
}
respondSuccess(c, out)
}
func (h *Handler) DeleteTimetableTeacher(c *gin.Context) {
uid := getUserID(c)
if uid == "" {
respondError(c, http.StatusUnauthorized, "User not authenticated")
return
}
if err := h.timetableService.DeleteTeacher(c.Request.Context(), c.Param("id"), uid); err != nil {
respondError(c, http.StatusInternalServerError, "Failed to delete teacher: "+err.Error())
return
}
c.JSON(http.StatusOK, gin.H{"message": "Teacher deleted"})
}
@@ -0,0 +1,104 @@
package handlers
import (
"net/http"
"github.com/breakpilot/school-service/internal/models"
"github.com/gin-gonic/gin"
)
// ---------- Curriculum ----------
func (h *Handler) CreateTimetableCurriculum(c *gin.Context) {
uid := getUserID(c)
if uid == "" {
respondError(c, http.StatusUnauthorized, "User not authenticated")
return
}
var req models.CreateTimetableCurriculumRequest
if err := c.ShouldBindJSON(&req); err != nil {
respondError(c, http.StatusBadRequest, "Invalid request: "+err.Error())
return
}
out, err := h.timetableService.CreateCurriculum(c.Request.Context(), uid, &req)
if err != nil {
respondError(c, http.StatusInternalServerError, "Failed to create curriculum: "+err.Error())
return
}
respondCreated(c, out)
}
func (h *Handler) ListTimetableCurriculum(c *gin.Context) {
uid := getUserID(c)
if uid == "" {
respondError(c, http.StatusUnauthorized, "User not authenticated")
return
}
out, err := h.timetableService.ListCurriculum(c.Request.Context(), uid)
if err != nil {
respondError(c, http.StatusInternalServerError, "Failed to list curriculum: "+err.Error())
return
}
respondSuccess(c, out)
}
func (h *Handler) DeleteTimetableCurriculum(c *gin.Context) {
uid := getUserID(c)
if uid == "" {
respondError(c, http.StatusUnauthorized, "User not authenticated")
return
}
if err := h.timetableService.DeleteCurriculum(c.Request.Context(), c.Param("id"), uid); err != nil {
respondError(c, http.StatusInternalServerError, "Failed to delete curriculum: "+err.Error())
return
}
c.JSON(http.StatusOK, gin.H{"message": "Curriculum entry deleted"})
}
// ---------- Assignment ----------
func (h *Handler) CreateTimetableAssignment(c *gin.Context) {
uid := getUserID(c)
if uid == "" {
respondError(c, http.StatusUnauthorized, "User not authenticated")
return
}
var req models.CreateTimetableAssignmentRequest
if err := c.ShouldBindJSON(&req); err != nil {
respondError(c, http.StatusBadRequest, "Invalid request: "+err.Error())
return
}
out, err := h.timetableService.CreateAssignment(c.Request.Context(), uid, &req)
if err != nil {
respondError(c, http.StatusInternalServerError, "Failed to create assignment: "+err.Error())
return
}
respondCreated(c, out)
}
func (h *Handler) ListTimetableAssignments(c *gin.Context) {
uid := getUserID(c)
if uid == "" {
respondError(c, http.StatusUnauthorized, "User not authenticated")
return
}
out, err := h.timetableService.ListAssignments(c.Request.Context(), uid)
if err != nil {
respondError(c, http.StatusInternalServerError, "Failed to list assignments: "+err.Error())
return
}
respondSuccess(c, out)
}
func (h *Handler) DeleteTimetableAssignment(c *gin.Context) {
uid := getUserID(c)
if uid == "" {
respondError(c, http.StatusUnauthorized, "User not authenticated")
return
}
if err := h.timetableService.DeleteAssignment(c.Request.Context(), c.Param("id"), uid); err != nil {
respondError(c, http.StatusInternalServerError, "Failed to delete assignment: "+err.Error())
return
}
c.JSON(http.StatusOK, gin.H{"message": "Assignment deleted"})
}