# frozen_string_literal: true
class UserNotificationRoutesController < DashboardController
before_action :redirect_if_not_player_mode, if: -> { current_context == current_community }
skip_before_action :load_communities, only: %i[create update destroy], if: -> { current_context == current_community }
def server_index
render action_name, locals: {
community: current_community.clientize,
servers: Community.servers_by_community,
channels: load_admin_channels,
pending_requests: load_pending_requests,
types: load_types,
users: load_users,
type_presets: load_type_presets,
view_path: "user_notification_routes/server"
}
end
def player_index
render action_name, locals: {
user: current_user.clientize,
communities: load_player_communities_and_channels,
servers: Community.servers_by_community,
pending_requests: load_pending_requests,
types: load_types,
type_presets: load_type_presets,
view_path: "user_notification_routes/player"
}
end
def create
# Validate and load the users
users =
if route_params[:user_ids].present?
users = User.where(discord_id: route_params[:user_ids]).select(:id)
return check_failed!(message: "Failed to find the requested users") if users.blank? || users.size != route_params[:user_ids].size
users
else
[current_user]
end
# Validate the community. If we're modifying this from the server dashboard, current_community will be defined.
community = current_community || Community.find_by_community_id(route_params[:community_id])
return check_failed!(message: "Failed to find the requested community") if community.nil?
# Validate the channel
filter =
if current_context == current_community
{community_id: current_community.id}
else
{user_id: current_user.id}
end
channel = ESM.channel(route_params[:channel_id], **filter)
return check_failed!(message: "You do not have access to that channel") if channel.nil?
# Presets for types :)
types =
case route_params[:types]
when "any"
UserNotificationRoute::TYPES
when "raids"
UserNotificationRoute::TYPE_PRESETS[:raids]
when "payments"
UserNotificationRoute::TYPE_PRESETS[:payments]
else
UserNotificationRoute::TYPES.select { |type| type.in?(route_params[:types]) }
end
# Validate the servers
servers =
if route_params[:server_ids].is_a?(String) && route_params[:server_ids].casecmp?("any")
[nil]
else
servers = Server.where(server_id: route_params[:server_ids]).select(:id)
return check_failed!(message: "Failed to find the requested servers") if servers.blank? || servers.size != route_params[:server_ids].size
servers
end
# This builds all of the queries as Array<Hash>
queries = users.map do |user|
# This auto accepts any requests for the current user
auto_accept =
if current_context == current_community
current_user == user
else
current_user == user && community.modifiable_by?(current_user)
end
types.map do |type|
servers.map do |server|
{
public_id: SecureRandom.uuid,
user_id: user.id,
source_server_id: server&.id,
destination_community_id: community.id,
channel_id: channel[:id],
notification_type: type,
community_accepted: auto_accept || current_context == current_community,
user_accepted: auto_accept || current_context == current_user
}
end
end
end.flatten
# Remove any duplicates
queries.reject! { |query| UserNotificationRoute.where(query.except(:community_accepted, :public_id)).exists? }
# I love bulk inserts
UserNotificationRoute.import(queries)
# Check if any were auto-accepted and notify the channel
accepted_uuids = queries.select { |query| query[:community_accepted] && query[:user_accepted] }.map { |query| query[:public_id] }
routes = UserNotificationRoute.where(public_id: accepted_uuids)
notify_channel(routes) if routes.present?
render json: {
routes: current_context.user_notification_routes.where(channel_id: channel[:id]).clientize,
message: "#{"Request".pluralize(queries.size)} sent. ESM will send a message to <span class='esm-text-color-toast-blue'>##{channel[:name]}</span> when the request has been accepted"
}, status: :created
end
def update
route = current_context.user_notification_routes.where(public_id: params[:id]).first
return check_failed!(message: "Failed to find the requested route") if route.blank?
route.update!(enabled: params[:enabled])
message = "#{route.notification_type.titleize} notifications are now #{route.enabled? ? "enabled" : "disabled"} for this channel"
message =
if current_context == current_community
"#{route.user.user_name}'s #{message}"
else
"Your #{message}"
end
render json: {message: message}, status: :ok
end
def destroy
route = current_context.user_notification_routes.where(public_id: params[:id]).first
return check_failed!(message: "Failed to find the requested route") if route.blank?
route.destroy!
message = "#{route.notification_type.titleize} notifications from #{route.source_server&.server_name || "any server"} will no longer route to this channel"
message =
if current_context == current_community
"#{route.user.user_name}'s #{message}"
else
"Your #{message}"
end
render json: {message: message}, status: :ok
end
def destroy_many
routes = current_context.user_notification_routes.where(public_id: params[:ids])
return check_failed!(message: "Failed to find the requested routes") if routes.blank?
routes.each(&:destroy)
route = routes.first
message = "notifications from #{route.source_server&.server_name || "any server"} will no longer route to this channel"
message =
if current_context == current_community
"#{route.user.user_name}'s #{message}"
else
"Your #{message}"
end
render json: {message: message}, status: :ok
end
def accept_requests
routes = current_context.user_notification_routes.where(public_id: params[:ids])
return check_failed!(message: "Failed to find the requested routes") if routes.blank? || routes.size != params[:ids].size
if current_community
routes.update_all(community_accepted: true, updated_at: Time.current)
else
routes.update_all(user_accepted: true, updated_at: Time.current)
end
render json: {message: "#{"Request".pluralize(routes.size)} accepted"}
notify_channel(routes)
end
def decline_requests
routes = current_context.user_notification_routes.where(public_id: params[:ids])
return check_failed!(message: "Failed to find the requested routes") if routes.blank? || routes.size != params[:ids].size
routes.delete_all
render json: {message: "#{"Request".pluralize(routes.size)} declined"}
end
def accept_all_requests
pending_requests =
if current_context == current_community
current_community.user_notification_routes.pending_community_acceptance
else
current_user.user_notification_routes.pending_user_acceptance
end
grouped_routes = pending_requests.by_channel_server_and_user.values
grouped_routes.each do |routes|
updated_fields = {updated_at: Time.current}
if current_context == current_community
updated_fields[:community_accepted] = true
else
updated_fields[:user_accepted] = true
end
UserNotificationRoute.where(id: routes.map(&:id)).update_all(**updated_fields)
notify_channel(routes)
end
render json: {message: "All requests accepted"}
end
def decline_all_requests
pending_requests =
if current_context == current_community
current_community.user_notification_routes.pending_community_acceptance
else
current_user.user_notification_routes.pending_user_acceptance
end
pending_requests.delete_all
render json: {message: "All requests declined"}
end
private
def route_params
# server_ids and types can be an array or a string
@route_params ||= params.permit(:server_ids, :types, :community_id, :channel_id, user_ids: [], types: [], server_ids: [])
end
def load_player_communities_and_channels
current_user.player_communities.map do |community|
{
id: community.community_id,
name: community.community_name,
channels: decorate_channels(community.player_channels(current_user))
}
end
end
def load_types
UserNotificationRoute::TYPES.sort.map { |type| {id: type, name: type.titleize} }
end
def load_type_presets
UserNotificationRoute::TYPE_PRESETS.transform_values { |v| v.map(&:titleize) }
end
def load_admin_channels
decorate_channels(current_community.admin_channels)
end
def load_users
user_discord_ids = ESM.community_users(current_community.id)&.map { |u| u[:id] }
users = User.order(:discord_username)
.select(:id, :discord_id, :steam_uid, :discord_username)
.where(discord_id: user_discord_ids)
.where.not(steam_uid: [nil, ""])
users.map do |user|
{
id: user.discord_id,
name: user.user_name
}
end
end
def load_pending_requests
requests =
if current_community
current_community.user_notification_routes.pending_community_acceptance
else
current_user.user_notification_routes.pending_user_acceptance
end
# requests are grouped by user and contain each server they have routes for
# Pending requests are by user, channel, and server so some data manipulation has to happen
requests.clientize.flat_map do |request|
request[:servers].map do |server|
{
server: server.except(:types),
user: request[:user],
channel: request[:channel],
types: server[:types],
community: request[:community],
types_expanded: false
}
end
end
end
def notify_channel(routes)
# Everything is grouped so all the requests are for one user, from one server, routing to one channel
template_route = routes.first
user = template_route.user
types_sentence =
if UserNotificationRoute::TYPES.size == routes.size
"all"
else
routes.map { |route| "`#{route.notification_type.titleize}`" }.to_sentence
end
server =
if template_route.source_server
"`#{template_route.source_server.server_id}`"
else
"any server"
end
ESM.send_message(
channel_id: template_route.channel_id,
message: <<~STRING
:incoming_envelope: #{user.mention}, this channel will now receive #{types_sentence} XM8 notifications sent to you from #{server}
STRING
)
end
def decorate_channels(channels)
channels.map do |category, category_channels|
category_channels.each do |channel|
channel[:routes] = current_context.user_notification_routes.where(channel_id: channel[:id]).clientize
channel[:name] = "##{channel[:name]}"
end
{category_name: category[:name], channels: category_channels}
end
end
end