feat: persist group business state in tables

This commit is contained in:
richjohnrambo
2026-08-17 09:50:31 +08:00
parent d8d1639d5b
commit ddf0f7a55e
10 changed files with 2065 additions and 130 deletions
+221
View File
@@ -1079,6 +1079,226 @@ func (callsMapper) List(tenantID types.TenantID, uid types.Uid, topic, cursor st
return tenantAdp.TenantCallList(tenantID, uid, strings.TrimSpace(topic), strings.TrimSpace(cursor), limit)
}
type GroupBusinessPersistenceInterface interface {
CreateSettings(types.TenantID, *types.GroupSettings) (*types.GroupSettings, error)
GetSettings(types.TenantID, string) (*types.GroupSettings, error)
UpdateSettings(types.TenantID, string, types.GroupSettingsPatch) (*types.GroupSettings, error)
UpsertMember(types.TenantID, *types.GroupMember) (*types.GroupMember, error)
GetMember(types.TenantID, string, types.Uid) (*types.GroupMember, error)
ListMembers(types.TenantID, string, types.GroupMemberListOptions) ([]types.GroupMember, string, error)
UpdateMember(types.TenantID, string, types.Uid, types.GroupMemberPatch) (*types.GroupMember, error)
CreateInvitation(types.TenantID, *types.GroupInvitation) (*types.GroupInvitation, error)
CreateJoinRequest(types.TenantID, *types.GroupJoinRequest) (*types.GroupJoinRequest, error)
ListJoinRequests(types.TenantID, string, string, string, int) ([]types.GroupJoinRequest, string, error)
ResolveJoinRequest(types.TenantID, string, string, types.Uid, string, string, int, time.Time) (*types.GroupJoinRequest, error)
LatestAnnouncement(types.TenantID, string) (*types.GroupAnnouncement, error)
PutAnnouncement(types.TenantID, *types.GroupAnnouncement, int) (*types.GroupAnnouncement, error)
ReadAnnouncement(types.TenantID, string, string, types.Uid, time.Time) error
ListMutes(types.TenantID, string, string, int) ([]types.GroupMute, string, error)
UpsertMute(types.TenantID, *types.GroupMute) (*types.GroupMute, error)
DeleteMute(types.TenantID, string, types.Uid, types.Uid, time.Time) error
}
type groupBusinessMapper struct{}
var GroupBusiness GroupBusinessPersistenceInterface
func (groupBusinessMapper) CreateSettings(tenantID types.TenantID, settings *types.GroupSettings) (*types.GroupSettings, error) {
if settings == nil || settings.Topic == "" || settings.OwnerUserID.IsZero() || settings.CreatedBy.IsZero() {
return nil, types.ErrMalformed
}
tenantAdp, err := tenantBusinessAdapter(tenantID)
if err != nil {
return nil, err
}
return tenantAdp.TenantGroupSettingsCreate(tenantID, settings)
}
func (groupBusinessMapper) GetSettings(tenantID types.TenantID, topic string) (*types.GroupSettings, error) {
if strings.TrimSpace(topic) == "" {
return nil, types.ErrMalformed
}
tenantAdp, err := tenantBusinessAdapter(tenantID)
if err != nil {
return nil, err
}
return tenantAdp.TenantGroupSettingsGet(tenantID, strings.TrimSpace(topic))
}
func (groupBusinessMapper) UpdateSettings(tenantID types.TenantID, topic string, patch types.GroupSettingsPatch) (*types.GroupSettings, error) {
if strings.TrimSpace(topic) == "" {
return nil, types.ErrMalformed
}
tenantAdp, err := tenantBusinessAdapter(tenantID)
if err != nil {
return nil, err
}
return tenantAdp.TenantGroupSettingsUpdate(tenantID, strings.TrimSpace(topic), patch)
}
func (groupBusinessMapper) UpsertMember(tenantID types.TenantID, member *types.GroupMember) (*types.GroupMember, error) {
if member == nil || member.Topic == "" || member.UserID.IsZero() {
return nil, types.ErrMalformed
}
tenantAdp, err := tenantBusinessAdapter(tenantID)
if err != nil {
return nil, err
}
return tenantAdp.TenantGroupMemberUpsert(tenantID, member)
}
func (groupBusinessMapper) GetMember(tenantID types.TenantID, topic string, uid types.Uid) (*types.GroupMember, error) {
if strings.TrimSpace(topic) == "" || uid.IsZero() {
return nil, types.ErrMalformed
}
tenantAdp, err := tenantBusinessAdapter(tenantID)
if err != nil {
return nil, err
}
return tenantAdp.TenantGroupMemberGet(tenantID, strings.TrimSpace(topic), uid)
}
func (groupBusinessMapper) ListMembers(tenantID types.TenantID, topic string, opts types.GroupMemberListOptions) ([]types.GroupMember, string, error) {
if strings.TrimSpace(topic) == "" {
return nil, "", types.ErrMalformed
}
if opts.Limit <= 0 || opts.Limit > 100 {
opts.Limit = 30
}
tenantAdp, err := tenantBusinessAdapter(tenantID)
if err != nil {
return nil, "", err
}
return tenantAdp.TenantGroupMemberList(tenantID, strings.TrimSpace(topic), opts)
}
func (groupBusinessMapper) UpdateMember(tenantID types.TenantID, topic string, uid types.Uid, patch types.GroupMemberPatch) (*types.GroupMember, error) {
if strings.TrimSpace(topic) == "" || uid.IsZero() {
return nil, types.ErrMalformed
}
tenantAdp, err := tenantBusinessAdapter(tenantID)
if err != nil {
return nil, err
}
return tenantAdp.TenantGroupMemberUpdate(tenantID, strings.TrimSpace(topic), uid, patch)
}
func (groupBusinessMapper) CreateInvitation(tenantID types.TenantID, invitation *types.GroupInvitation) (*types.GroupInvitation, error) {
if invitation == nil || invitation.Topic == "" || invitation.InviterUserID.IsZero() || invitation.TargetUserID.IsZero() {
return nil, types.ErrMalformed
}
tenantAdp, err := tenantBusinessAdapter(tenantID)
if err != nil {
return nil, err
}
return tenantAdp.TenantGroupInvitationCreate(tenantID, invitation)
}
func (groupBusinessMapper) CreateJoinRequest(tenantID types.TenantID, req *types.GroupJoinRequest) (*types.GroupJoinRequest, error) {
if req == nil || req.Topic == "" || req.ApplicantUserID.IsZero() || req.RequestID == "" {
return nil, types.ErrMalformed
}
tenantAdp, err := tenantBusinessAdapter(tenantID)
if err != nil {
return nil, err
}
return tenantAdp.TenantGroupJoinRequestCreate(tenantID, req)
}
func (groupBusinessMapper) ListJoinRequests(tenantID types.TenantID, topic, status, cursor string, limit int) ([]types.GroupJoinRequest, string, error) {
if strings.TrimSpace(topic) == "" {
return nil, "", types.ErrMalformed
}
if limit <= 0 || limit > 100 {
limit = 50
}
tenantAdp, err := tenantBusinessAdapter(tenantID)
if err != nil {
return nil, "", err
}
return tenantAdp.TenantGroupJoinRequestList(tenantID, strings.TrimSpace(topic), strings.TrimSpace(status), strings.TrimSpace(cursor), limit)
}
func (groupBusinessMapper) ResolveJoinRequest(tenantID types.TenantID, topic, requestID string, actor types.Uid, action, rejectReason string, version int, now time.Time) (*types.GroupJoinRequest, error) {
if strings.TrimSpace(topic) == "" || strings.TrimSpace(requestID) == "" || actor.IsZero() || now.IsZero() {
return nil, types.ErrMalformed
}
tenantAdp, err := tenantBusinessAdapter(tenantID)
if err != nil {
return nil, err
}
return tenantAdp.TenantGroupJoinRequestResolve(tenantID, strings.TrimSpace(topic), strings.TrimSpace(requestID), actor, strings.TrimSpace(action), strings.TrimSpace(rejectReason), version, now)
}
func (groupBusinessMapper) LatestAnnouncement(tenantID types.TenantID, topic string) (*types.GroupAnnouncement, error) {
if strings.TrimSpace(topic) == "" {
return nil, types.ErrMalformed
}
tenantAdp, err := tenantBusinessAdapter(tenantID)
if err != nil {
return nil, err
}
return tenantAdp.TenantGroupAnnouncementLatest(tenantID, strings.TrimSpace(topic))
}
func (groupBusinessMapper) PutAnnouncement(tenantID types.TenantID, ann *types.GroupAnnouncement, expectedVersion int) (*types.GroupAnnouncement, error) {
if ann == nil || ann.Topic == "" || ann.AuthorUserID.IsZero() || ann.Content == "" {
return nil, types.ErrMalformed
}
tenantAdp, err := tenantBusinessAdapter(tenantID)
if err != nil {
return nil, err
}
return tenantAdp.TenantGroupAnnouncementPut(tenantID, ann, expectedVersion)
}
func (groupBusinessMapper) ReadAnnouncement(tenantID types.TenantID, topic, announcementID string, uid types.Uid, readAt time.Time) error {
if strings.TrimSpace(topic) == "" || strings.TrimSpace(announcementID) == "" || uid.IsZero() || readAt.IsZero() {
return types.ErrMalformed
}
tenantAdp, err := tenantBusinessAdapter(tenantID)
if err != nil {
return err
}
return tenantAdp.TenantGroupAnnouncementRead(tenantID, strings.TrimSpace(topic), strings.TrimSpace(announcementID), uid, readAt)
}
func (groupBusinessMapper) ListMutes(tenantID types.TenantID, topic, cursor string, limit int) ([]types.GroupMute, string, error) {
if strings.TrimSpace(topic) == "" {
return nil, "", types.ErrMalformed
}
if limit <= 0 || limit > 100 {
limit = 50
}
tenantAdp, err := tenantBusinessAdapter(tenantID)
if err != nil {
return nil, "", err
}
return tenantAdp.TenantGroupMuteList(tenantID, strings.TrimSpace(topic), strings.TrimSpace(cursor), limit)
}
func (groupBusinessMapper) UpsertMute(tenantID types.TenantID, mute *types.GroupMute) (*types.GroupMute, error) {
if mute == nil || mute.Topic == "" || mute.UserID.IsZero() || mute.MutedBy.IsZero() {
return nil, types.ErrMalformed
}
tenantAdp, err := tenantBusinessAdapter(tenantID)
if err != nil {
return nil, err
}
return tenantAdp.TenantGroupMuteUpsert(tenantID, mute)
}
func (groupBusinessMapper) DeleteMute(tenantID types.TenantID, topic string, uid, actor types.Uid, now time.Time) error {
if strings.TrimSpace(topic) == "" || uid.IsZero() || actor.IsZero() || now.IsZero() {
return types.ErrMalformed
}
tenantAdp, err := tenantBusinessAdapter(tenantID)
if err != nil {
return err
}
return tenantAdp.TenantGroupMuteDelete(tenantID, strings.TrimSpace(topic), uid, actor, now)
}
type WorkbenchPersistenceInterface interface {
Get(tenantID types.TenantID, uid types.Uid) ([]types.WorkbenchModule, int, error)
}
@@ -2861,6 +3081,7 @@ func init() {
Subs = subsMapper{}
Messages = messagesMapper{}
Calls = callsMapper{}
GroupBusiness = groupBusinessMapper{}
Workbench = workbenchMapper{}
Organization = organizationMapper{}
Meetings = meetingsMapper{}
+182
View File
@@ -0,0 +1,182 @@
package types
import "time"
const (
GroupStatusActive = "active"
GroupMemberRoleOwner = "owner"
GroupMemberRoleAdmin = "admin"
GroupMemberRoleMember = "member"
GroupMemberStatusActive = "active"
GroupMemberStatusLeft = "left"
GroupInvitationStatusJoined = "joined"
GroupJoinRequestStatusPending = "pending"
GroupJoinRequestStatusAccepted = "accepted"
GroupJoinRequestStatusRejected = "rejected"
GroupAnnouncementStatusCurrent = "current"
GroupAnnouncementStatusSuperseded = "superseded"
GroupMuteStatusActive = "active"
GroupMuteStatusRevoked = "revoked"
)
type GroupSettings struct {
Topic string `db:"topic"`
OwnerUserID Uid `db:"-"`
Name string `db:"name"`
AvatarFileID string `db:"avatar_file_id"`
Description string `db:"description"`
GroupStatus string `db:"group_status"`
JoinApproval bool `db:"join_approval"`
MuteAll bool `db:"mute_all"`
MemberUploadFiles bool `db:"member_upload_files"`
MemberPrivateChat bool `db:"member_private_chat"`
MemberInvite bool `db:"member_invite"`
MaxMemberCount int `db:"max_member_count"`
MaxAdminCount int `db:"max_admin_count"`
MemberCount int `db:"member_count"`
Version int `db:"version"`
CreatedBy Uid `db:"-"`
DissolvedBy Uid `db:"-"`
DissolvedAt *time.Time `db:"dissolved_at"`
CreatedAt time.Time `db:"created_at"`
UpdatedAt time.Time `db:"updated_at"`
OwnerUserIDRaw int64 `db:"owner_user_id"`
CreatedByRaw int64 `db:"created_by"`
DissolvedByRaw *int64 `db:"dissolved_by"`
}
type GroupSettingsPatch struct {
Name *string
AvatarFileID *string
Description *string
OwnerUserID *Uid
JoinApproval *bool
MuteAll *bool
MemberUploadFiles *bool
MemberPrivateChat *bool
MemberInvite *bool
MemberCountDelta int
Version int
}
type GroupMember struct {
ID int64 `db:"id"`
Topic string `db:"topic"`
UserID Uid `db:"-"`
Role string `db:"role"`
MemberStatus string `db:"member_status"`
Nickname string `db:"nickname"`
Pinned bool `db:"pinned"`
MuteNotifications bool `db:"mute_notifications"`
JoinSource string `db:"join_source"`
JoinedBy Uid `db:"-"`
JoinedAt time.Time `db:"joined_at"`
LeftAt *time.Time `db:"left_at"`
RemovedBy Uid `db:"-"`
Version int `db:"version"`
CreatedAt time.Time `db:"created_at"`
UpdatedAt time.Time `db:"updated_at"`
UserIDRaw int64 `db:"user_id"`
JoinedByRaw *int64 `db:"joined_by"`
RemovedByRaw *int64 `db:"removed_by"`
}
type GroupMemberPatch struct {
Role *string
MemberStatus *string
Nickname *string
Pinned *bool
MuteNotifications *bool
LeftAt *time.Time
RemovedBy *Uid
Version int
}
type GroupMemberListOptions struct {
Role string
EligibleRole string
Query string
Cursor string
Limit int
}
type GroupInvitation struct {
ID int64 `db:"id"`
InvitationID string `db:"invitation_id"`
Topic string `db:"topic"`
InviterUserID Uid `db:"-"`
TargetUserID Uid `db:"-"`
Message string `db:"message"`
Status string `db:"status"`
Result string `db:"result"`
JoinRequestID string `db:"join_request_id"`
IdempotencyKey string `db:"idempotency_key"`
ExpiresAt time.Time `db:"expires_at"`
HandledAt *time.Time `db:"handled_at"`
CreatedAt time.Time `db:"created_at"`
UpdatedAt time.Time `db:"updated_at"`
InviterUserRaw int64 `db:"inviter_user_id"`
TargetUserRaw int64 `db:"target_user_id"`
}
type GroupJoinRequest struct {
ID int64 `db:"id"`
RequestID string `db:"request_id"`
Topic string `db:"topic"`
ApplicantUserID Uid `db:"-"`
InviterUserID Uid `db:"-"`
Source string `db:"source"`
QRTokenHash string `db:"qr_token_hash"`
Message string `db:"message"`
Status string `db:"status"`
RejectReason string `db:"reject_reason"`
HandledBy Uid `db:"-"`
HandledAt *time.Time `db:"handled_at"`
ExpiresAt *time.Time `db:"expires_at"`
IdempotencyKey string `db:"idempotency_key"`
Version int `db:"version"`
CreatedAt time.Time `db:"created_at"`
UpdatedAt time.Time `db:"updated_at"`
ApplicantUserRaw int64 `db:"applicant_user_id"`
InviterUserRaw *int64 `db:"inviter_user_id"`
HandledByRaw *int64 `db:"handled_by"`
}
type GroupAnnouncement struct {
ID int64 `db:"id"`
AnnouncementID string `db:"announcement_id"`
Topic string `db:"topic"`
AuthorUserID Uid `db:"-"`
Content string `db:"content"`
Status string `db:"status"`
Version int `db:"version"`
PublishedAt time.Time `db:"published_at"`
IdempotencyKey string `db:"idempotency_key"`
CreatedAt time.Time `db:"created_at"`
UpdatedAt time.Time `db:"updated_at"`
AuthorUserIDRaw int64 `db:"author_user_id"`
}
type GroupMute struct {
Topic string `db:"topic"`
UserID Uid `db:"-"`
MutedBy Uid `db:"-"`
MutedAt time.Time `db:"muted_at"`
ExpiresAt *time.Time `db:"expires_at"`
Reason string `db:"reason"`
Status string `db:"status"`
RevokedBy Uid `db:"-"`
RevokedAt *time.Time `db:"revoked_at"`
Version int `db:"version"`
CreatedAt time.Time `db:"created_at"`
UpdatedAt time.Time `db:"updated_at"`
UserIDRaw int64 `db:"user_id"`
MutedByRaw int64 `db:"muted_by"`
RevokedRaw *int64 `db:"revoked_by"`
}