package model import ( "context" "time" "spark-server/internal/module/group/entity" "spark-server/internal/pkg/errs" ) // Group maps table groups. type Group struct { ID int64 `gorm:"column:id;primaryKey;autoIncrement;comment:群号,表内自增,对外群 ID"` ConversationID int64 `gorm:"column:conversation_id;uniqueIndex;comment:对应 conversations.id(Snowflake),发消息与同步用"` Name string `gorm:"column:name;comment:群名称"` Avatar string `gorm:"column:avatar;comment:群头像对象存储 key,空表示未设置"` Announcement string `gorm:"column:announcement;comment:群公告,仅群主/管理员可改"` OwnerID int64 `gorm:"column:owner_id;comment:群主 user_id"` MutedAll int `gorm:"column:muted_all;comment:0=未全员禁言 1=全员禁言(群主/管理员仍可发言)"` MemberCount int `gorm:"column:member_count;comment:当前成员数,含群主"` Status int `gorm:"column:status;comment:1=正常 2=已解散"` CreatedAt time.Time `gorm:"column:created_at;comment:创建时间"` UpdatedAt time.Time `gorm:"column:updated_at;comment:资料或成员变更时间"` } func (Group) TableName() string { return "groups" } func (r *Group) entity() *entity.Group { if r == nil { return nil } g := &entity.Group{ ID: r.ID, ConversationID: r.ConversationID, Name: r.Name, Avatar: r.Avatar, Announcement: r.Announcement, OwnerID: r.OwnerID, MemberCount: r.MemberCount, Status: entity.Status(r.Status), CreatedAtMS: r.CreatedAt.UnixMilli(), UpdatedAtMS: r.UpdatedAt.UnixMilli(), } g.MutedAll = r.MutedAll != 0 return g } func groupRow(g *entity.Group) *Group { now := msTime(g.CreatedAtMS) updated := msTime(g.UpdatedAtMS) if g.UpdatedAtMS <= 0 { updated = now } row := &Group{ ID: g.ID, ConversationID: g.ConversationID, Name: g.Name, Avatar: g.Avatar, Announcement: g.Announcement, OwnerID: g.OwnerID, MemberCount: g.MemberCount, Status: int(g.Status), CreatedAt: now, UpdatedAt: updated, } if g.MutedAll { row.MutedAll = 1 } if row.Status == 0 { row.Status = int(entity.StatusActive) } return row } func (m *Repo) Get(ctx context.Context, id int64) (*entity.Group, error) { if id <= 0 { return nil, errs.ErrInvalidArgument } db, err := m.session(ctx) if err != nil { return nil, err } var row Group if err := db.Where("id = ?", id).Take(&row).Error; err != nil { return nil, mapDBErr(err) } return row.entity(), nil } func (m *Repo) GetByConversation(ctx context.Context, conversationID int64) (*entity.Group, error) { if conversationID <= 0 { return nil, errs.ErrInvalidArgument } db, err := m.session(ctx) if err != nil { return nil, err } var row Group if err := db.Where("conversation_id = ?", conversationID).Take(&row).Error; err != nil { return nil, mapDBErr(err) } return row.entity(), nil } func (m *Repo) ListByUser(ctx context.Context, userID int64, limit int) ([]*entity.Group, error) { if userID <= 0 { return nil, errs.ErrInvalidArgument } db, err := m.session(ctx) if err != nil { return nil, err } limit = entity.ClampLimit(limit) var rows []Group err = db.Table("groups AS g").Select("g.*"). Joins("INNER JOIN group_members m ON m.group_id = g.id"). Where("m.user_id = ? AND g.status = ?", userID, entity.StatusActive). Order("g.updated_at DESC").Limit(limit).Find(&rows).Error if err != nil { return nil, mapDBErr(err) } out := make([]*entity.Group, 0, len(rows)) for i := range rows { out = append(out, rows[i].entity()) } return out, nil } const purgeListCap = 10000 func (m *Repo) ListAllActiveByUser(ctx context.Context, userID int64) ([]*entity.Group, error) { if userID <= 0 { return nil, errs.ErrInvalidArgument } db, err := m.session(ctx) if err != nil { return nil, err } var rows []Group err = db.Table("groups AS g").Select("g.*"). Joins("INNER JOIN group_members m ON m.group_id = g.id"). Where("m.user_id = ? AND g.status = ?", userID, entity.StatusActive). Order("g.updated_at DESC").Limit(purgeListCap).Find(&rows).Error if err != nil { return nil, mapDBErr(err) } out := make([]*entity.Group, 0, len(rows)) for i := range rows { out = append(out, rows[i].entity()) } return out, nil } func (m *Repo) UpdateProfile(ctx context.Context, g *entity.Group) error { if g == nil || g.ID <= 0 { return errs.ErrInvalidArgument } db, err := m.session(ctx) if err != nil { return err } res := db.Model(&Group{}).Where("id = ? AND status = ?", g.ID, entity.StatusActive).Updates(map[string]any{ "name": g.Name, "avatar": g.Avatar, "announcement": g.Announcement, "muted_all": boolInt(g.MutedAll), "updated_at": msTime(g.UpdatedAtMS), }) if res.Error != nil { return mapDBErr(res.Error) } if res.RowsAffected == 0 { return errs.ErrNotFound } return nil } func (m *Repo) UpdateLastMsg(ctx context.Context, conversationID, msgID, atMS int64) error { db, err := m.session(ctx) if err != nil { return err } res := db.Table("conversations").Where("id = ?", conversationID).Updates(map[string]any{ "last_msg_id": msgID, "last_msg_at": msTime(atMS), }) if res.Error != nil { return mapDBErr(res.Error) } if res.RowsAffected == 0 { return errs.ErrNotFound } return nil } func msTime(ms int64) time.Time { if ms <= 0 { return time.Now() } return time.UnixMilli(ms) } func boolInt(v bool) int { if v { return 1 } return 0 }