支持 square/squarenovel/squaresteps/squarehealth 实例与 category 筛选;音乐增加免登录 grouped/track;订阅套餐按 package_name 区分;补 migration 0030/0031 与 OpenAPI。 Co-authored-by: Cursor <cursoragent@cursor.com>
219 lines
5.1 KiB
Go
219 lines
5.1 KiB
Go
package model
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"time"
|
|
|
|
"spark-server/internal/module/square/entity"
|
|
"spark-server/internal/pkg/errs"
|
|
)
|
|
|
|
func (m *MemoryRepo) CreatePost(_ context.Context, post *entity.Post, audience []int64) error {
|
|
if post == nil || post.ID <= 0 || post.UserID <= 0 {
|
|
return errs.ErrInvalidArgument
|
|
}
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
if _, ok := m.posts[post.ID]; ok {
|
|
return errs.ErrDuplicate
|
|
}
|
|
cp := clonePost(post)
|
|
if cp.Channel == "" {
|
|
cp.Channel = entity.ChannelSquare
|
|
}
|
|
m.posts[post.ID] = cp
|
|
if len(audience) > 0 {
|
|
m.audience[post.ID] = append([]int64(nil), audience...)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (m *MemoryRepo) CreateBotPost(_ 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
|
|
}
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
if _, ok := m.botLinks[url]; ok {
|
|
return errs.ErrDuplicate
|
|
}
|
|
if _, ok := m.posts[post.ID]; ok {
|
|
return errs.ErrDuplicate
|
|
}
|
|
m.botLinks[url] = post.ID
|
|
m.posts[post.ID] = clonePost(post)
|
|
return nil
|
|
}
|
|
|
|
func (m *MemoryRepo) SoftDeleteExpiredBotPosts(_ context.Context, nowMS int64, limit int) ([]*entity.Post, error) {
|
|
if limit <= 0 {
|
|
limit = 100
|
|
}
|
|
if nowMS <= 0 {
|
|
nowMS = time.Now().UnixMilli()
|
|
}
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
out := make([]*entity.Post, 0)
|
|
for _, row := range m.posts {
|
|
if row.Status != entity.StatusNormal || !row.Source.IsBot() {
|
|
continue
|
|
}
|
|
if !row.Expired(nowMS) {
|
|
continue
|
|
}
|
|
row.Status = entity.StatusDeleted
|
|
out = append(out, clonePost(row))
|
|
if len(out) >= limit {
|
|
break
|
|
}
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func (m *MemoryRepo) SoftDeletePost(_ context.Context, userID, postID int64) (*entity.Post, error) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
row, ok := m.posts[postID]
|
|
if !ok || row.UserID != userID || row.Status == entity.StatusDeleted {
|
|
return nil, errs.ErrNotFound
|
|
}
|
|
row.Status = entity.StatusDeleted
|
|
return clonePost(row), nil
|
|
}
|
|
|
|
func (m *MemoryRepo) AddLike(_ context.Context, like *entity.Like) (bool, error) {
|
|
if like == nil || like.PostID <= 0 || like.UserID <= 0 {
|
|
return false, errs.ErrInvalidArgument
|
|
}
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
post, ok := m.posts[like.PostID]
|
|
if !ok {
|
|
return false, errs.ErrNotFound
|
|
}
|
|
key := likeKey{PostID: like.PostID, UserID: like.UserID}
|
|
if _, ok := m.likes[key]; ok {
|
|
return false, nil
|
|
}
|
|
cp := *like
|
|
if cp.CreatedAtMS == 0 {
|
|
cp.CreatedAtMS = time.Now().UnixMilli()
|
|
}
|
|
m.likes[key] = &cp
|
|
post.LikeCount++
|
|
return true, nil
|
|
}
|
|
|
|
func (m *MemoryRepo) RemoveLike(_ context.Context, postID, userID int64) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
key := likeKey{PostID: postID, UserID: userID}
|
|
if _, ok := m.likes[key]; !ok {
|
|
return errs.ErrNotFound
|
|
}
|
|
delete(m.likes, key)
|
|
if post, ok := m.posts[postID]; ok && post.LikeCount > 0 {
|
|
post.LikeCount--
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (m *MemoryRepo) AddComment(_ context.Context, c *entity.Comment) error {
|
|
if c == nil || c.ID <= 0 || c.PostID <= 0 || c.UserID <= 0 {
|
|
return errs.ErrInvalidArgument
|
|
}
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
post, ok := m.posts[c.PostID]
|
|
if !ok {
|
|
return errs.ErrNotFound
|
|
}
|
|
if _, ok := m.comments[c.ID]; ok {
|
|
return errs.ErrDuplicate
|
|
}
|
|
m.comments[c.ID] = cloneComment(c)
|
|
post.CommentCount++
|
|
return nil
|
|
}
|
|
|
|
func (m *MemoryRepo) SoftDeleteComment(_ context.Context, id int64) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
row, ok := m.comments[id]
|
|
if !ok || row.Status == entity.StatusDeleted {
|
|
return errs.ErrNotFound
|
|
}
|
|
row.Status = entity.StatusDeleted
|
|
if post, ok := m.posts[row.PostID]; ok && post.CommentCount > 0 {
|
|
post.CommentCount--
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (m *MemoryRepo) CreateNotices(_ context.Context, rows []*entity.Notice) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
for _, n := range rows {
|
|
if n == nil || n.ID <= 0 {
|
|
return errs.ErrInvalidArgument
|
|
}
|
|
if _, ok := m.notices[n.ID]; ok {
|
|
return errs.ErrDuplicate
|
|
}
|
|
cp := *n
|
|
if cp.Channel == "" {
|
|
cp.Channel = entity.ChannelSquare
|
|
}
|
|
m.notices[n.ID] = &cp
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (m *MemoryRepo) MarkNoticesRead(_ context.Context, userID int64, channel entity.Channel, cursor int64) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
ch := memChannel(channel)
|
|
now := time.Now().UnixMilli()
|
|
for _, n := range m.notices {
|
|
if n.ToUserID != userID || memChannel(n.Channel) != ch || n.ReadAtMS > 0 {
|
|
continue
|
|
}
|
|
if cursor > 0 && n.ID > cursor {
|
|
continue
|
|
}
|
|
n.ReadAtMS = now
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (m *MemoryRepo) AdvanceNoticeRead(_ context.Context, userID int64, channel entity.Channel, noticeID int64) error {
|
|
if userID <= 0 || noticeID <= 0 {
|
|
return errs.ErrInvalidArgument
|
|
}
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
ch := memChannel(channel)
|
|
key := noticeStateKey{UserID: userID, Channel: ch}
|
|
cur := m.noticeState[key]
|
|
if cur == nil {
|
|
cur = entity.DefaultNoticeState(userID, ch)
|
|
m.noticeState[key] = cur
|
|
}
|
|
if cur.LastReadNoticeID >= noticeID {
|
|
return nil
|
|
}
|
|
cur.LastReadNoticeID = noticeID
|
|
cur.UpdatedAtMS = time.Now().UnixMilli()
|
|
return nil
|
|
}
|