spark-server/internal/module/friend/model/memory.go
zhr ae9a71034d feat: implement Spark IM V3 modular monolith
Add HTTP /v1 MVC, TCP send path, groups with server-side member-avatar collage, object-storage upload, and friends/favorites around the architecture-v3 layout.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-17 19:52:11 +08:00

154 lines
3.4 KiB
Go

package model
import (
"context"
"sort"
"sync"
"time"
"spark-server/internal/module/friend/entity"
"spark-server/internal/pkg/errs"
)
type pairKey struct {
Small int64
Big int64
}
type requestKey struct {
From int64
To int64
}
type remarkKey struct {
UserID int64
FriendID int64
}
type blockKey struct {
UserID int64
BlockedID int64
}
// MemoryRepo is an in-process friend store for tests and empty-DSN bring-up.
type MemoryRepo struct {
mu sync.Mutex
relations map[pairKey]*entity.Relation
requests map[int64]*entity.Request
byPair map[requestKey]int64
remarks map[remarkKey]string
blocks map[blockKey]time.Time
reports []*entity.Report
}
func NewMemoryRepo() *MemoryRepo {
return &MemoryRepo{
relations: make(map[pairKey]*entity.Relation),
requests: make(map[int64]*entity.Request),
byPair: make(map[requestKey]int64),
remarks: make(map[remarkKey]string),
blocks: make(map[blockKey]time.Time),
}
}
func (m *MemoryRepo) GetRelation(_ context.Context, a, b int64) (*entity.Relation, error) {
m.mu.Lock()
defer m.mu.Unlock()
rel, ok := m.relations[pairOf(a, b)]
if !ok {
return nil, errs.ErrNotFound
}
cp := *rel
return &cp, nil
}
func (m *MemoryRepo) ListFriends(_ context.Context, userID int64, limit int) ([]*entity.Relation, error) {
m.mu.Lock()
defer m.mu.Unlock()
limit = entity.ClampLimit(limit)
out := make([]*entity.Relation, 0)
for _, rel := range m.relations {
if rel.UserSmall == userID || rel.UserBig == userID {
cp := *rel
out = append(out, &cp)
}
}
sort.Slice(out, func(i, j int) bool { return out[i].CreatedAt.After(out[j].CreatedAt) })
if len(out) > limit {
out = out[:limit]
}
return out, nil
}
func (m *MemoryRepo) GetRequest(_ context.Context, id int64) (*entity.Request, error) {
m.mu.Lock()
defer m.mu.Unlock()
req, ok := m.requests[id]
if !ok {
return nil, errs.ErrNotFound
}
cp := *req
return &cp, nil
}
func (m *MemoryRepo) GetRequestByPair(_ context.Context, from, to int64) (*entity.Request, error) {
m.mu.Lock()
defer m.mu.Unlock()
id, ok := m.byPair[requestKey{From: from, To: to}]
if !ok {
return nil, errs.ErrNotFound
}
cp := *m.requests[id]
return &cp, nil
}
func (m *MemoryRepo) UpsertRequest(_ context.Context, req *entity.Request) error {
if req == nil || req.FromUser <= 0 || req.ToUser <= 0 {
return errs.ErrInvalidArgument
}
m.mu.Lock()
defer m.mu.Unlock()
key := requestKey{From: req.FromUser, To: req.ToUser}
if id, ok := m.byPair[key]; ok {
cur := m.requests[id]
cur.Source = req.Source
cur.Reason = req.Reason
cur.Remark = req.Remark
cur.Status = req.Status
cur.UpdatedAt = req.UpdatedAt
*req = *cur
return nil
}
cp := *req
m.requests[req.ID] = &cp
m.byPair[key] = req.ID
return nil
}
func (m *MemoryRepo) ListIncoming(_ context.Context, toUserID int64, status, limit int) ([]*entity.Request, error) {
m.mu.Lock()
defer m.mu.Unlock()
limit = entity.ClampLimit(limit)
out := make([]*entity.Request, 0)
for _, req := range m.requests {
if req.ToUser != toUserID {
continue
}
if status > 0 && req.Status != status {
continue
}
cp := *req
out = append(out, &cp)
}
sort.Slice(out, func(i, j int) bool { return out[i].UpdatedAt.After(out[j].UpdatedAt) })
if len(out) > limit {
out = out[:limit]
}
return out, nil
}
func pairOf(a, b int64) pairKey {
small, big := entity.OrderUsers(a, b)
return pairKey{Small: small, Big: big}
}