591 lines
21 KiB
Go
591 lines
21 KiB
Go
package handler
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
"regexp"
|
|
"strings"
|
|
"time"
|
|
|
|
"past/internal/geo"
|
|
"past/internal/httpapi"
|
|
"past/internal/store"
|
|
)
|
|
|
|
var targetIDRe = regexp.MustCompile(`^520\d{3,}$`)
|
|
|
|
func registerCouple(r *httpapi.Router) {
|
|
r.Handle("generate_invite_link", generateInviteLink)
|
|
r.Handle("check_invite_data_status", checkInviteDataStatus)
|
|
r.Handle("check_invite_code", checkInviteCode)
|
|
r.Handle("invite_by_phone", inviteByPhone)
|
|
r.Handle("invite_partner", invitePartner)
|
|
r.Handle("my_invitations", myInvitations)
|
|
r.Handle("respond_invitation", respondInvitation)
|
|
r.Handle("accept_share_invite", acceptShareInvite)
|
|
r.Handle("couple_info", coupleInfo)
|
|
r.Handle("update_couple_info", updateCoupleInfo)
|
|
r.Handle("unbind_request", unbindRequest)
|
|
r.Handle("unbind_respond", unbindRespond)
|
|
r.Handle("unbind_cancel", unbindCancel)
|
|
r.Handle("unbind_status", unbindStatus)
|
|
r.Handle("unbind_couple", unbindCouple)
|
|
}
|
|
|
|
// parseDate 对应 new DateTime($str)(仅支持常见格式)
|
|
func parseDate(s string) (time.Time, error) {
|
|
s = strings.TrimSpace(s)
|
|
for _, layout := range []string{"2006-01-02 15:04:05", "2006-01-02", "2006/01/02", "2006-1-2", "2006.01.02"} {
|
|
if t, err := time.ParseInLocation(layout, s, time.Local); err == nil {
|
|
return t, nil
|
|
}
|
|
}
|
|
return time.Time{}, fmt.Errorf("DateTime::__construct(): Failed to parse time string (%s)", s)
|
|
}
|
|
|
|
// diffDays 对应 DateTime::diff()->days(整天数,绝对值)
|
|
func diffDays(a, b time.Time) int64 {
|
|
d := a.Sub(b)
|
|
if d < 0 {
|
|
d = -d
|
|
}
|
|
return int64(math.Floor(d.Hours() / 24))
|
|
}
|
|
|
|
// birthdayCountdown 按日历日计算下一次生日。生日当天仍是 0 天,不会因为当天零点已过而跳到下一年。
|
|
func birthdayCountdown(nowT, bday time.Time, currentAge int64) (time.Time, int64, int64) {
|
|
today := time.Date(nowT.Year(), nowT.Month(), nowT.Day(), 0, 0, 0, 0, nowT.Location())
|
|
next := time.Date(today.Year(), bday.Month(), bday.Day(), 0, 0, 0, 0, nowT.Location())
|
|
if next.Before(today) {
|
|
next = next.AddDate(1, 0, 0)
|
|
}
|
|
// 用 UTC 中的日历日做差,避免当前时分秒导致“明天生日却显示 0 天”。
|
|
todayDate := time.Date(today.Year(), today.Month(), today.Day(), 0, 0, 0, 0, time.UTC)
|
|
nextDate := time.Date(next.Year(), next.Month(), next.Day(), 0, 0, 0, 0, time.UTC)
|
|
daysLeft := int64(nextDate.Sub(todayDate) / (24 * time.Hour))
|
|
return next, daysLeft, currentAge + 1
|
|
}
|
|
|
|
func generateInviteLink(c *httpapi.Ctx) error {
|
|
if err := c.RequirePOST(); err != nil {
|
|
return err
|
|
}
|
|
uid := c.UID()
|
|
user, _ := c.DB.QueryOne("SELECT couple_id FROM users WHERE id = ?", uid)
|
|
if user != nil && user.Int("couple_id") != 0 {
|
|
return c.Err(400, "您已绑定情侣")
|
|
}
|
|
if ex, _ := c.DB.QueryOne("SELECT code FROM invite_links WHERE user_id = ? AND used = 0 ORDER BY created_at DESC LIMIT 1", uid); ex != nil {
|
|
return c.OK(M{"success": true, "code": ex.Str("code"), "url": "?invite=" + ex.Str("code")})
|
|
}
|
|
code := randomHex(8)
|
|
if _, err := c.DB.Exec("INSERT INTO invite_links (user_id, code) VALUES (?, ?)", uid, code); err != nil {
|
|
return err
|
|
}
|
|
return c.OK(M{"success": true, "code": code, "url": "?invite=" + code})
|
|
}
|
|
|
|
func checkInviteDataStatus(c *httpapi.Ctx) error {
|
|
uid := c.UID()
|
|
cp, _ := c.DB.QueryOne("SELECT c.id, c.data_deleted, c.status FROM couples c WHERE (c.user1_id = ? OR c.user2_id = ?) AND c.status = 'unbound' AND c.data_deleted = 0 LIMIT 1", uid, uid)
|
|
if cp != nil {
|
|
return c.OK(M{"has_pending_data": true, "msg": "请等待系统删除之前的情侣数据后再进行绑定操作"})
|
|
}
|
|
return c.OK(M{"has_pending_data": false})
|
|
}
|
|
|
|
func checkInviteCode(c *httpapi.Ctx) error {
|
|
code := strings.TrimSpace(c.Q("code"))
|
|
if code == "" {
|
|
return c.Err(400, "缺少邀请码")
|
|
}
|
|
link, err := c.DB.QueryOne("SELECT il.*, u.nickname, u.avatar FROM invite_links il JOIN users u ON il.user_id = u.id WHERE il.code = ? AND il.used = 0", code)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if link == nil {
|
|
return c.Err(404, "邀请链接无效或已使用")
|
|
}
|
|
// PHP 中 $link['inviter_couple_id'] ?? $link['couple_id'] 两个键都不存在,判断恒为假,此处保持一致
|
|
return c.OK(M{"success": true, "inviter": M{"nickname": link["nickname"], "avatar": link["avatar"]}})
|
|
}
|
|
|
|
func inviteByPhone(c *httpapi.Ctx) error {
|
|
if err := c.RequirePOST(); err != nil {
|
|
return err
|
|
}
|
|
uid := c.UID()
|
|
target := c.InTrim("username")
|
|
if !c.InHas("username") {
|
|
target = c.InTrim("phone")
|
|
}
|
|
message := c.InTrim("message")
|
|
if target == "" {
|
|
return c.Err(400, "请输入对方用户名")
|
|
}
|
|
if pass, msg := c.App.Wechat.CheckText(message, userOpenId(c.DB, uid)); !pass {
|
|
return c.Err(400, msg)
|
|
}
|
|
me, _ := c.DB.QueryOne("SELECT username, couple_id FROM users WHERE id = ?", uid)
|
|
if me != nil && me.Int("couple_id") != 0 {
|
|
return c.Err(400, "您已绑定情侣")
|
|
}
|
|
if me != nil && me.Str("username") == target {
|
|
return c.Err(400, "不能邀请自己")
|
|
}
|
|
t, _ := c.DB.QueryOne("SELECT id, username, couple_id FROM users WHERE username = ?", target)
|
|
if t == nil {
|
|
return c.Err(400, "该用户不存在")
|
|
}
|
|
if t.Int("couple_id") != 0 {
|
|
return c.Err(400, "对方已绑定情侣")
|
|
}
|
|
if ex, _ := c.DB.QueryOne("SELECT id FROM invitations WHERE from_user_id = ? AND to_user_id = ? AND status = 'pending'", uid, t.Int("id")); ex != nil {
|
|
return c.Err(400, "已发送过邀请,等待对方回应")
|
|
}
|
|
if _, err := c.DB.Exec("INSERT INTO invitations (from_user_id, to_user_id, to_phone, message, type) VALUES (?, ?, ?, ?, 'username')", uid, t.Int("id"), target, message); err != nil {
|
|
return err
|
|
}
|
|
return c.OK(M{"success": true, "msg": "邀请已发送"})
|
|
}
|
|
|
|
func invitePartner(c *httpapi.Ctx) error {
|
|
if err := c.RequirePOST(); err != nil {
|
|
return err
|
|
}
|
|
uid := c.UID()
|
|
targetID := c.InTrim("target_user_id")
|
|
message := c.InTrim("message")
|
|
if targetID == "" {
|
|
return c.Err(400, "请输入对方ID")
|
|
}
|
|
if !targetIDRe.MatchString(targetID) {
|
|
return c.Err(400, "ID格式不正确,请输入6位数字ID")
|
|
}
|
|
me, _ := c.DB.QueryOne("SELECT user_id, couple_id, gender FROM users WHERE id = ?", uid)
|
|
if me != nil && me.Int("couple_id") != 0 {
|
|
return c.Err(400, "您已绑定情侣")
|
|
}
|
|
if me != nil && me.Str("user_id") == targetID {
|
|
return c.Err(400, "不能绑定自己")
|
|
}
|
|
if ex, _ := c.DB.QueryOne("SELECT c.id FROM couples c WHERE (c.user1_id = ? OR c.user2_id = ?) AND c.status = 'unbound' AND c.data_deleted = 0 LIMIT 1", uid, uid); ex != nil {
|
|
return c.Err(400, "请等待系统删除之前的情侣数据后再进行绑定操作")
|
|
}
|
|
t, _ := c.DB.QueryOne("SELECT id, user_id, couple_id, nickname, gender FROM users WHERE user_id = ?", targetID)
|
|
if t == nil {
|
|
return c.Err(400, "该ID不存在,请确认对方ID是否正确")
|
|
}
|
|
if t.Int("couple_id") != 0 {
|
|
return c.Err(400, "对方已绑定情侣")
|
|
}
|
|
if ex, _ := c.DB.QueryOne("SELECT id FROM invitations WHERE from_user_id = ? AND to_user_id = ? AND status = 'pending'", uid, t.Int("id")); ex != nil {
|
|
return c.Err(400, "已发送过邀请,等待对方回应")
|
|
}
|
|
id, err := c.DB.Insert("INSERT INTO invitations (from_user_id, to_user_id, message, type) VALUES (?, ?, ?, 'id')", uid, t.Int("id"), message)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return c.OK(M{"success": true, "invitation_id": fmt.Sprint(id), "msg": "邀请已发送,等待对方同意"})
|
|
}
|
|
|
|
func myInvitations(c *httpapi.Ctx) error {
|
|
uid := c.UID()
|
|
user, _ := c.DB.QueryOne("SELECT couple_id FROM users WHERE id = ?", uid)
|
|
var coupleID int64
|
|
if user != nil {
|
|
coupleID = user.Int("couple_id")
|
|
}
|
|
received, err := c.DB.Query("SELECT i.*, u.username as from_username, u.nickname as from_nickname, u.avatar as from_avatar FROM invitations i JOIN users u ON i.from_user_id = u.id WHERE i.to_user_id = ? ORDER BY i.created_at DESC", uid)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
sent, err := c.DB.Query("SELECT i.*, u.username as to_username, u.nickname as to_nickname, u.avatar as to_avatar FROM invitations i JOIN users u ON i.to_user_id = u.id WHERE i.from_user_id = ? ORDER BY i.created_at DESC", uid)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
links, err := c.DB.Query("SELECT * FROM invite_links WHERE user_id = ? ORDER BY created_at DESC", uid)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return c.OK(M{"received": rows(received), "sent": rows(sent), "links": rows(links), "couple_id": coupleID})
|
|
}
|
|
|
|
// bindCouple 创建情侣关系(事务):couples 记录、空间 ID、双方 couple_id;返回 coupleId
|
|
func bindCouple(c *httpapi.Ctx, user1, user2 int64, extra func(tx *store.Tx) error) (int64, error) {
|
|
tx, err := c.DB.Begin()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
defer tx.Rollback()
|
|
coupleID, err := tx.Insert("INSERT INTO couples (user1_id, user2_id) VALUES (?, ?)", user1, user2)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
if _, err := tx.Exec("UPDATE couples SET couple_space_id = ? WHERE id = ?", generateCoupleSpaceId(coupleID), coupleID); err != nil {
|
|
return 0, err
|
|
}
|
|
if _, err := tx.Exec("UPDATE users SET couple_id = ?, updated_at = CURRENT_TIMESTAMP WHERE id IN (?, ?)", coupleID, user1, user2); err != nil {
|
|
return 0, err
|
|
}
|
|
if extra != nil {
|
|
if err := extra(tx); err != nil {
|
|
return 0, err
|
|
}
|
|
}
|
|
if err := tx.Commit(); err != nil {
|
|
return 0, err
|
|
}
|
|
ensureUserDirs(user1)
|
|
ensureUserDirs(user2)
|
|
return coupleID, nil
|
|
}
|
|
|
|
func respondInvitation(c *httpapi.Ctx) error {
|
|
if err := c.RequirePOST(); err != nil {
|
|
return err
|
|
}
|
|
uid := c.UID()
|
|
invID := c.InInt("invitation_id")
|
|
accept := c.InBool("accept")
|
|
inv, err := c.DB.QueryOne("SELECT * FROM invitations WHERE id = ? AND to_user_id = ? AND status = 'pending'", invID, uid)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if inv == nil {
|
|
return c.Err(404, "邀请不存在或已处理")
|
|
}
|
|
fromID := inv.Int("from_user_id")
|
|
rs, _ := c.DB.Query("SELECT couple_id FROM users WHERE id IN (?, ?)", uid, fromID)
|
|
for _, r := range rs {
|
|
if r.Int("couple_id") != 0 {
|
|
return c.Err(400, "已绑定情侣,无法接受")
|
|
}
|
|
}
|
|
if accept {
|
|
coupleID, err := bindCouple(c, fromID, uid, func(tx *store.Tx) error {
|
|
_, err := tx.Exec("UPDATE invitations SET status = 'accepted', responded_at = CURRENT_TIMESTAMP WHERE id = ?", invID)
|
|
return err
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
createDefaultAlbums(c.DB, coupleID, uid)
|
|
return c.OK(M{"success": true, "couple_id": fmt.Sprint(coupleID)})
|
|
}
|
|
c.DB.Exec("UPDATE invitations SET status = 'rejected', responded_at = CURRENT_TIMESTAMP WHERE id = ?", invID)
|
|
return c.OK(M{"success": true, "msg": "已拒绝邀请"})
|
|
}
|
|
|
|
func acceptShareInvite(c *httpapi.Ctx) error {
|
|
if err := c.RequirePOST(); err != nil {
|
|
return err
|
|
}
|
|
uid := c.UID()
|
|
inviterDisplayID := c.InTrim("inviter_id")
|
|
if inviterDisplayID == "" {
|
|
return c.Err(400, "缺少邀请人ID")
|
|
}
|
|
me, _ := c.DB.QueryOne("SELECT user_id, couple_id, gender, nickname FROM users WHERE id = ?", uid)
|
|
if me != nil && me.Int("couple_id") != 0 {
|
|
return c.Err(400, "您已绑定情侣")
|
|
}
|
|
inviter, _ := c.DB.QueryOne("SELECT id, user_id, couple_id, gender, nickname FROM users WHERE user_id = ?", inviterDisplayID)
|
|
if inviter == nil {
|
|
return c.Err(400, "邀请人不存在")
|
|
}
|
|
if inviter.Int("couple_id") != 0 {
|
|
return c.Err(400, "邀请人已绑定情侣")
|
|
}
|
|
if me != nil && me.Str("user_id") == inviter.Str("user_id") {
|
|
return c.Err(400, "不能绑定自己")
|
|
}
|
|
if me != nil && me.Str("gender") != "" && inviter.Str("gender") != "" && me.Str("gender") == inviter.Str("gender") {
|
|
return c.Err(400, "双方性别一致,仅支持异性绑定")
|
|
}
|
|
inviterID := inviter.Int("id")
|
|
existing, _ := c.DB.QueryOne("SELECT id FROM invitations WHERE ((from_user_id = ? AND to_user_id = ?) OR (from_user_id = ? AND to_user_id = ?)) AND status = 'pending'", uid, inviterID, inviterID, uid)
|
|
if existing != nil {
|
|
c.DB.Exec("UPDATE invitations SET status = 'accepted', responded_at = CURRENT_TIMESTAMP WHERE id = ?", existing.Int("id"))
|
|
}
|
|
coupleID, err := bindCouple(c, uid, inviterID, func(tx *store.Tx) error {
|
|
if existing == nil {
|
|
_, err := tx.Exec("INSERT INTO invitations (from_user_id, to_user_id, status, responded_at, type) VALUES (?, ?, 'accepted', CURRENT_TIMESTAMP, 'share')", inviterID, uid)
|
|
return err
|
|
}
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
createDefaultAlbums(c.DB, coupleID, uid)
|
|
return c.OK(M{"success": true, "couple_id": fmt.Sprint(coupleID), "partner_nickname": inviter["nickname"]})
|
|
}
|
|
|
|
func coupleInfo(c *httpapi.Ctx) error {
|
|
cid := c.CoupleID()
|
|
uid := c.UID()
|
|
if cid == 0 {
|
|
user, _ := c.DB.QueryOne("SELECT id, user_id, username, nickname, avatar, gender, age, birthday, created_at FROM users WHERE id = ?", uid)
|
|
regDate := today()
|
|
if user != nil {
|
|
ca := user.Str("created_at")
|
|
if len(ca) >= 10 {
|
|
regDate = ca[:10]
|
|
} else {
|
|
regDate = ca
|
|
}
|
|
}
|
|
var days int64
|
|
if regDate != "" {
|
|
t, err := parseDate(regDate)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
days = diffDays(time.Now(), t)
|
|
}
|
|
var p1 any
|
|
if user != nil {
|
|
p1 = user
|
|
}
|
|
return c.OK(M{"not_bound": true, "partner1": p1, "partner2": nil, "love_start_date": regDate, "days_together": days})
|
|
}
|
|
info, err := c.DB.QueryOne("SELECT * FROM couples WHERE id = ?", cid)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if info == nil {
|
|
info = store.Row{}
|
|
}
|
|
partners, err := c.DB.Query("SELECT id, user_id, username, nickname, avatar, gender, age, birthday, region, location, lat, lng FROM users WHERE couple_id = ? ORDER BY id ASC", cid)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
info["partner1"] = nil
|
|
info["partner2"] = nil
|
|
if len(partners) > 0 {
|
|
info["partner1"] = partners[0]
|
|
}
|
|
if len(partners) > 1 {
|
|
info["partner2"] = partners[1]
|
|
}
|
|
info["distance"] = nil
|
|
if len(partners) >= 2 {
|
|
lat1, lng1 := store.ToFloat(partners[0]["lat"]), store.ToFloat(partners[0]["lng"])
|
|
lat2, lng2 := store.ToFloat(partners[1]["lat"]), store.ToFloat(partners[1]["lng"])
|
|
if lat1 != 0 && lng1 != 0 && lat2 != 0 && lng2 != 0 {
|
|
info["distance"] = geo.Haversine(lat1, lng1, lat2, lng2)
|
|
}
|
|
}
|
|
var partner store.Row
|
|
for _, p := range partners {
|
|
if p.Int("id") != uid {
|
|
partner = p
|
|
}
|
|
}
|
|
if partner != nil && partner.Str("birthday") != "" {
|
|
bday, err := parseDate(partner.Str("birthday"))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
next, daysLeft, ageNext := birthdayCountdown(time.Now(), bday, partner.Int("age"))
|
|
info["partner_birthday_countdown"] = M{
|
|
"birthday": partner["birthday"],
|
|
"next_date": next.Format("2006-01-02"),
|
|
"days_left": daysLeft,
|
|
"age_next": ageNext,
|
|
}
|
|
}
|
|
if store.Truthy(info["love_start_date"]) {
|
|
start, err := parseDate(info.Str("love_start_date"))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
info["days_together"] = diffDays(start, time.Now())
|
|
} else {
|
|
info["days_together"] = 0
|
|
}
|
|
return c.OK(info)
|
|
}
|
|
|
|
func updateCoupleInfo(c *httpapi.Ctx) error {
|
|
if err := c.RequirePOST(); err != nil {
|
|
return err
|
|
}
|
|
cid := c.CoupleID()
|
|
if cid == 0 {
|
|
return c.Err(400, "请先绑定情侣")
|
|
}
|
|
uid := c.UID()
|
|
if pass, msg := c.App.Wechat.CheckText(c.InTrim("love_story"), userOpenId(c.DB, uid)); !pass {
|
|
return c.Err(400, msg)
|
|
}
|
|
if _, err := c.DB.Exec("UPDATE couples SET love_start_date = ?, love_story = ? WHERE id = ?", c.InStr("love_start_date"), c.InStr("love_story"), cid); err != nil {
|
|
return err
|
|
}
|
|
if store.Truthy(c.InStr("love_start_date")) {
|
|
logActivity(c.DB, cid, uid, "couple_edit", "设置了恋爱纪念日", "")
|
|
}
|
|
return c.OK(M{"success": true})
|
|
}
|
|
|
|
func userCoupleID(c *httpapi.Ctx, uid int64) int64 {
|
|
u, _ := c.DB.QueryOne("SELECT couple_id FROM users WHERE id = ?", uid)
|
|
if u == nil {
|
|
return 0
|
|
}
|
|
return u.Int("couple_id")
|
|
}
|
|
|
|
func unbindRequest(c *httpapi.Ctx) error {
|
|
if err := c.RequirePOST(); err != nil {
|
|
return err
|
|
}
|
|
uid := c.UID()
|
|
cid := userCoupleID(c, uid)
|
|
if cid == 0 {
|
|
return c.Err(400, "未绑定情侣")
|
|
}
|
|
existing, _ := c.DB.QueryOne("SELECT id, status, requester_id, created_at FROM unbind_requests WHERE couple_id = ? AND status IN ('pending','agreed') ORDER BY id DESC LIMIT 1", cid)
|
|
if existing != nil {
|
|
if existing.Str("status") == "pending" {
|
|
return c.Err(400, "已有待处理的解绑请求,请等待对方回应")
|
|
}
|
|
if existing.Str("status") == "agreed" {
|
|
softUnbind(c.DB, cid)
|
|
return c.OK(M{"success": true, "msg": "对方已同意解绑,情侣关系已解除,所有数据已保留"})
|
|
}
|
|
return nil
|
|
}
|
|
if _, err := c.DB.Exec("INSERT INTO unbind_requests (couple_id, requester_id, status, created_at) VALUES (?, ?, 'pending', NOW())", cid, uid); err != nil {
|
|
return err
|
|
}
|
|
logActivity(c.DB, cid, uid, "unbind_request", "发起了解除情侣绑定请求", "")
|
|
return c.OK(M{"success": true, "msg": "解绑请求已发送,等待对方确认(72小时内有效)"})
|
|
}
|
|
|
|
func unbindRespond(c *httpapi.Ctx) error {
|
|
if err := c.RequirePOST(); err != nil {
|
|
return err
|
|
}
|
|
uid := c.UID()
|
|
// $input = json_decode(...) ?: $_POST
|
|
agree := c.InBool("agree")
|
|
if len(c.Input()) == 0 {
|
|
agree = store.Truthy(c.Form("agree"))
|
|
}
|
|
cid := userCoupleID(c, uid)
|
|
if cid == 0 {
|
|
return c.Err(400, "未绑定情侣")
|
|
}
|
|
req, _ := c.DB.QueryOne("SELECT id, requester_id, created_at FROM unbind_requests WHERE couple_id = ? AND status = 'pending' AND requester_id != ? ORDER BY id DESC LIMIT 1", cid, uid)
|
|
if req == nil {
|
|
return c.Err(400, "没有待处理的解绑请求")
|
|
}
|
|
if created, err := parseDate(req.Str("created_at")); err == nil && time.Since(created) > 72*time.Hour {
|
|
c.DB.Exec("UPDATE unbind_requests SET status = 'expired', responded_at = NOW() WHERE id = ?", req.Int("id"))
|
|
return c.Err(400, "解绑请求已超过72小时,已自动取消")
|
|
}
|
|
if agree {
|
|
c.DB.Exec("UPDATE unbind_requests SET status = 'agreed', responded_at = NOW() WHERE id = ?", req.Int("id"))
|
|
logActivity(c.DB, cid, uid, "unbind_agreed", "同意了解除情侣绑定请求", "")
|
|
softUnbind(c.DB, cid)
|
|
return c.OK(M{"success": true, "msg": "已同意解绑,情侣关系已解除,所有数据已保留"})
|
|
}
|
|
c.DB.Exec("UPDATE unbind_requests SET status = 'rejected', responded_at = NOW() WHERE id = ?", req.Int("id"))
|
|
logActivity(c.DB, cid, uid, "unbind_rejected", "拒绝了解除情侣绑定请求", "")
|
|
return c.OK(M{"success": true, "msg": "已拒绝解绑请求"})
|
|
}
|
|
|
|
func unbindCancel(c *httpapi.Ctx) error {
|
|
if err := c.RequirePOST(); err != nil {
|
|
return err
|
|
}
|
|
uid := c.UID()
|
|
cid := userCoupleID(c, uid)
|
|
if cid == 0 {
|
|
return c.Err(400, "未绑定情侣")
|
|
}
|
|
req, _ := c.DB.QueryOne("SELECT id, status FROM unbind_requests WHERE couple_id = ? AND requester_id = ? AND status IN ('pending','agreed') ORDER BY id DESC LIMIT 1", cid, uid)
|
|
if req == nil {
|
|
return c.Err(400, "没有可撤销的解绑请求")
|
|
}
|
|
c.DB.Exec("UPDATE unbind_requests SET status = 'cancelled', responded_at = NOW() WHERE id = ?", req.Int("id"))
|
|
logActivity(c.DB, cid, uid, "unbind_cancelled", "撤销了解除情侣绑定请求", "")
|
|
return c.OK(M{"success": true, "msg": "已撤销解绑请求"})
|
|
}
|
|
|
|
func unbindStatus(c *httpapi.Ctx) error {
|
|
uid := c.UID()
|
|
// $cid 来源不同类型不同:来自 users 行为字符串;来自 unbind_requests 反查时经过 intval 为整数
|
|
var cidVal any
|
|
u, _ := c.DB.QueryOne("SELECT couple_id FROM users WHERE id = ?", uid)
|
|
cid := int64(0)
|
|
if u != nil && u.Int("couple_id") != 0 {
|
|
cid, cidVal = u.Int("couple_id"), u["couple_id"]
|
|
}
|
|
if cid == 0 {
|
|
found, _ := c.DB.QueryOne("SELECT ur.couple_id FROM unbind_requests ur WHERE ur.requester_id = ? OR ur.id IN (SELECT ur2.id FROM unbind_requests ur2 JOIN couples c ON ur2.couple_id = c.id WHERE (c.user1_id = ? OR c.user2_id = ?)) ORDER BY ur.id DESC LIMIT 1", uid, uid, uid)
|
|
if found != nil {
|
|
cid, cidVal = found.Int("couple_id"), found.Int("couple_id")
|
|
}
|
|
}
|
|
if cid == 0 {
|
|
return c.OK(M{"status": "none", "msg": "未绑定情侣"})
|
|
}
|
|
req, err := c.DB.QueryOne("SELECT id, requester_id, status, created_at, responded_at FROM unbind_requests WHERE couple_id = ? ORDER BY id DESC LIMIT 1", cid)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
dataDeleted := int64(0)
|
|
if cd, _ := c.DB.QueryOne("SELECT data_deleted FROM couples WHERE id = ?", cid); cd != nil {
|
|
dataDeleted = cd.Int("data_deleted")
|
|
}
|
|
if req == nil {
|
|
return c.OK(M{"status": "none", "msg": "无解绑请求", "couple_id": cidVal, "data_deleted": dataDeleted})
|
|
}
|
|
if req.Str("status") == "pending" {
|
|
created, _ := parseDate(req.Str("created_at"))
|
|
elapsed := int64(time.Since(created).Seconds())
|
|
remaining := int64(72*3600) - elapsed
|
|
if remaining < 0 {
|
|
remaining = 0
|
|
}
|
|
if elapsed > 72*3600 {
|
|
c.DB.Exec("UPDATE unbind_requests SET status = 'expired', responded_at = NOW() WHERE id = ?", req.Int("id"))
|
|
req["status"] = "expired"
|
|
} else {
|
|
req["remaining_seconds"] = remaining
|
|
}
|
|
}
|
|
req["is_requester"] = req.Int("requester_id") == uid
|
|
requester, _ := c.DB.QueryOne("SELECT nickname, username FROM users WHERE id = ?", req.Int("requester_id"))
|
|
name := "未知"
|
|
if requester != nil {
|
|
name = requester.Str("nickname")
|
|
if name == "" {
|
|
name = requester.Str("username")
|
|
}
|
|
}
|
|
req["requester_name"] = name
|
|
req["couple_id"] = cid
|
|
req["data_deleted"] = dataDeleted
|
|
return c.OK(req)
|
|
}
|
|
|
|
func unbindCouple(c *httpapi.Ctx) error {
|
|
if err := c.RequirePOST(); err != nil {
|
|
return err
|
|
}
|
|
uid := c.UID()
|
|
cid := userCoupleID(c, uid)
|
|
if cid == 0 {
|
|
return c.Err(400, "未绑定情侣")
|
|
}
|
|
if ag, _ := c.DB.QueryOne("SELECT id FROM unbind_requests WHERE couple_id = ? AND status = 'agreed' ORDER BY id DESC LIMIT 1", cid); ag == nil {
|
|
return c.Err(400, "没有已确认的解绑请求,请先发起解绑")
|
|
}
|
|
softUnbind(c.DB, cid)
|
|
return c.OK(M{"success": true, "msg": "已解除绑定"})
|
|
}
|