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>
150 lines
3.3 KiB
Go
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
|
|
}
|