129 lines
3.8 KiB
Go
129 lines
3.8 KiB
Go
package logic
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
deliverentity "spark-server/internal/module/deliver/entity"
|
|
"spark-server/internal/module/group/entity"
|
|
msgentity "spark-server/internal/module/message/entity"
|
|
userentity "spark-server/internal/module/user/entity"
|
|
)
|
|
|
|
const memberCacheTTL = 3 * time.Second
|
|
|
|
const cmdMessage uint16 = 6
|
|
|
|
// Repository is group persistence. Model implements it; do not split this by table.
|
|
type Repository interface {
|
|
CreateGroup(ctx context.Context, g *entity.Group, members []*entity.Member) error
|
|
Get(ctx context.Context, id int64) (*entity.Group, error)
|
|
GetByConversation(ctx context.Context, conversationID int64) (*entity.Group, error)
|
|
ListByUser(ctx context.Context, userID int64, limit int) ([]*entity.Group, error)
|
|
ListAllActiveByUser(ctx context.Context, userID int64) ([]*entity.Group, error)
|
|
UpdateProfile(ctx context.Context, g *entity.Group) error
|
|
GetMember(ctx context.Context, groupID, userID int64) (*entity.Member, error)
|
|
ListMembers(ctx context.Context, groupID int64, limit int) ([]*entity.Member, error)
|
|
ListMemberIDs(ctx context.Context, groupID int64) ([]int64, error)
|
|
AddMembers(ctx context.Context, groupID int64, members []*entity.Member) error
|
|
RemoveMember(ctx context.Context, groupID, userID int64) error
|
|
UpdateMember(ctx context.Context, mem *entity.Member) error
|
|
TransferOwner(ctx context.Context, groupID, fromID, toID int64) error
|
|
Dissolve(ctx context.Context, groupID int64) error
|
|
UpdateLastMsg(ctx context.Context, conversationID, msgID, atMS int64) error
|
|
}
|
|
|
|
type Users interface {
|
|
Get(ctx context.Context, id int64) (*userentity.User, error)
|
|
}
|
|
|
|
type Friendships interface {
|
|
AreFriends(ctx context.Context, a, b int64) (bool, error)
|
|
}
|
|
|
|
type IDGen interface {
|
|
NextID() (int64, error)
|
|
}
|
|
|
|
type Messages interface {
|
|
Create(ctx context.Context, m *msgentity.Message) error
|
|
}
|
|
|
|
type DeliverQueue interface {
|
|
Push(ctx context.Context, job *deliverentity.Job) error
|
|
}
|
|
|
|
type Objects interface {
|
|
Get(ctx context.Context, key string) ([]byte, error)
|
|
Put(ctx context.Context, key string, body []byte, contentType string) error
|
|
}
|
|
|
|
type memberCacheEntry struct {
|
|
ids []int64
|
|
at time.Time
|
|
}
|
|
|
|
// Service is the group use-case layer (HTTP and TCP share it).
|
|
type Service struct {
|
|
repo Repository
|
|
users Users
|
|
friends Friendships
|
|
ids IDGen
|
|
msgs Messages
|
|
queue DeliverQueue
|
|
objects Objects
|
|
cache sync.Map
|
|
gate TextGate
|
|
}
|
|
|
|
// TextGate scans user-authored text. Nil skips the check.
|
|
type TextGate interface {
|
|
GateText(ctx context.Context, userID int64, text string) (string, error)
|
|
}
|
|
|
|
// BindModeration wires the shared sensitive-word gate.
|
|
func (s *Service) BindModeration(gate TextGate) {
|
|
if s == nil {
|
|
return
|
|
}
|
|
s.gate = gate
|
|
}
|
|
|
|
func (s *Service) gateText(ctx context.Context, userID int64, text string) (string, error) {
|
|
if s == nil || s.gate == nil || strings.TrimSpace(text) == "" {
|
|
return text, nil
|
|
}
|
|
return s.gate.GateText(ctx, userID, text)
|
|
}
|
|
|
|
func NewService(repo Repository, users Users, friends Friendships, ids IDGen, msgs Messages, queue DeliverQueue, objects Objects) *Service {
|
|
return &Service{repo: repo, users: users, friends: friends, ids: ids, msgs: msgs, queue: queue, objects: objects}
|
|
}
|
|
|
|
func (s *Service) ready() bool {
|
|
return s != nil && s.repo != nil && s.users != nil && s.friends != nil && s.ids != nil
|
|
}
|
|
|
|
func (s *Service) cachedMemberIDs(groupID int64) ([]int64, bool) {
|
|
v, ok := s.cache.Load(groupID)
|
|
if !ok {
|
|
return nil, false
|
|
}
|
|
e, ok := v.(memberCacheEntry)
|
|
if !ok || time.Since(e.at) > memberCacheTTL {
|
|
s.cache.Delete(groupID)
|
|
return nil, false
|
|
}
|
|
return append([]int64(nil), e.ids...), true
|
|
}
|
|
|
|
func (s *Service) putMemberIDs(groupID int64, ids []int64) {
|
|
s.cache.Store(groupID, memberCacheEntry{ids: append([]int64(nil), ids...), at: time.Now()})
|
|
}
|
|
|
|
func (s *Service) invalidateMembers(groupID int64) {
|
|
s.cache.Delete(groupID)
|
|
}
|