Files
sundynix-pets/pets-be/internal/httpapi/meetups.go
T

198 lines
6.0 KiB
Go

package httpapi
import (
"net/http"
"strings"
"time"
"github.com/gin-gonic/gin"
"github.com/sundynix/pets-be/internal/model"
"gorm.io/gorm"
)
func (a *API) meetupView(m model.Meetup) gin.H {
var count int64
a.db.Model(&model.MeetupParticipant{}).Where("meetup_id = ?", m.ID).Count(&count)
return gin.H{"id": m.ID, "host_user_id": m.HostUserID, "title": m.Title, "description": m.Description, "place_name": m.PlaceName, "latitude": m.Latitude, "longitude": m.Longitude, "starts_at": m.StartsAt, "max_people": m.MaxPeople, "status": m.Status, "participants": count, "cover_file_id": m.CoverFileID, "cover_url": a.fileURL(m.CoverFileID)}
}
func (a *API) listMeetups(c *gin.Context) {
var rows []model.Meetup
if a.db.Where("starts_at > ? AND status = ?", time.Now().Add(-time.Hour), "open").Order("starts_at ASC").Limit(100).Find(&rows).Error != nil {
fail(c, 500, "读取约遛失败")
return
}
items := make([]gin.H, 0, len(rows))
for _, m := range rows {
items = append(items, a.meetupView(m))
}
ok(c, items)
}
func (a *API) getMeetup(c *gin.Context) {
var m model.Meetup
if !validID(c.Param("id")) || a.db.First(&m, "id = ?", c.Param("id")).Error != nil {
fail(c, 404, "约遛不存在")
return
}
view := a.meetupView(m)
var joins []model.MeetupParticipant
a.db.Where("meetup_id = ?", m.ID).Find(&joins)
people := make([]gin.H, 0, len(joins))
for _, j := range joins {
var pets []model.MeetupPet
a.db.Where("participant_id = ?", j.ID).Find(&pets)
ids := []string{}
for _, p := range pets {
ids = append(ids, p.PetID)
}
people = append(people, gin.H{"user_id": j.UserID, "pet_ids": ids})
}
view["people"] = people
ok(c, view)
}
type meetupInput struct {
Title string `json:"title" binding:"required"`
Description string `json:"description"`
PlaceName string `json:"place_name" binding:"required"`
Latitude float64 `json:"latitude"`
Longitude float64 `json:"longitude"`
StartsAt time.Time `json:"starts_at" binding:"required"`
MaxPeople int `json:"max_people"`
PetIDs []string `json:"pet_ids" binding:"required"`
CoverFileID *string `json:"cover_file_id"`
}
func (a *API) createMeetup(c *gin.Context) {
var in meetupInput
if c.ShouldBindJSON(&in) != nil || strings.TrimSpace(in.Title) == "" || strings.TrimSpace(in.PlaceName) == "" || !in.StartsAt.After(time.Now()) || len(in.PetIDs) == 0 {
fail(c, 400, "约遛资料无效")
return
}
if in.MaxPeople == 0 {
in.MaxPeople = 8
}
if in.MaxPeople < 2 || in.MaxPeople > 100 {
fail(c, 400, "人数应为 2 到 100")
return
}
for _, id := range in.PetIDs {
if _, err := a.ownedPet(actor(c), id); err != nil {
fail(c, 400, "宠物无效")
return
}
}
if in.CoverFileID != nil && !a.fileExists(*in.CoverFileID) {
fail(c, 400, "封面文件无效")
return
}
m := model.Meetup{HostUserID: actor(c), Title: strings.TrimSpace(in.Title), Description: strings.TrimSpace(in.Description), PlaceName: strings.TrimSpace(in.PlaceName), Latitude: in.Latitude, Longitude: in.Longitude, StartsAt: in.StartsAt, MaxPeople: in.MaxPeople, Status: "open", CoverFileID: in.CoverFileID}
err := a.db.Transaction(func(tx *gorm.DB) error {
if e := tx.Create(&m).Error; e != nil {
return e
}
j := model.MeetupParticipant{MeetupID: m.ID, UserID: actor(c)}
if e := tx.Create(&j).Error; e != nil {
return e
}
for _, id := range in.PetIDs {
if e := tx.Create(&model.MeetupPet{ParticipantID: j.ID, PetID: id}).Error; e != nil {
return e
}
}
return nil
})
if err != nil {
fail(c, 500, "发布失败")
return
}
created(c, a.meetupView(m))
}
func (a *API) joinMeetup(c *gin.Context) {
var in struct {
PetIDs []string `json:"pet_ids" binding:"required"`
}
if c.ShouldBindJSON(&in) != nil || len(in.PetIDs) == 0 {
fail(c, 400, "请选择宠物")
return
}
for _, id := range in.PetIDs {
if _, err := a.ownedPet(actor(c), id); err != nil {
fail(c, 400, "宠物无效")
return
}
}
var m model.Meetup
if !validID(c.Param("id")) || a.db.First(&m, "id = ?", c.Param("id")).Error != nil {
fail(c, 404, "约遛不存在")
return
}
if m.Status != "open" || !m.StartsAt.After(time.Now()) {
fail(c, 409, "约遛已关闭")
return
}
var count int64
a.db.Model(&model.MeetupParticipant{}).Where("meetup_id = ?", m.ID).Count(&count)
if count >= int64(m.MaxPeople) {
fail(c, 409, "人数已满")
return
}
var existing int64
a.db.Model(&model.MeetupParticipant{}).Where("meetup_id = ? AND user_id = ?", m.ID, actor(c)).Count(&existing)
if existing > 0 {
fail(c, 409, "已经参加")
return
}
j := model.MeetupParticipant{MeetupID: m.ID, UserID: actor(c)}
err := a.db.Transaction(func(tx *gorm.DB) error {
if e := tx.Create(&j).Error; e != nil {
return e
}
for _, id := range in.PetIDs {
if e := tx.Create(&model.MeetupPet{ParticipantID: j.ID, PetID: id}).Error; e != nil {
return e
}
}
return nil
})
if err != nil {
fail(c, 500, "报名失败")
return
}
ok(c, a.meetupView(m))
}
func (a *API) leaveMeetup(c *gin.Context) {
var j model.MeetupParticipant
if !validID(c.Param("id")) || a.db.Where("meetup_id = ? AND user_id = ?", c.Param("id"), actor(c)).First(&j).Error != nil {
fail(c, 404, "未参加")
return
}
var m model.Meetup
a.db.First(&m, "id = ?", j.MeetupID)
if m.HostUserID == actor(c) {
fail(c, http.StatusConflict, "发起人请取消约遛")
return
}
if a.db.Transaction(func(tx *gorm.DB) error {
if e := tx.Where("participant_id = ?", j.ID).Delete(&model.MeetupPet{}).Error; e != nil {
return e
}
return tx.Delete(&j).Error
}) != nil {
fail(c, 500, "退出失败")
return
}
ok(c, gin.H{"left": true})
}
func (a *API) cancelMeetup(c *gin.Context) {
if !validID(c.Param("id")) {
fail(c, 400, "ID 无效")
return
}
result := a.db.Model(&model.Meetup{}).Where("id = ? AND host_user_id = ? AND status = ?", c.Param("id"), actor(c), "open").Update("status", "cancelled")
if result.Error != nil || result.RowsAffected == 0 {
fail(c, 404, "约遛不存在或已关闭")
return
}
ok(c, gin.H{"status": "cancelled"})
}