fix: keep DM participant rows when a user leaves the chat group

The participant sync and the non-member prune now remove only ogólny's
rows. A user taken out of studenci, even briefly, keeps their DMs (the
live membership check denies access while they are out) and gets them
back when re-added, instead of losing them for good.
This commit is contained in:
2026-10-26 19:34:53 +01:00
parent 8b81295458
commit 0ff943b6af
2 changed files with 20 additions and 15 deletions

View File

@@ -34,9 +34,12 @@ JOIN user_groups ON user_groups.id = users_groups.user_group_id
JOIN users ON users.id = users_groups.user_id
WHERE user_groups.code = ? AND users.deleted_at IS NULL`
// chatConversationsSQL selects the ids of the conversations of context
// bm/1 (ogólny and every DM).
const chatConversationsSQL = `SELECT id FROM golem15_chat_conversations WHERE context_type = ? AND context_id = ?`
// chatGroupConversationsSQL selects the ids of the group conversations of
// context bm/1 (ogólny). DMs are never listed: their participant rows are
// kept when someone leaves the chat group, so the history comes back when
// they are re-added. Access while they are out is denied by golem15.chat's
// live membership check (ChatContext.RequireLiveMembership).
const chatGroupConversationsSQL = `SELECT id FROM golem15_chat_conversations WHERE context_type = ? AND context_id = ? AND type = ?`
// EnsureChatGroups creates a user group for each code that has none and
// returns how many it created. It never assigns a user to a group: members
@@ -120,12 +123,12 @@ func FindGeneral(ctx context.Context, db *gorm.DB) (*chatmodels.Conversation, er
return &conv, nil
}
// SyncChatParticipants makes the chat's participants follow the chat
// group, in one transaction: every member missing from ogólny (generalID)
// joins it, and users no longer in the group lose their participant rows
// in every conversation of context bm/1. Nothing is pruned while no group
// with that code exists, so a mistyped code cannot wipe the DMs. It returns
// how many rows it added and removed.
// SyncChatParticipants makes ogólny's participants follow the chat group,
// in one transaction: every member missing from ogólny (generalID) joins
// it, and users no longer in the group leave it. DM participant rows are
// kept (a re-added user gets their DMs back; until then golem15.chat's
// live membership check denies access). Nothing is pruned while no group
// with that code exists. It returns how many rows it added and removed.
func SyncChatParticipants(ctx context.Context, db *gorm.DB, generalID uint, group string) (int64, int64, error) {
if db == nil {
return 0, 0, errors.New("jz.bm: chat sync needs a database")
@@ -145,9 +148,9 @@ ON CONFLICT (conversation_id, user_id) DO NOTHING`, generalID, group)
added = res.RowsAffected
}
res := tx.Exec(`DELETE FROM golem15_chat_participants
WHERE conversation_id IN (`+chatConversationsSQL+`)
WHERE conversation_id IN (`+chatGroupConversationsSQL+`)
AND user_id NOT IN (`+chatMembersSQL+`)
AND EXISTS (SELECT 1 FROM user_groups WHERE code = ?)`, ChatContextType, ChatContextID, group, group)
AND EXISTS (SELECT 1 FROM user_groups WHERE code = ?)`, ChatContextType, ChatContextID, chatmodels.TypeGroup, group, group)
if res.Error != nil {
return res.Error
}
@@ -157,14 +160,16 @@ WHERE conversation_id IN (`+chatConversationsSQL+`)
return added, removed, err
}
// PruneChatParticipant removes userID from every conversation of context
// bm/1. It runs when someone outside the chat group asks for the chat.
// PruneChatParticipant removes userID from ogólny (the group
// conversations of context bm/1); their DM rows are kept, as in
// SyncChatParticipants. It runs when someone outside the chat group asks
// for the chat.
func PruneChatParticipant(ctx context.Context, db *gorm.DB, userID uint) error {
if db == nil {
return errors.New("jz.bm: chat prune needs a database")
}
return db.WithContext(ctx).Exec(`DELETE FROM golem15_chat_participants
WHERE user_id = ? AND conversation_id IN (`+chatConversationsSQL+`)`, userID, ChatContextType, ChatContextID).Error
WHERE user_id = ? AND conversation_id IN (`+chatGroupConversationsSQL+`)`, userID, ChatContextType, ChatContextID, chatmodels.TypeGroup).Error
}
// AssignMissingHandles gives every chat-group member without a handle one

View File

@@ -49,7 +49,7 @@ func ChatSyncCommand(withDB func(ctx context.Context, fn func(*gorm.DB) error) e
} else {
out.Printf("ogólny participants added: %d\n", added)
}
out.Printf("participant rows removed: %d\n", removed)
out.Printf("ogólny participants removed: %d\n", removed)
out.Printf("handles assigned: %d\n", handles)
out.Success(chatSyncName + ": chat group " + group + " synced")
return nil