package logic import ( "context" "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 } 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) }