package httpapi import ( "net/http" "strings" "time" "github.com/gin-gonic/gin" "gorm.io/gorm" "github.com/sundynix/pets-be/internal/model" ) type petInput struct { Name string `json:"name" binding:"required"` Species string `json:"species"` Breed string `json:"breed"` Gender string `json:"gender"` BirthDate string `json:"birth_date"` WeightKg float64 `json:"weight_kg"` AvatarFileID *string `json:"avatar_file_id"` Coat string `json:"coat"` Allergies string `json:"allergies"` CareNotes string `json:"care_notes"` } func (a *API) listPets(c *gin.Context) { var pets []model.Pet if err := a.db.Where("user_id = ? AND archived = false", actor(c)).Order("created_at ASC").Find(&pets).Error; err != nil { fail(c, http.StatusInternalServerError, "读取宠物失败") return } items := make([]gin.H, 0, len(pets)) for _, pet := range pets { items = append(items, a.petView(pet)) } ok(c, items) } func (a *API) getPet(c *gin.Context) { pet, err := a.ownedPet(actor(c), c.Param("id")) if err != nil { fail(c, http.StatusNotFound, "宠物不存在") return } ok(c, a.petView(pet)) } func (a *API) createPet(c *gin.Context) { var in petInput if c.ShouldBindJSON(&in) != nil { fail(c, http.StatusBadRequest, "宠物资料无效") return } pet, valid := a.buildPet(c, in) if !valid { return } pet.UserID = actor(c) if err := a.db.Create(&pet).Error; err != nil { fail(c, http.StatusInternalServerError, "创建宠物失败") return } created(c, a.petView(pet)) } func (a *API) updatePet(c *gin.Context) { pet, err := a.ownedPet(actor(c), c.Param("id")) if err != nil { fail(c, http.StatusNotFound, "宠物不存在") return } var in petInput if c.ShouldBindJSON(&in) != nil { fail(c, http.StatusBadRequest, "宠物资料无效") return } updated, valid := a.buildPet(c, in) if !valid { return } pet.Name, pet.Species, pet.Breed, pet.Gender = updated.Name, updated.Species, updated.Breed, updated.Gender pet.BirthDate, pet.WeightKg, pet.AvatarFileID = updated.BirthDate, updated.WeightKg, updated.AvatarFileID pet.Coat, pet.Allergies, pet.CareNotes = updated.Coat, updated.Allergies, updated.CareNotes if err := a.db.Save(&pet).Error; err != nil { fail(c, http.StatusInternalServerError, "更新宠物失败") return } ok(c, a.petView(pet)) } func (a *API) buildPet(c *gin.Context, in petInput) (model.Pet, bool) { pet := model.Pet{} in.Name = strings.TrimSpace(in.Name) if in.Name == "" || len([]rune(in.Name)) > 64 || in.WeightKg < 0 || in.WeightKg > 200 { fail(c, http.StatusBadRequest, "名字或体重无效") return pet, false } if in.Species == "" { in.Species = "dog" } if in.Species != "dog" && in.Species != "cat" && in.Species != "other" { fail(c, http.StatusBadRequest, "宠物类型无效") return pet, false } if in.AvatarFileID != nil { var count int64 if !validID(*in.AvatarFileID) || a.db.Model(&model.File{}).Where("id = ?", *in.AvatarFileID).Count(&count).Error != nil || count == 0 { fail(c, http.StatusBadRequest, "头像文件无效") return pet, false } } var birthday *time.Time if in.BirthDate != "" { d, err := parseDay(in.BirthDate) if err != nil || d.After(time.Now()) { fail(c, http.StatusBadRequest, "生日无效") return pet, false } birthday = &d } pet = model.Pet{Name: in.Name, Species: in.Species, Breed: strings.TrimSpace(in.Breed), Gender: in.Gender, BirthDate: birthday, WeightKg: in.WeightKg, AvatarFileID: in.AvatarFileID, Coat: in.Coat, Allergies: in.Allergies, CareNotes: in.CareNotes} return pet, true } func (a *API) archivePet(c *gin.Context) { pet, err := a.ownedPet(actor(c), c.Param("id")) if err != nil { fail(c, http.StatusNotFound, "宠物不存在") return } var active int64 if err := a.db.Table("sundynix_walk_pet AS wp").Joins("JOIN sundynix_walk AS w ON w.id = wp.walk_id"). Where("wp.pet_id = ? AND w.status IN ?", pet.ID, []string{"active", "paused"}).Count(&active).Error; err != nil { fail(c, http.StatusInternalServerError, "检查遛狗状态失败") return } if active > 0 { fail(c, http.StatusConflict, "这只宠物正在遛狗,结束后再归档") return } if err := a.db.Model(&model.Pet{}).Where("id = ? AND user_id = ?", pet.ID, actor(c)).Update("archived", true).Error; err != nil && err != gorm.ErrRecordNotFound { fail(c, http.StatusInternalServerError, "归档失败") return } ok(c, gin.H{"archived": true}) }