Delete orphaned watches and pins when a card is moved to a private board

For watchers and users with pins that don't have access to the private
board where the card was moved to.
This commit is contained in:
Rosa Gutierrez
2026-01-14 19:06:43 +01:00
committed by Rosa Gutierrez
parent 0f35109e00
commit ee636f1994
4 changed files with 79 additions and 11 deletions
@@ -0,0 +1,7 @@
class Card::CleanInaccessibleDataJob < ApplicationJob
discard_on ActiveJob::DeserializationError
def perform(card)
card.clean_inaccessible_data
end
end
+2 -11
View File
@@ -1,5 +1,5 @@
class Card < ApplicationRecord
include Assignable, Attachments, Broadcastable, Closeable, Colored, Entropic, Eventable,
include Accessible, Assignable, Attachments, Broadcastable, Closeable, Colored, Entropic, Eventable,
Exportable, Golden, Mentions, Multistep, Pinnable, Postponable, Promptable,
Readable, Searchable, Stallable, Statuses, Storage::Tracked, Taggable, Triageable, Watchable
@@ -46,12 +46,6 @@ class Card < ApplicationRecord
end
end
delegate :accessible_to?, to: :board
def publicly_accessible?
published? && board.publicly_accessible?
end
def card
self
end
@@ -135,16 +129,13 @@ class Card < ApplicationRecord
end
remove_inaccessible_notifications_later
clean_inaccessible_data_later
end
def track_board_change_event(old_board_name)
track_event "board_changed", particulars: { old_board: old_board_name, new_board: board.name }
end
def grant_access_to_assignees
board.accesses.grant_to(assignees)
end
def assign_number
self.number ||= account.increment!(:cards_count).cards_count
end
+26
View File
@@ -0,0 +1,26 @@
module Card::Accessible
extend ActiveSupport::Concern
included do
delegate :accessible_to?, to: :board
end
def publicly_accessible?
published? && board.publicly_accessible?
end
def clean_inaccessible_data
accessible_user_ids = board.accesses.pluck(:user_id)
pins.where.not(user_id: accessible_user_ids).in_batches.destroy_all
watches.where.not(user_id: accessible_user_ids).in_batches.destroy_all
end
private
def grant_access_to_assignees
board.accesses.grant_to(assignees)
end
def clean_inaccessible_data_later
Card::CleanInaccessibleDataJob.perform_later(self)
end
end
+44
View File
@@ -163,4 +163,48 @@ class CardTest < ActiveSupport::TestCase
assert_not Card.new.filled?
end
test "pins are deleted when card moves to a board user cannot access" do
card = cards(:logo)
kevin = users(:kevin)
david = users(:david)
# David pins the card (Kevin already has it pinned via fixture)
card.pin_by(david)
assert card.pinned_by?(kevin)
assert card.pinned_by?(david)
# Kevin has access to the private board, David does not
assert boards(:private).accessible_to?(kevin)
assert_not boards(:private).accessible_to?(david)
perform_enqueued_jobs only: Card::CleanInaccessibleDataJob do
card.move_to(boards(:private))
end
assert card.pinned_by?(kevin), "Kevin's pin should remain (has board access)"
assert_not card.pinned_by?(david), "David's pin should be deleted (no board access)"
end
test "watches are deleted when card moves to a board user cannot access" do
card = cards(:logo)
kevin = users(:kevin)
david = users(:david)
# Both watch the card via fixtures
assert card.watched_by?(kevin)
assert card.watched_by?(david)
# Kevin has access to the private board, David does not
assert boards(:private).accessible_to?(kevin)
assert_not boards(:private).accessible_to?(david)
perform_enqueued_jobs only: Card::CleanInaccessibleDataJob do
card.move_to(boards(:private))
end
assert card.watched_by?(kevin), "Kevin's watch should remain (has board access)"
assert_not card.watched_by?(david), "David's watch should be deleted (no board access)"
end
end