Files
past-go/past-go/internal/handler/period_source.go
T
Blizzard 4934a69581 feat: migrate to PostgreSQL GORM and MinIO file storage
Use UUID models and soft deletion, persist file relationships with deduplicated monthly MinIO objects, and add admin file management. Update mini-program media handling and add local dev configuration.
2026-11-01 11:48:45 +08:00

148 lines
4.8 KiB
Go

package handler
import (
"fmt"
"past/internal/httpapi"
"past/internal/store"
)
// 绑定前的姨姨记录可能双方都有。只允许一份成为情侣的当前记录;
// 在用户选择前保留两份,选择时在同一事务中删除另一份并迁入当前情侣空间。
func periodSourceState(c *httpapi.Ctx) (required bool, me, partner string, err error) {
cid, uid := c.CoupleID(), c.UID()
if cid == "" {
return false, uid, "", nil
}
couple, err := c.DB.QueryOne("SELECT user1_id, user2_id, period_source_user_id FROM couples WHERE id = ?", cid)
if err != nil || couple == nil {
return false, "", "", err
}
if couple.Str("user1_id") == uid {
partner = couple.Str("user2_id")
} else if couple.Str("user2_id") == uid {
partner = couple.Str("user1_id")
} else {
return false, "", "", fmt.Errorf("账号不属于当前情侣")
}
if couple.Str("period_source_user_id") != "" {
return false, uid, partner, nil
}
myCount, err := periodSourceCount(c.DB, uid)
if err != nil {
return false, "", "", err
}
partnerCount, err := periodSourceCount(c.DB, partner)
if err != nil {
return false, "", "", err
}
if myCount > 0 && partnerCount > 0 {
return true, uid, partner, nil
}
if myCount > 0 {
err = selectPeriodSource(c, uid)
} else if partnerCount > 0 {
err = selectPeriodSource(c, partner)
}
return false, uid, partner, err
}
func periodSourceCount(db store.Querier, uid string) (int64, error) {
row, err := db.QueryOne(`SELECT
(SELECT COUNT(*) FROM period_records WHERE user_id = ? AND couple_id IS NULL) +
(SELECT COUNT(*) FROM period_daily_records WHERE user_id = ? AND couple_id IS NULL) +
(SELECT COUNT(*) FROM period_sex_records WHERE user_id = ? AND couple_id IS NULL) AS n`,
uid, uid, uid)
if err != nil {
return 0, err
}
return row.Int("n"), nil
}
func periodSourcePreview(db store.Querier, uid string) (M, error) {
row, err := db.QueryOne("SELECT COUNT(*) AS cycles, MAX(start_date) AS latest FROM period_records WHERE user_id = ? AND couple_id IS NULL", uid)
if err != nil {
return nil, err
}
total, err := periodSourceCount(db, uid)
if err != nil {
return nil, err
}
return M{"cycles": row.Int("cycles"), "latest": row.Str("latest"), "items": total}, nil
}
func selectPeriodSource(c *httpapi.Ctx, keepUserID string) error {
cid := c.CoupleID()
if cid == "" {
return c.Err(400, "未绑定伴侣")
}
tx, err := c.DB.Begin()
if err != nil {
return err
}
defer tx.Rollback()
couple, err := tx.QueryOne("SELECT user1_id, user2_id, period_source_user_id FROM couples WHERE id = ? FOR UPDATE", cid)
if err != nil {
return err
}
if couple == nil || (keepUserID != couple.Str("user1_id") && keepUserID != couple.Str("user2_id")) {
return c.Err(400, "记录来源不是当前情侣成员")
}
if existing := couple.Str("period_source_user_id"); existing != "" {
if existing == keepUserID {
return nil
}
return c.Err(409, "姨姨记录来源已经选定")
}
deleteUserID := couple.Str("user1_id")
if deleteUserID == keepUserID {
deleteUserID = couple.Str("user2_id")
}
for _, table := range []string{"period_records", "period_daily_records", "period_sex_records"} {
if _, err := tx.Exec("DELETE FROM "+table+" WHERE user_id = ? AND couple_id IS NULL", deleteUserID); err != nil {
return err
}
}
// daily 的旧 NULL 记录可能有重复日期,逐条迁入以避免情侣日期唯一索引冲突。
daily, err := tx.Query("SELECT id, record_date FROM period_daily_records WHERE user_id = ? AND couple_id IS NULL ORDER BY created_at DESC, id DESC FOR UPDATE", keepUserID)
if err != nil {
return err
}
for _, record := range daily {
existing, err := tx.QueryOne("SELECT id FROM period_daily_records WHERE couple_id = ? AND record_date = ? LIMIT 1", cid, record["record_date"])
if err != nil {
return err
}
if existing != nil {
if _, err := tx.Exec("DELETE FROM period_daily_records WHERE id = ?", record.Str("id")); err != nil {
return err
}
} else if _, err := tx.Exec("UPDATE period_daily_records SET couple_id = ? WHERE id = ?", cid, record.Str("id")); err != nil {
return err
}
}
for _, table := range []string{"period_records", "period_sex_records"} {
if _, err := tx.Exec("UPDATE "+table+" SET couple_id = ? WHERE user_id = ? AND couple_id IS NULL", cid, keepUserID); err != nil {
return err
}
}
if _, err := tx.Exec("UPDATE couples SET period_source_user_id = ? WHERE id = ?", keepUserID, cid); err != nil {
return err
}
return tx.Commit()
}
func periodSourceSelect(c *httpapi.Ctx) error {
if err := c.RequirePOST(); err != nil {
return err
}
if err := requirePeriodAccess(c); err != nil {
return err
}
keepUserID := c.InTrim("user_id")
if err := selectPeriodSource(c, keepUserID); err != nil {
return err
}
return c.OK(M{"success": true, "source_user_id": keepUserID})
}