package model import ( "context" "time" "gorm.io/gorm" conventity "spark-server/internal/module/conversation/entity" convmodel "spark-server/internal/module/conversation/model" "spark-server/internal/module/group/entity" "spark-server/internal/pkg/errs" ) func (m *Repo) CreateGroup(ctx context.Context, g *entity.Group, members []*entity.Member) error { if g == nil || g.ConversationID <= 0 || len(members) == 0 { return errs.ErrInvalidArgument } db, err := m.session(ctx) if err != nil { return err } now := msTime(g.CreatedAtMS) return db.Transaction(func(tx *gorm.DB) error { conv := convmodel.Conversation{ ID: g.ConversationID, Type: conventity.TypeGroup, LastMsgAt: now, CreatedAt: now, } if err := tx.Create(&conv).Error; err != nil { return mapDBErr(err) } g.ID = 0 g.MemberCount = len(members) row := groupRow(g) if err := tx.Create(row).Error; err != nil { return mapDBErr(err) } g.ID = row.ID for _, mem := range members { if mem == nil { continue } mem.GroupID = g.ID if err := insertMember(tx, mem, g.ConversationID, now); err != nil { return err } } return nil }) } func (m *Repo) AddMembers(ctx context.Context, groupID int64, members []*entity.Member) error { if groupID <= 0 || len(members) == 0 { return errs.ErrInvalidArgument } db, err := m.session(ctx) if err != nil { return err } now := time.Now() return db.Transaction(func(tx *gorm.DB) error { g, err := takeActiveGroup(tx, groupID) if err != nil { return err } for _, mem := range members { if mem == nil { continue } mem.GroupID = groupID if err := insertMember(tx, mem, g.ConversationID, now); err != nil { return err } } return bumpMemberCount(tx, groupID, len(members), now) }) } func (m *Repo) RemoveMember(ctx context.Context, groupID, userID int64) error { if groupID <= 0 || userID <= 0 { return errs.ErrInvalidArgument } db, err := m.session(ctx) if err != nil { return err } now := time.Now() return db.Transaction(func(tx *gorm.DB) error { g, err := takeActiveGroup(tx, groupID) if err != nil { return err } res := tx.Where("group_id = ? AND user_id = ?", groupID, userID).Delete(&GroupMember{}) if res.Error != nil { return mapDBErr(res.Error) } if res.RowsAffected == 0 { return errs.ErrNotFound } if err := tx.Where("conversation_id = ? AND user_id = ?", g.ConversationID, userID). Delete(&convmodel.ConversationMember{}).Error; err != nil { return mapDBErr(err) } return bumpMemberCount(tx, groupID, -1, now) }) } func (m *Repo) Dissolve(ctx context.Context, groupID int64) error { if groupID <= 0 { return errs.ErrInvalidArgument } db, err := m.session(ctx) if err != nil { return err } now := time.Now() return db.Transaction(func(tx *gorm.DB) error { g, err := takeActiveGroup(tx, groupID) if err != nil { return err } res := tx.Model(&Group{}).Where("id = ? AND status = ?", groupID, entity.StatusActive).Updates(map[string]any{ "status": entity.StatusDissolved, "member_count": 0, "updated_at": now, }) if res.Error != nil { return mapDBErr(res.Error) } if res.RowsAffected == 0 { return errs.ErrNotFound } if err := tx.Where("group_id = ?", groupID).Delete(&GroupMember{}).Error; err != nil { return mapDBErr(err) } if err := tx.Where("conversation_id = ?", g.ConversationID).Delete(&convmodel.ConversationMember{}).Error; err != nil { return mapDBErr(err) } return nil }) } func (m *Repo) TransferOwner(ctx context.Context, groupID, fromID, toID int64) error { if groupID <= 0 || fromID <= 0 || toID <= 0 || fromID == toID { return errs.ErrInvalidArgument } db, err := m.session(ctx) if err != nil { return err } now := time.Now() return db.Transaction(func(tx *gorm.DB) error { res := tx.Model(&Group{}).Where("id = ? AND owner_id = ? AND status = ?", groupID, fromID, entity.StatusActive). Updates(map[string]any{"owner_id": toID, "updated_at": now}) if res.Error != nil { return mapDBErr(res.Error) } if res.RowsAffected == 0 { return errs.ErrNotFound } if err := tx.Model(&GroupMember{}).Where("group_id = ? AND user_id = ?", groupID, fromID). Update("role", entity.RoleMember).Error; err != nil { return mapDBErr(err) } res = tx.Model(&GroupMember{}).Where("group_id = ? AND user_id = ?", groupID, toID). Updates(map[string]any{"role": entity.RoleOwner, "speak_muted": 0}) if res.Error != nil { return mapDBErr(res.Error) } if res.RowsAffected == 0 { return errs.ErrNotFound } return nil }) } func takeActiveGroup(tx *gorm.DB, groupID int64) (*Group, error) { var row Group if err := tx.Where("id = ? AND status = ?", groupID, entity.StatusActive).Take(&row).Error; err != nil { return nil, mapDBErr(err) } return &row, nil } func insertMember(tx *gorm.DB, mem *entity.Member, conversationID int64, now time.Time) error { if mem.JoinedAtMS <= 0 { mem.JoinedAtMS = now.UnixMilli() } if err := tx.Create(memberRow(mem)).Error; err != nil { return mapDBErr(err) } row := convmodel.ConversationMember{ ConversationID: conversationID, UserID: mem.UserID, JoinedAt: msTime(mem.JoinedAtMS), } return mapDBErr(tx.Create(&row).Error) } func bumpMemberCount(tx *gorm.DB, groupID int64, delta int, now time.Time) error { res := tx.Model(&Group{}).Where("id = ? AND status = ?", groupID, entity.StatusActive). Updates(map[string]any{ "member_count": gorm.Expr("member_count + ?", delta), "updated_at": now, }) if res.Error != nil { return mapDBErr(res.Error) } if res.RowsAffected == 0 { return errs.ErrNotFound } return nil }