package handler import ( "time" "github.com/gin-gonic/gin" "gorm.io/datatypes" "github.com/sundynix/pets-be/internal/middleware" "github.com/sundynix/pets-be/internal/service" "github.com/sundynix/pets-be/pkg/response" ) type petReq struct { Name string `json:"name"` Emoji string `json:"emoji"` Type string `json:"type"` Gender string `json:"gender"` Birthday string `json:"birthday"` // YYYY-MM-DD ArrivedAt string `json:"arrived_at"` // YYYY-MM-DD,到家日期 Weight string `json:"weight"` Stage string `json:"stage"` Age string `json:"age"` Color string `json:"color"` Breed string `json:"breed"` AvatarFileID string `json:"avatar_file_id"` Goals []string `json:"goals"` } func (r petReq) toInput() service.PetInput { in := service.PetInput{ Name: r.Name, Emoji: r.Emoji, Type: r.Type, Gender: r.Gender, Weight: r.Weight, Stage: r.Stage, Age: r.Age, Color: r.Color, Breed: r.Breed, AvatarFileID: r.AvatarFileID, } if r.Birthday != "" { if t, err := time.ParseInLocation("2006-01-02", r.Birthday, time.Local); err == nil { // 生日不接受未来日期:前端限制过一道,服务端不能只信前端 if !t.After(time.Now()) { in.Birthday = &t } } } if r.ArrivedAt != "" { if t, err := time.ParseInLocation("2006-01-02", r.ArrivedAt, time.Local); err == nil && !t.After(time.Now()) { in.ArrivedAt = &t } } // 阶段以生日为准。用户在引导页可以手改,但服务端只认年龄阶段三选一—— // 「刚到家」是叠加包不是年龄段,混进来会让整套模板取错。 in.Stage = service.NormalizeStage(service.SpeciesOf(r.Type), in.Stage, in.Birthday) if r.Goals != nil { if b, err := jsonMarshal(r.Goals); err == nil { in.Goals = datatypes.JSON(b) } } return in } // ListPets GET /api/pets func (h *Handler) ListPets(c *gin.Context) { pets, err := h.svc.ListPets(middleware.UserID(c)) if err != nil { response.FailErr(c, err) return } response.OK(c, pets) } // CreatePet POST /api/pets func (h *Handler) CreatePet(c *gin.Context) { var req petReq if err := c.ShouldBindJSON(&req); err != nil { response.FailParams(c, err.Error()) return } pet, err := h.svc.CreatePet(middleware.UserID(c), req.toInput()) if err != nil { response.FailErr(c, err) return } response.OK(c, pet) } // GetPet GET /api/pets/:id func (h *Handler) GetPet(c *gin.Context) { pet, err := h.svc.GetPet(middleware.UserID(c), idParam(c, "id")) if err != nil { respondErr(c, err) return } response.OK(c, pet) } // UpdatePet PUT /api/pets/:id func (h *Handler) UpdatePet(c *gin.Context) { var req petReq if err := c.ShouldBindJSON(&req); err != nil { response.FailParams(c, err.Error()) return } fields := map[string]any{} if req.Name != "" { fields["name"] = req.Name } if req.Emoji != "" { fields["emoji"] = req.Emoji } if req.Weight != "" { fields["weight"] = req.Weight } if req.Stage != "" { fields["stage"] = req.Stage } if req.Gender != "" { fields["gender"] = req.Gender } if req.Age != "" { fields["age"] = req.Age } if req.Color != "" { fields["color"] = req.Color } if req.Breed != "" { fields["breed"] = req.Breed } if req.AvatarFileID != "" { fields["avatar_file_id"] = req.AvatarFileID } pet, err := h.svc.UpdatePet(middleware.UserID(c), idParam(c, "id"), fields) if err != nil { respondErr(c, err) return } response.OK(c, pet) } // DeletePet DELETE /api/pets/:id func (h *Handler) DeletePet(c *gin.Context) { if err := h.svc.DeletePet(middleware.UserID(c), idParam(c, "id")); err != nil { respondErr(c, err) return } response.OK(c, gin.H{"deleted": true}) } // Onboarding POST /api/onboarding func (h *Handler) Onboarding(c *gin.Context) { var req petReq if err := c.ShouldBindJSON(&req); err != nil { response.FailParams(c, err.Error()) return } pet, err := h.svc.Onboarding(middleware.UserID(c), req.toInput()) if err != nil { response.FailErr(c, err) return } response.OK(c, pet) }