104 lines
2.5 KiB
Go
104 lines
2.5 KiB
Go
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[:])
|
|
}
|