package handler import ( "net/http" "strings" "time" "github.com/gin-gonic/gin" "github.com/google/uuid" "github.com/yuxingu/digital-psychology/apps/api/internal/middleware" "github.com/yuxingu/digital-psychology/apps/api/internal/repository" "github.com/yuxingu/digital-psychology/apps/api/pkg/response" ) // GrowthHandler exposes growth plans and mood trail. type GrowthHandler struct { Plans *repository.GrowthRepo } // Register mounts growth routes. func (h *GrowthHandler) Register(rg *gin.RouterGroup) { rg.GET("/growth/plans", h.ListPlans) rg.POST("/growth/plans", h.CreatePlan) rg.POST("/growth/plans/:id/checkin", h.Checkin) rg.GET("/growth/plans/:id/checkins", h.ListCheckins) rg.GET("/moods/recent", h.MoodsRecent) } // ListPlans handles GET /growth/plans. func (h *GrowthHandler) ListPlans(c *gin.Context) { userID, ok := middleware.UserIDFromContext(c) if !ok { response.Fail(c, http.StatusUnauthorized, 40100, "unauthorized") return } items, err := h.Plans.ListPlans(c.Request.Context(), userID) if err != nil { response.Fail(c, http.StatusInternalServerError, 50000, err.Error()) return } response.OK(c, gin.H{"items": items}) } // CreatePlan handles POST /growth/plans. func (h *GrowthHandler) CreatePlan(c *gin.Context) { userID, ok := middleware.UserIDFromContext(c) if !ok { response.Fail(c, http.StatusUnauthorized, 40100, "unauthorized") return } var req struct { Title string `json:"title" binding:"required"` Focus string `json:"focus"` } if err := c.ShouldBindJSON(&req); err != nil { response.Fail(c, http.StatusBadRequest, 10000, "invalid request") return } title := strings.TrimSpace(req.Title) if title == "" || len([]rune(title)) > 40 { response.Fail(c, http.StatusBadRequest, 10000, "invalid title") return } p, err := h.Plans.CreatePlan(c.Request.Context(), userID, title, strings.TrimSpace(req.Focus)) if err != nil { response.Fail(c, http.StatusBadRequest, 30020, err.Error()) return } response.OK(c, p) } // Checkin handles POST /growth/plans/:id/checkin. func (h *GrowthHandler) Checkin(c *gin.Context) { userID, ok := middleware.UserIDFromContext(c) if !ok { response.Fail(c, http.StatusUnauthorized, 40100, "unauthorized") return } pid, err := uuid.Parse(c.Param("id")) if err != nil { response.Fail(c, http.StatusBadRequest, 10000, "invalid id") return } var req struct { Note *string `json:"note"` } _ = c.ShouldBindJSON(&req) out, err := h.Plans.Checkin(c.Request.Context(), userID, pid, time.Now(), req.Note) if err != nil { response.Fail(c, http.StatusBadRequest, 30021, err.Error()) return } response.OK(c, out) } // ListCheckins handles GET /growth/plans/:id/checkins. func (h *GrowthHandler) ListCheckins(c *gin.Context) { userID, ok := middleware.UserIDFromContext(c) if !ok { response.Fail(c, http.StatusUnauthorized, 40100, "unauthorized") return } pid, err := uuid.Parse(c.Param("id")) if err != nil { response.Fail(c, http.StatusBadRequest, 10000, "invalid id") return } items, err := h.Plans.ListCheckinsRecent(c.Request.Context(), userID, pid, 14) if err != nil { response.Fail(c, http.StatusInternalServerError, 50000, err.Error()) return } response.OK(c, gin.H{"items": items}) } // MoodsRecent handles GET /moods/recent. func (h *GrowthHandler) MoodsRecent(c *gin.Context) { userID, ok := middleware.UserIDFromContext(c) if !ok { response.Fail(c, http.StatusUnauthorized, 40100, "unauthorized") return } items, err := h.Plans.ListMoodsRecent(c.Request.Context(), userID, 7) if err != nil { response.Fail(c, http.StatusInternalServerError, 50000, err.Error()) return } response.OK(c, gin.H{"items": items}) }