feat: rebuild pet app and backend around core care flows
This commit is contained in:
@@ -0,0 +1,149 @@
|
||||
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})
|
||||
}
|
||||
Reference in New Issue
Block a user