package model import ( "context" "crypto/sha256" "encoding/hex" "strings" "time" "gorm.io/gorm" "gorm.io/gorm/clause" "spark-server/internal/module/square/entity" "spark-server/internal/pkg/errs" ) // CreateBotPost inserts a bot RSS post and claims its source URL atomically. func (m *Repo) CreateBotPost(ctx context.Context, post *entity.Post) error { if post == nil || post.ID <= 0 || post.UserID <= 0 { return errs.ErrInvalidArgument } url := strings.TrimSpace(post.SourceURL) if url == "" { return errs.ErrInvalidArgument } post.SourceURL = url if post.Source == 0 { post.Source = entity.SourceBotRSS } db, err := m.session(ctx) if err != nil { return err } row := postFrom(post) if row.CreatedAt.IsZero() { row.CreatedAt = time.Now() } hash := urlHash(url) return db.Transaction(func(tx *gorm.DB) error { link := BotLink{URLHash: hash, URL: url, PostID: post.ID, CreatedAt: row.CreatedAt} res := tx.Clauses(clause.OnConflict{DoNothing: true}).Create(&link) if res.Error != nil { return mapDBErr(res.Error) } if res.RowsAffected == 0 { return errs.ErrDuplicate } if err := tx.Create(row).Error; err != nil { return mapDBErr(err) } return nil }) } // SoftDeleteExpiredBotPosts marks expired bot posts deleted; returns deleted rows for media GC. func (m *Repo) SoftDeleteExpiredBotPosts(ctx context.Context, nowMS int64, limit int) ([]*entity.Post, error) { if limit <= 0 { limit = 100 } if nowMS <= 0 { nowMS = time.Now().UnixMilli() } db, err := m.session(ctx) if err != nil { return nil, err } deadline := time.UnixMilli(nowMS) var rows []Post err = db.Transaction(func(tx *gorm.DB) error { q := tx.Where( "source = ? AND status = ? AND expires_at IS NOT NULL AND expires_at <= ?", entity.SourceBotRSS, entity.StatusNormal, deadline, ).Order("expires_at ASC").Limit(limit) if err := q.Find(&rows).Error; err != nil { return mapDBErr(err) } if len(rows) == 0 { return nil } ids := make([]int64, 0, len(rows)) for i := range rows { ids = append(ids, rows[i].ID) } res := tx.Model(&Post{}).Where("id IN ? AND status = ?", ids, entity.StatusNormal). Update("status", entity.StatusDeleted) if res.Error != nil { return mapDBErr(res.Error) } for i := range rows { rows[i].Status = int16(entity.StatusDeleted) } return nil }) if err != nil { return nil, err } return collectPosts(rows), nil } func urlHash(url string) string { sum := sha256.Sum256([]byte(strings.TrimSpace(url))) return hex.EncodeToString(sum[:]) }