package model import ( "context" "time" "gorm.io/gorm" "gorm.io/gorm/clause" "spark-server/internal/module/square/entity" "spark-server/internal/pkg/errs" ) func (m *Repo) CreatePost(ctx context.Context, post *entity.Post, audience []int64) error { if post == nil || post.ID <= 0 || post.UserID <= 0 { return errs.ErrInvalidArgument } db, err := m.session(ctx) if err != nil { return err } row := postFrom(post) if row.CreatedAt.IsZero() { row.CreatedAt = time.Now() } return db.Transaction(func(tx *gorm.DB) error { if err := tx.Create(row).Error; err != nil { return mapDBErr(err) } for _, peer := range audience { if err := tx.Create(&Audience{PostID: post.ID, PeerID: peer}).Error; err != nil { return mapDBErr(err) } } return nil }) } func (m *Repo) SoftDeletePost(ctx context.Context, userID, postID int64) (*entity.Post, error) { db, err := m.session(ctx) if err != nil { return nil, err } var row Post err = db.Transaction(func(tx *gorm.DB) error { if err := tx.Where("id = ? AND user_id = ?", postID, userID).Take(&row).Error; err != nil { return mapDBErr(err) } if row.Status == int16(entity.StatusDeleted) { return errs.ErrNotFound } res := tx.Model(&row).Update("status", entity.StatusDeleted) if res.Error != nil { return mapDBErr(res.Error) } if res.RowsAffected == 0 { return errs.ErrNotFound } row.Status = int16(entity.StatusDeleted) return nil }) if err != nil { return nil, err } return row.Entity(), nil } func (m *Repo) AddLike(ctx context.Context, like *entity.Like) (bool, error) { if like == nil || like.PostID <= 0 || like.UserID <= 0 { return false, errs.ErrInvalidArgument } db, err := m.session(ctx) if err != nil { return false, err } row := Like{PostID: like.PostID, UserID: like.UserID, CreatedAt: time.Now()} if like.CreatedAtMS > 0 { row.CreatedAt = time.UnixMilli(like.CreatedAtMS) } created := false err = db.Transaction(func(tx *gorm.DB) error { res := tx.Clauses(clause.OnConflict{ Columns: []clause.Column{{Name: "post_id"}, {Name: "user_id"}}, DoNothing: true, }).Create(&row) if res.Error != nil { return mapDBErr(res.Error) } if res.RowsAffected == 0 { return nil } created = true return bumpCount(tx, like.PostID, "like_count", 1) }) return created, err } func (m *Repo) RemoveLike(ctx context.Context, postID, userID int64) error { db, err := m.session(ctx) if err != nil { return err } return db.Transaction(func(tx *gorm.DB) error { res := tx.Where("post_id = ? AND user_id = ?", postID, userID).Delete(&Like{}) if res.Error != nil { return mapDBErr(res.Error) } if res.RowsAffected == 0 { return errs.ErrNotFound } return bumpCount(tx, postID, "like_count", -1) }) } func (m *Repo) AddComment(ctx context.Context, c *entity.Comment) error { if c == nil || c.ID <= 0 || c.PostID <= 0 || c.UserID <= 0 { return errs.ErrInvalidArgument } db, err := m.session(ctx) if err != nil { return err } row := commentFrom(c) if row.CreatedAt.IsZero() { row.CreatedAt = time.Now() } return db.Transaction(func(tx *gorm.DB) error { if err := tx.Create(row).Error; err != nil { return mapDBErr(err) } return bumpCount(tx, c.PostID, "comment_count", 1) }) } func (m *Repo) SoftDeleteComment(ctx context.Context, id int64) error { db, err := m.session(ctx) if err != nil { return err } return db.Transaction(func(tx *gorm.DB) error { var row Comment if err := tx.Where("id = ?", id).Take(&row).Error; err != nil { return mapDBErr(err) } if row.Status == int16(entity.StatusDeleted) { return errs.ErrNotFound } if err := tx.Model(&row).Update("status", entity.StatusDeleted).Error; err != nil { return mapDBErr(err) } return bumpCount(tx, row.PostID, "comment_count", -1) }) } func (m *Repo) CreateNotices(ctx context.Context, rows []*entity.Notice) error { if len(rows) == 0 { return nil } db, err := m.session(ctx) if err != nil { return err } now := time.Now() for _, n := range rows { row := Notice{ ID: n.ID, ToUserID: n.ToUserID, Channel: normalizeChannel(n.Channel), FromUserID: n.FromUserID, Kind: int16(n.Kind), PostID: n.PostID, CommentID: n.CommentID, CreatedAt: now, } if n.CreatedAtMS > 0 { row.CreatedAt = time.UnixMilli(n.CreatedAtMS) } if err := db.Create(&row).Error; err != nil { return mapDBErr(err) } } return nil } func (m *Repo) MarkNoticesRead(ctx context.Context, userID int64, channel entity.Channel, cursor int64) error { db, err := m.session(ctx) if err != nil { return err } ch := normalizeChannel(channel) q := db.Model(&Notice{}).Where("to_user_id = ? AND channel = ? AND read_at IS NULL", userID, ch) if cursor > 0 { q = q.Where("id <= ?", cursor) } return mapDBErr(q.Update("read_at", time.Now()).Error) } func (m *Repo) AdvanceNoticeRead(ctx context.Context, userID int64, channel entity.Channel, noticeID int64) error { if userID <= 0 || noticeID <= 0 { return errs.ErrInvalidArgument } db, err := m.session(ctx) if err != nil { return err } ch := entity.Channel(normalizeChannel(channel)) cur, err := m.GetNoticeState(ctx, userID, ch) if err != nil { return err } if cur.LastReadNoticeID >= noticeID { return nil } cur.LastReadNoticeID = noticeID cur.UpdatedAtMS = time.Now().UnixMilli() row := NoticeState{ UserID: cur.UserID, Channel: string(ch), LastReadNoticeID: cur.LastReadNoticeID, UpdatedAt: time.UnixMilli(cur.UpdatedAtMS), } return mapDBErr(db.Clauses(clause.OnConflict{ Columns: []clause.Column{{Name: "user_id"}, {Name: "channel"}}, DoUpdates: clause.AssignmentColumns([]string{"last_read_notice_id", "updated_at"}), }).Create(&row).Error) } func bumpCount(tx *gorm.DB, postID int64, col string, delta int) error { expr := col + " + ?" if delta < 0 { expr = "CASE WHEN " + col + " + ? < 0 THEN 0 ELSE " + col + " + ? END" return mapDBErr(tx.Model(&Post{}).Where("id = ?", postID).Update(col, gorm.Expr(expr, delta, delta)).Error) } return mapDBErr(tx.Model(&Post{}).Where("id = ?", postID).Update(col, gorm.Expr(expr, delta)).Error) }