Files
fizzy/app/models/board/accessible.rb
T
Donal McBreen c4498212dc Merge branch 'main' into sqlite
* main: (116 commits)
  Ensure avatar thumbnails are square
  Update useragent to recognize twitterbot/facebot
  Add defensive styles for non-square avatar images
  Update test for copy changes
  Missed commit
  AI: standardize on https://agents.md
  Make it clear this is just notifications, not comprehensive activity
  AI: configure MCP servers for Chrome, Grafana, and Sentry (#1727)
  Allow requests from Google Image Proxy
  Update to basecamp's useragent fork
  Clean up a little bit the CSRF reporting code
  Claude: production observability guidance (#1725)
  Prevent autoscroll to the root columns container to prevent jump on page load
  Include full name string so you can type your name to filter
  Prioritize current user and assigned users in assignment dropdown
  Check and report on Sec-Fetch-Site header for forgery protection
  bundle update
  Bump bootsnap from 1.18.6 to 1.19.0
  Bump rails from `077c3ad` to `17f6e00`
  Fix cards getting stuck in edit mode
  ...
2025-11-26 10:07:41 +00:00

103 lines
3.6 KiB
Ruby

module Board::Accessible
extend ActiveSupport::Concern
included do
has_many :accesses, dependent: :delete_all do
def revise(granted: [], revoked: [])
transaction do
grant_to granted
revoke_from revoked
end
end
def grant_to(users)
Access.insert_all Array(users).collect { |user| { id: ActiveRecord::Type::Uuid.generate, board_id: proxy_association.owner.id, user_id: user.id, account_id: proxy_association.owner.account.id } }
end
def revoke_from(users)
destroy_by user: users unless proxy_association.owner.all_access?
end
end
has_many :users, through: :accesses
has_many :access_only_users, -> { merge(Access.access_only) }, through: :accesses, source: :user
scope :all_access, -> { where(all_access: true) }
after_create :grant_access_to_creator
after_save_commit :grant_access_to_everyone
end
def accessed_by(user)
access_for(user).accessed
end
def access_for(user)
accesses.find_by(user: user)
end
def accessible_to?(user)
access_for(user).present?
end
def clean_inaccessible_data_for(user)
return if accessible_to?(user)
mentions_for_user(user).destroy_all
notifications_for_user(user).destroy_all
watches_for(user).destroy_all
end
def watchers
users.active.where(accesses: { involvement: :watching })
end
private
def grant_access_to_creator
accesses.create(user: creator, involvement: :watching)
end
def grant_access_to_everyone
accesses.grant_to(account.users.active) if all_access_previously_changed?(to: true)
end
def mentions_for_user(user)
# Query handles 2 paths:
#
# 1. Mention->Card
# 2. Mention->Comment->Card
board_id_binary = ActiveRecord::Type::Uuid.new.serialize(id)
user.mentions
.joins("LEFT JOIN cards ON mentions.source_id = cards.id AND mentions.source_type = 'Card'")
.joins("LEFT JOIN comments ON mentions.source_id = comments.id AND mentions.source_type = 'Comment'")
.joins("LEFT JOIN cards AS comment_cards ON comments.card_id = comment_cards.id")
.where("(mentions.source_type = 'Card' AND cards.board_id = ?) OR (mentions.source_type = 'Comment' AND comment_cards.board_id = ?)", board_id_binary, board_id_binary)
end
def notifications_for_user(user)
# Query handles 2 paths:
#
# 1. Notification->Event->Card
# 2. Notification->Event->Comment->Card
#
# Notification->Event->Mention->Card and Notification->Event->Mention->Comment->Card are
# handled by destroying mentions_for_user.
uuid_type = ActiveRecord::Type.lookup(:uuid, adapter: :trilogy)
board_id_binary = uuid_type.serialize(id)
user.notifications
.joins("LEFT JOIN events ON notifications.source_id = events.id AND notifications.source_type = 'Event'")
.joins("LEFT JOIN cards AS event_cards ON events.eventable_id = event_cards.id AND events.eventable_type = 'Card'")
.joins("LEFT JOIN comments AS event_comments ON events.eventable_id = event_comments.id AND events.eventable_type = 'Comment'")
.joins("LEFT JOIN cards AS event_comment_cards ON event_comments.card_id = event_comment_cards.id")
.where("(notifications.source_type = 'Event' AND events.eventable_type = 'Card' AND event_cards.board_id = ?) OR
(notifications.source_type = 'Event' AND events.eventable_type = 'Comment' AND event_comment_cards.board_id = ?)",
board_id_binary, board_id_binary)
end
def watches_for(user)
Watch.where(card: cards, user: user)
end
end