diff --git a/app/jobs/card/clean_inaccessible_data_job.rb b/app/jobs/card/clean_inaccessible_data_job.rb new file mode 100644 index 000000000..c3fbef936 --- /dev/null +++ b/app/jobs/card/clean_inaccessible_data_job.rb @@ -0,0 +1,7 @@ +class Card::CleanInaccessibleDataJob < ApplicationJob + discard_on ActiveJob::DeserializationError + + def perform(card) + card.clean_inaccessible_data + end +end diff --git a/app/models/card.rb b/app/models/card.rb index 18957cb9b..d76512093 100644 --- a/app/models/card.rb +++ b/app/models/card.rb @@ -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 diff --git a/app/models/card/accessible.rb b/app/models/card/accessible.rb new file mode 100644 index 000000000..e74e0b914 --- /dev/null +++ b/app/models/card/accessible.rb @@ -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 diff --git a/test/models/card_test.rb b/test/models/card_test.rb index 9c7d45add..bb72fddf8 100644 --- a/test/models/card_test.rb +++ b/test/models/card_test.rb @@ -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