mirror of
https://github.com/basecamp/once-campfire.git
synced 2026-08-12 18:10:43 +09:00
5c5c82b27a
Rooms::DirectsController relaxes ensure_can_administer to true, because every participant in a direct room may administer it. set_room was inherited unscoped, though, so that relaxation applied to any room the caller was merely a member of: DELETE /rooms/directs/<id> destroyed open and closed rooms and all their messages. The same unscoped lookup let a direct room be loaded by the opens and closeds controllers, where force_room_type promoted it. Promoting a DM to open grants every user on the account membership and republishes the whole conversation, including the other participant's messages; converting it to closed lets the initiator revise who is in it and lock the other participant out. Each controller now narrows room_scope to the types it may act on. Opens and closeds keep reach into each other, since converting between them is a feature. Neither can reach a direct room, and directs can only reach directs. Room also refuses to change type away from Rooms::Direct, so the invariant holds for any future caller of becomes! rather than only these two controllers.
82 lines
2.2 KiB
Ruby
82 lines
2.2 KiB
Ruby
class Rooms::ClosedsController < RoomsController
|
|
before_action :set_room, only: %i[ show edit update ]
|
|
before_action :ensure_can_administer, only: %i[ update ]
|
|
before_action :remember_last_room_visited, only: :show
|
|
before_action :force_room_type, only: %i[ edit update ]
|
|
before_action :ensure_permission_to_create_rooms, only: %i[ new create ]
|
|
|
|
DEFAULT_ROOM_NAME = "New room"
|
|
|
|
def show
|
|
redirect_to room_url(@room)
|
|
end
|
|
|
|
def new
|
|
@room = Rooms::Closed.new(name: DEFAULT_ROOM_NAME)
|
|
@users = User.active.ordered
|
|
end
|
|
|
|
def create
|
|
room = Rooms::Closed.create_for(room_params, users: grantees)
|
|
|
|
broadcast_create_room(room)
|
|
redirect_to room_url(room)
|
|
end
|
|
|
|
def edit
|
|
selected_user_ids = @room.users.pluck(:id)
|
|
@selected_users, @unselected_users = User.active.ordered.partition { |user| selected_user_ids.include?(user.id) }
|
|
end
|
|
|
|
def update
|
|
@room.update! room_params
|
|
@room.memberships.revise(granted: grantees, revoked: revokees)
|
|
|
|
broadcast_update_room
|
|
redirect_to room_url(@room)
|
|
end
|
|
|
|
private
|
|
# Allows us to edit an open room and turn it into a closed one on saving.
|
|
def force_room_type
|
|
@room = @room.becomes!(Rooms::Closed)
|
|
end
|
|
|
|
# Open and closed rooms convert into each other, so both are in reach here. Direct
|
|
# rooms never are: converting one would let its creator revise who's in it.
|
|
def room_scope
|
|
Current.user.rooms.without_directs
|
|
end
|
|
|
|
def grantees
|
|
User.where(id: grantee_ids)
|
|
end
|
|
|
|
def revokees
|
|
@room.users.where.not(id: grantee_ids)
|
|
end
|
|
|
|
def grantee_ids
|
|
params.fetch(:user_ids, [])
|
|
end
|
|
|
|
def broadcast_create_room(room)
|
|
each_user_and_html_for(room) do |user, html|
|
|
broadcast_prepend_to user, :rooms, target: :shared_rooms, html: html
|
|
end
|
|
end
|
|
|
|
def broadcast_update_room
|
|
each_user_and_html_for(@room) do |user, html|
|
|
broadcast_replace_to user, :rooms, target: [ @room, :list ], html: html
|
|
end
|
|
end
|
|
|
|
def each_user_and_html_for(room)
|
|
# Optimization to avoid rendering the same partial for every user
|
|
html = render_to_string(partial: "users/sidebars/rooms/shared", locals: { room: room })
|
|
|
|
room.users.each { |user| yield user, html }
|
|
end
|
|
end
|