Skip to content

Commit 5546abc

Browse files
committed
Fixing incorrect topic participants
The alias-moving code had a logic issue, which sometimes created incorrect topic participations. This commit fixes the issue, but we also have to recalculate all affected records. We do this by querying topic participations where we see somebody registered who didn't really post messages to that topic, and completely rebuild the records for these threads.
1 parent fbe9aa6 commit 5546abc

3 files changed

Lines changed: 71 additions & 14 deletions

File tree

app/jobs/person_id_propagation_job.rb

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
11
class PersonIdPropagationJob < ApplicationJob
22
queue_as :default
33

4-
def perform(alias_id, new_person_id)
5-
alias_record = Alias.find_by(id: alias_id)
6-
old_person_id = alias_record&.person_id_before_last_save || find_old_person_id(alias_id, new_person_id)
7-
4+
def perform(alias_id, new_person_id, old_person_id)
85
# Update topics where this alias is the creator
96
Topic.where(creator_id: alias_id)
107
.where.not(creator_person_id: new_person_id)
@@ -27,15 +24,6 @@ def perform(alias_id, new_person_id)
2724

2825
private
2926

30-
def find_old_person_id(alias_id, new_person_id)
31-
# Try to find the old person by looking at topic_participants that reference
32-
# topics where this alias sent messages but the participant has a different person_id
33-
topic_ids = Message.where(sender_id: alias_id).select(:topic_id).distinct
34-
TopicParticipant.where(topic_id: topic_ids)
35-
.where.not(person_id: new_person_id)
36-
.pick(:person_id)
37-
end
38-
3927
def merge_topic_participants(old_person_id, new_person_id)
4028
is_new_contributor = ContributorMembership.exists?(person_id: new_person_id)
4129
affected_topic_ids = []

app/models/alias.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,8 @@ def contributor_badge
111111
def queue_person_id_propagation
112112
return unless person_id.present?
113113

114-
PersonIdPropagationJob.perform_later(id, person_id)
114+
old_person_id = person_id_before_last_save
115+
PersonIdPropagationJob.perform_later(id, person_id, old_person_id)
115116
end
116117

117118
def auto_star_recent_topics

script/fix_topic_participants.rb

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
#!/usr/bin/env ruby
2+
# frozen_string_literal: true
3+
4+
# Fix incorrect TopicParticipant records.
5+
#
6+
# This script finds TopicParticipant records where the person has no messages
7+
# in that topic (orphaned records created by a bug in PersonIdPropagationJob),
8+
# and rebuilds the correct participant data from actual messages.
9+
#
10+
# Usage:
11+
# ruby script/fix_topic_participants.rb # dry run, report only
12+
# ruby script/fix_topic_participants.rb --fix # actually fix the records
13+
14+
require_relative '../config/environment'
15+
16+
dry_run = !ARGV.include?('--fix')
17+
18+
puts "Finding topics with incorrect TopicParticipant records..."
19+
puts "(dry run - use --fix to apply changes)" if dry_run
20+
puts
21+
22+
# Find TopicParticipant records where the person has no messages in that topic
23+
orphaned_participants = TopicParticipant.where(<<~SQL)
24+
NOT EXISTS (
25+
SELECT 1 FROM messages
26+
WHERE messages.topic_id = topic_participants.topic_id
27+
AND messages.sender_person_id = topic_participants.person_id
28+
)
29+
SQL
30+
31+
affected_topic_ids = orphaned_participants.distinct.pluck(:topic_id)
32+
33+
if affected_topic_ids.empty?
34+
puts "No incorrect TopicParticipant records found."
35+
exit 0
36+
end
37+
38+
puts "Found #{orphaned_participants.count} orphaned TopicParticipant records across #{affected_topic_ids.size} topics."
39+
puts
40+
41+
# Show details of affected topics
42+
affected_topic_ids.each do |topic_id|
43+
topic = Topic.find(topic_id)
44+
orphaned = orphaned_participants.where(topic_id: topic_id).includes(:person)
45+
46+
puts "Topic ##{topic_id}: #{topic.title.truncate(60)}"
47+
orphaned.each do |tp|
48+
person_name = tp.person&.display_name || "Person ##{tp.person_id}"
49+
puts " - #{person_name} (#{tp.message_count} messages recorded, 0 actual)"
50+
end
51+
end
52+
53+
puts
54+
55+
if dry_run
56+
puts "Run with --fix to rebuild these #{affected_topic_ids.size} topics."
57+
else
58+
puts "Rebuilding #{affected_topic_ids.size} topics..."
59+
60+
affected_topic_ids.each_with_index do |topic_id, index|
61+
topic = Topic.find(topic_id)
62+
topic.recalculate_participants!
63+
puts " [#{index + 1}/#{affected_topic_ids.size}] Fixed topic ##{topic_id}"
64+
end
65+
66+
puts
67+
puts "Done. Rebuilt #{affected_topic_ids.size} topics."
68+
end

0 commit comments

Comments
 (0)