spark-server/internal/module/group/model/memory_write.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

150 lines
3.3 KiB
Go

package model
import (
"context"
"time"
"spark-server/internal/module/group/entity"
"spark-server/internal/pkg/errs"
)
func (m *MemoryRepo) CreateGroup(_ context.Context, g *entity.Group, members []*entity.Member) error {
if g == nil || g.ConversationID <= 0 || len(members) == 0 {
return errs.ErrInvalidArgument
}
m.mu.Lock()
defer m.mu.Unlock()
if _, ok := m.byConv[g.ConversationID]; ok {
return errs.ErrDuplicate
}
m.nextID++
cp := *g
cp.ID = m.nextID
cp.MemberCount = len(members)
if cp.Status == 0 {
cp.Status = entity.StatusActive
}
if cp.CreatedAtMS <= 0 {
cp.CreatedAtMS = time.Now().UnixMilli()
}
if cp.UpdatedAtMS <= 0 {
cp.UpdatedAtMS = cp.CreatedAtMS
}
m.groups[cp.ID] = &cp
m.byConv[cp.ConversationID] = cp.ID
g.ID = cp.ID
byUser := make(map[int64]*entity.Member, len(members))
for _, mem := range members {
if mem == nil || mem.UserID <= 0 {
continue
}
row := *mem
row.GroupID = cp.ID
if row.JoinedAtMS <= 0 {
row.JoinedAtMS = cp.CreatedAtMS
}
if row.Role == 0 {
row.Role = entity.RoleMember
}
byUser[row.UserID] = &row
}
m.members[cp.ID] = byUser
return nil
}
func (m *MemoryRepo) AddMembers(_ context.Context, groupID int64, members []*entity.Member) error {
if groupID <= 0 || len(members) == 0 {
return errs.ErrInvalidArgument
}
m.mu.Lock()
defer m.mu.Unlock()
g, ok := m.groups[groupID]
if !ok || g.Status != entity.StatusActive {
return errs.ErrNotFound
}
byUser := m.members[groupID]
if byUser == nil {
byUser = make(map[int64]*entity.Member)
m.members[groupID] = byUser
}
now := time.Now().UnixMilli()
for _, mem := range members {
if mem == nil || mem.UserID <= 0 {
continue
}
if _, ok := byUser[mem.UserID]; ok {
return errs.ErrDuplicate
}
row := *mem
row.GroupID = groupID
if row.JoinedAtMS <= 0 {
row.JoinedAtMS = now
}
if row.Role == 0 {
row.Role = entity.RoleMember
}
byUser[row.UserID] = &row
}
g.MemberCount = len(byUser)
g.UpdatedAtMS = now
return nil
}
func (m *MemoryRepo) RemoveMember(_ context.Context, groupID, userID int64) error {
m.mu.Lock()
defer m.mu.Unlock()
g, ok := m.groups[groupID]
if !ok || g.Status != entity.StatusActive {
return errs.ErrNotFound
}
byUser := m.members[groupID]
if byUser == nil {
return errs.ErrNotFound
}
if _, ok := byUser[userID]; !ok {
return errs.ErrNotFound
}
delete(byUser, userID)
g.MemberCount = len(byUser)
g.UpdatedAtMS = time.Now().UnixMilli()
return nil
}
func (m *MemoryRepo) Dissolve(_ context.Context, groupID int64) error {
m.mu.Lock()
defer m.mu.Unlock()
g, ok := m.groups[groupID]
if !ok || g.Status != entity.StatusActive {
return errs.ErrNotFound
}
g.Status = entity.StatusDissolved
g.MemberCount = 0
g.UpdatedAtMS = time.Now().UnixMilli()
delete(m.members, groupID)
return nil
}
func (m *MemoryRepo) TransferOwner(_ context.Context, groupID, fromID, toID int64) error {
m.mu.Lock()
defer m.mu.Unlock()
g, ok := m.groups[groupID]
if !ok || g.Status != entity.StatusActive || g.OwnerID != fromID {
return errs.ErrNotFound
}
byUser := m.members[groupID]
from, ok := byUser[fromID]
if !ok {
return errs.ErrNotFound
}
to, ok := byUser[toID]
if !ok {
return errs.ErrNotFound
}
from.Role = entity.RoleMember
to.Role = entity.RoleOwner
to.SpeakMuted = false
g.OwnerID = toID
g.UpdatedAtMS = time.Now().UnixMilli()
return nil
}