owpengram-server/internal/store/memory/star_gift.go

178 lines
4.3 KiB
Go

package memory
import (
"context"
"sort"
"sync"
"telesrv/internal/domain"
)
// StarGiftStore 是 store.StarGiftStore 的内存实现。
type StarGiftStore struct {
mu sync.Mutex
nextID int64
gifts []domain.SavedStarGift // 追加序
}
// NewStarGiftStore 创建内存 StarGiftStore。
func NewStarGiftStore() *StarGiftStore {
return &StarGiftStore{}
}
func (s *StarGiftStore) Create(_ context.Context, gift domain.SavedStarGift) (int64, error) {
if !validSavedStarGift(gift) {
return 0, domain.ErrStarGiftInvalid
}
s.mu.Lock()
defer s.mu.Unlock()
s.nextID++
gift.ID = s.nextID
if gift.Owner.Type == domain.PeerTypeChannel && gift.SavedID == 0 {
gift.SavedID = gift.ID
}
gift.Converted = false
s.gifts = append(s.gifts, gift)
return gift.ID, nil
}
func (s *StarGiftStore) ListByOwner(_ context.Context, owner domain.Peer, excludeUnsaved bool, offset string, limit int) (domain.SavedStarGiftPage, error) {
if !validStarGiftOwner(owner) {
return domain.SavedStarGiftPage{}, nil
}
if limit <= 0 || limit > domain.MaxSavedStarGiftsLimit {
limit = domain.MaxSavedStarGiftsLimit
}
s.mu.Lock()
defer s.mu.Unlock()
matched := make([]domain.SavedStarGift, 0)
for _, g := range s.gifts {
if g.Owner != owner || g.Converted {
continue
}
if excludeUnsaved && g.Unsaved {
continue
}
matched = append(matched, g)
}
sort.Slice(matched, func(i, j int) bool { return matched[i].ID > matched[j].ID })
page := domain.SavedStarGiftPage{Count: len(matched)}
cursor, hasCursor := domain.DecodeStarGiftCursor(offset)
out := make([]domain.SavedStarGift, 0, limit)
for _, g := range matched {
if hasCursor && g.ID >= cursor {
continue
}
out = append(out, g)
if len(out) == limit {
break
}
}
if len(out) == limit {
// 还有更早的则给下一页游标。
last := out[len(out)-1].ID
for _, g := range matched {
if g.ID < last {
page.NextOffset = domain.EncodeStarGiftCursor(last)
break
}
}
}
page.Gifts = out
return page, nil
}
func (s *StarGiftStore) GetByRef(_ context.Context, ref domain.SavedStarGiftRef) (domain.SavedStarGift, bool, error) {
if !ref.Valid() {
return domain.SavedStarGift{}, false, nil
}
s.mu.Lock()
defer s.mu.Unlock()
for _, g := range s.gifts {
if savedStarGiftMatchesRef(g, ref) {
return g, true, nil
}
}
return domain.SavedStarGift{}, false, nil
}
func (s *StarGiftStore) CountByOwner(_ context.Context, owner domain.Peer) (int, error) {
if !validStarGiftOwner(owner) {
return 0, nil
}
s.mu.Lock()
defer s.mu.Unlock()
n := 0
for _, g := range s.gifts {
if g.Owner == owner && !g.Converted && !g.Unsaved {
n++
}
}
return n, nil
}
func (s *StarGiftStore) SetUnsaved(_ context.Context, ref domain.SavedStarGiftRef, unsaved bool) (bool, error) {
if !ref.Valid() {
return false, domain.ErrStarGiftNotFound
}
s.mu.Lock()
defer s.mu.Unlock()
for i := range s.gifts {
if savedStarGiftMatchesRef(s.gifts[i], ref) && !s.gifts[i].Converted {
s.gifts[i].Unsaved = unsaved
return true, nil
}
}
return false, nil
}
func (s *StarGiftStore) MarkConverted(_ context.Context, ref domain.SavedStarGiftRef) (domain.SavedStarGift, error) {
if !ref.Valid() {
return domain.SavedStarGift{}, domain.ErrStarGiftNotFound
}
s.mu.Lock()
defer s.mu.Unlock()
for i := range s.gifts {
if savedStarGiftMatchesRef(s.gifts[i], ref) {
if s.gifts[i].Converted {
return domain.SavedStarGift{}, domain.ErrStarGiftAlreadyConverted
}
s.gifts[i].Converted = true
s.gifts[i].Unsaved = true
return s.gifts[i], nil
}
}
return domain.SavedStarGift{}, domain.ErrStarGiftNotFound
}
func validSavedStarGift(g domain.SavedStarGift) bool {
if g.GiftID == 0 || !validStarGiftOwner(g.Owner) {
return false
}
switch g.Owner.Type {
case domain.PeerTypeUser:
return g.MsgID > 0 && g.SavedID == 0
case domain.PeerTypeChannel:
return g.MsgID == 0 && g.SavedID >= 0
default:
return false
}
}
func validStarGiftOwner(owner domain.Peer) bool {
return owner.ID != 0 && (owner.Type == domain.PeerTypeUser || owner.Type == domain.PeerTypeChannel)
}
func savedStarGiftMatchesRef(g domain.SavedStarGift, ref domain.SavedStarGiftRef) bool {
if g.Owner != ref.Owner {
return false
}
switch ref.Owner.Type {
case domain.PeerTypeUser:
return g.MsgID == ref.MsgID
case domain.PeerTypeChannel:
return g.SavedID == ref.SavedID
default:
return false
}
}