package handlers import ( "net/http" "github.com/breakpilot/school-service/internal/models" "github.com/gin-gonic/gin" ) // ---------- Solutions ---------- func (h *Handler) CreateTimetableSolution(c *gin.Context) { uid := getUserID(c) if uid == "" { respondError(c, http.StatusUnauthorized, "User not authenticated") return } var req models.CreateTimetableSolutionRequest if err := c.ShouldBindJSON(&req); err != nil { respondError(c, http.StatusBadRequest, "Invalid request: "+err.Error()) return } sol, err := h.timetableService.CreateSolution(c.Request.Context(), uid, &req) if err != nil { respondError(c, http.StatusInternalServerError, "Failed to create solution: "+err.Error()) return } // Fire-and-forget the solver invocation; the row is persisted regardless. if err := h.timetableService.TriggerSolve(c.Request.Context(), h.solverServiceURL, sol.ID.String(), uid); err != nil { // Don't fail the request — the solution row already shows status=failed. // The client will see error_message via GET /solutions/:id. respondCreated(c, sol) return } respondCreated(c, sol) } func (h *Handler) ListTimetableSolutions(c *gin.Context) { uid := getUserID(c) if uid == "" { respondError(c, http.StatusUnauthorized, "User not authenticated") return } out, err := h.timetableService.ListSolutions(c.Request.Context(), uid) if err != nil { respondError(c, http.StatusInternalServerError, "Failed to list solutions: "+err.Error()) return } respondSuccess(c, out) } func (h *Handler) GetTimetableSolution(c *gin.Context) { uid := getUserID(c) if uid == "" { respondError(c, http.StatusUnauthorized, "User not authenticated") return } sol, err := h.timetableService.GetSolution(c.Request.Context(), c.Param("id"), uid) if err != nil { respondError(c, http.StatusNotFound, "Solution not found") return } respondSuccess(c, sol) } func (h *Handler) ListTimetableLessons(c *gin.Context) { uid := getUserID(c) if uid == "" { respondError(c, http.StatusUnauthorized, "User not authenticated") return } out, err := h.timetableService.ListLessons(c.Request.Context(), c.Param("id"), uid) if err != nil { respondError(c, http.StatusInternalServerError, "Failed to list lessons: "+err.Error()) return } respondSuccess(c, out) } func (h *Handler) DeleteTimetableSolution(c *gin.Context) { uid := getUserID(c) if uid == "" { respondError(c, http.StatusUnauthorized, "User not authenticated") return } if err := h.timetableService.DeleteSolution(c.Request.Context(), c.Param("id"), uid); err != nil { respondError(c, http.StatusInternalServerError, "Failed to delete solution: "+err.Error()) return } c.JSON(http.StatusOK, gin.H{"message": "Solution deleted"}) } // UpdateTimetableLessonPin flips the pinned flag on a single lesson. // The solver respects pinned cells via @PlanningPin when this user re-solves. func (h *Handler) UpdateTimetableLessonPin(c *gin.Context) { uid := getUserID(c) if uid == "" { respondError(c, http.StatusUnauthorized, "User not authenticated") return } var req models.UpdateLessonPinRequest if err := c.ShouldBindJSON(&req); err != nil { respondError(c, http.StatusBadRequest, "Invalid request: "+err.Error()) return } if err := h.timetableService.UpdateLessonPin(c.Request.Context(), c.Param("id"), uid, req.Pinned); err != nil { respondError(c, http.StatusInternalServerError, "Failed to update lesson pin: "+err.Error()) return } c.JSON(http.StatusOK, gin.H{"message": "Lesson pin updated", "pinned": req.Pinned}) }