-
13
module Admin
-
13
class AdminController < ApplicationController
-
13
before_action :require_admin!, :set_admin_border
-
-
13
private
-
-
13
def require_admin!
-
18
else: 16
then: 2
unless Current.user.admin? || Current.impersonating?
-
2
render plain: "Admins only",
-
status: :forbidden
-
end
-
end
-
-
13
def set_admin_border
-
16
@admin_border = true
-
end
-
end
-
end
-
module Admin
-
class BandsController < AdminController
-
before_action :set_band, except: %i[
-
index
-
]
-
-
def index
-
@bands = Band.all
-
end
-
-
def show
-
end
-
-
def edit
-
end
-
-
def update
-
updated = @band.update(edit_band_params)
-
if updated
-
redirect_to edit_admin_band_path(@band), notice: "Band updated"
-
else
-
flash[:alert] = @band.errors.full_messages
-
render :edit, status: :unprocessable_entity
-
end
-
end
-
-
def destroy
-
@band.destroy!
-
redirect_to admin_bands_path, notice: "Band deleted."
-
end
-
-
private
-
-
def set_band
-
@band = Band.find(params[:id])
-
redirect_to admin_bands_path, alert: "Band not found" unless @band
-
end
-
-
def edit_band_params
-
params.require(:band).permit(
-
:name
-
)
-
end
-
end
-
end
-
5
module Admin
-
5
class ImpersonationController < AdminController
-
5
def create
-
3
target_user = User.find(params[:user_id])
-
3
session[:impersonation] = {
-
original_user_id: Current.user.id,
-
target_user_id: target_user.id,
-
started_at: Time.current.utc
-
}
-
-
3
redirect_to(events_path, notice: "Impersonating '#{target_user.username}'")
-
end
-
-
5
def destroy
-
2
else: 1
then: 1
return redirect_to(admin_users_path, alert: "No active impersonation session") unless session[:impersonation]
-
-
1
target_user = User.find(session[:impersonation]["target_user_id"])
-
1
original_user = User.find(session[:impersonation]["original_user_id"])
-
1
username = target_user.username
-
-
1
session[:user_id] = original_user.id
-
1
session.delete(:impersonation)
-
-
1
redirect_to(admin_users_path, notice: "Ended impersonation of #{username}")
-
end
-
end
-
end
-
10
module Admin
-
10
class UsersController < AdminController
-
10
before_action :set_user, except: %i[
-
index
-
]
-
-
10
def index
-
4
then: 2
else: 2
@user_type = params[:user_type]&.to_sym
-
-
4
then: 1
else: 3
if @user_type && !User.user_types[@user_type]
-
1
flash[:alert] = "Invalid user type"
-
1
redirect_to admin_users_path
-
end
-
-
4
@user_types = User.user_types.keys
-
-
@users =
-
4
then: 2
if @user_type
-
2
User.where(user_type: @user_type)
-
else: 2
else
-
2
User.all
-
end
-
end
-
-
10
def show
-
end
-
-
10
def edit
-
end
-
-
10
def update
-
3
updated = @user.update(edit_user_params)
-
2
then: 1
if updated
-
1
redirect_to edit_admin_user_path(@user), notice: "User updated"
-
else: 1
else
-
1
flash[:alert] = @user.errors.full_messages
-
1
render :edit, status: :unprocessable_entity
-
end
-
end
-
-
10
def destroy
-
1
@user.destroy!
-
1
redirect_to admin_users_path, notice: "User deleted."
-
end
-
-
10
private
-
-
10
def set_user
-
7
@user = User.find_by(username: params[:username])
-
7
else: 7
then: 0
redirect_to admin_users_path, alert: "User not found" unless @user
-
end
-
-
10
def edit_user_params
-
3
params.require(:user).permit(
-
:username,
-
:name,
-
:email,
-
:user_type,
-
:confirmed
-
)
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
16
class ApplicationController < ActionController::Base
-
16
prepend_before_action :set_current_user
-
16
before_action :require_login
-
16
helper_method :show_tabs, :nav_tabs, :my_pending_invites, :body_class, :set_current_user_by_session
-
-
16
def nav_tabs
-
416
else: 408
then: 8
return unless show_tabs
-
408
@nav_tabs ||= build_nav_tabs
-
end
-
-
16
def my_pending_invites
-
212
then: 204
else: 8
else: 70
then: 142
return unless Current.user&.member?
-
70
@my_pending_invites ||= Permission.where(user_id: Current.user.id, status: "pending")
-
end
-
-
16
def body_class
-
212
then: 7
else: 205
@admin_border ? "admin-background" : ""
-
end
-
-
16
def show_tabs
-
620
@show_tabs ||= Current.user.present?
-
end
-
-
16
private
-
-
16
def set_current_user
-
444
set_current_user_by_session(session)
-
end
-
-
16
def set_current_user_by_session(session)
-
445
then: 2
if session[:impersonation]
-
2
else: 443
handle_impersonation(session)
-
443
then: 288
elsif session[:user_id]
-
288
set_regular_user(session[:user_id])
-
else: 155
else
-
155
clear_current_user
-
end
-
end
-
-
16
def handle_impersonation(session)
-
2
impersonation = session[:impersonation]
-
original_user_id =
-
2
impersonation["original_user_id"] ||
-
impersonation[:original_user_id]
-
-
target_user_id =
-
2
impersonation["target_user_id"] ||
-
impersonation[:target_user_id]
-
-
started_at =
-
2
impersonation["started_at"] ||
-
impersonation[:started_at]
-
-
2
original_user = User.find_by(id: original_user_id)
-
2
target_user = User.find_by(id: target_user_id)
-
-
2
then: 2
if valid_impersonation?(original_user, target_user, started_at)
-
2
set_impersonated_user(original_user, target_user)
-
else: 0
else
-
end_impersonation(session)
-
end
-
end
-
-
16
def valid_impersonation?(original_user, target_user, started_at)
-
failure_reasons = {
-
2
then: 2
else: 0
original_not_admin: !original_user&.admin?,
-
no_target_user: !target_user,
-
missing_time: !started_at,
-
too_old: started_at && started_at.to_time < 12.hours.ago
-
8
}.select! { |k, v| !!v }.keys
-
-
2
then: 0
if failure_reasons.any?
-
then: 0
else: 0
if request
-
flash[:alert] = "Impersonation failed: #{failure_reasons.join(", ")}"
-
end
-
false
-
else: 2
else
-
2
true
-
end
-
end
-
-
16
def set_impersonated_user(original_user, target_user)
-
2
Current.user = target_user
-
2
Current.impersonator = original_user
-
end
-
-
16
def set_regular_user(user_id)
-
288
user = User.find_by(id: user_id)
-
288
then: 288
if user
-
288
Current.user = user
-
288
Current.impersonator = nil
-
else: 0
else
-
clear_current_user
-
end
-
end
-
-
16
def clear_current_user
-
155
Current.user = nil
-
155
Current.impersonator = nil
-
end
-
-
16
def end_impersonation(session)
-
session.delete(:impersonation)
-
set_regular_user(session[:user_id])
-
end
-
-
16
def require_login
-
444
then: 289
else: 155
then: 440
else: 4
return if Current.user&.confirmed? || allowed_path?
-
-
4
then: 0
if Current.user && !Current.user.confirmed?
-
handle_unconfirmed_user
-
else: 4
else
-
4
redirect_to_login
-
end
-
end
-
-
16
def allowed_path?
-
[
-
155
login_path,
-
logout_path,
-
register_path,
-
not_confirmed_path,
-
resend_confirmation_path,
-
confirm_registration_path,
-
confirm_registration_submit_path
-
].include?(request.path)
-
end
-
-
16
def handle_unconfirmed_user
-
else: 0
then: 0
unless [not_confirmed_path, resend_confirmation_path].include?(request.path)
-
flash[:alert] = "Your account is not confirmed"
-
redirect_to not_confirmed_path
-
end
-
end
-
-
16
def redirect_to_login
-
4
flash[:alert] = "You must be logged in"
-
4
redirect_to login_url
-
end
-
-
16
def build_nav_tabs
-
shares_name =
-
204
then: 134
else: 70
(Current.user.admin? || Current.user.organiser?) ? "Sharing" : "Shares"
-
[
-
204
{display_name: "Events", url: events_path},
-
{display_name: "Bands", url: bands_path},
-
{display_name: "Members", url: members_path},
-
{display_name: shares_name, url: permissions_path}
-
]
-
end
-
end
-
14
class BandsController < ApplicationController
-
14
include AccessPermissions
-
14
include EventsHelper
-
-
14
before_action :set_events, except: %i[index new create]
-
14
before_action :set_view, only: %i[show edit update confirm_destroy]
-
14
before_action :verify_organiser, only: %i[new create]
-
14
before_action :verify_organiser_or_admin, only: %i[confirm_destroy destroy]
-
-
14
def index
-
8
@bands = sort_bands(@bands, params[:sort])
-
-
8
bands_count = @bands.to_a.size
-
-
8
then: 1
if bands_count == 0
-
1
else: 7
redirect_to action: :new
-
7
then: 1
else: 6
elsif Current.user.member? && bands_count == 1
-
1
redirect_to @bands.first
-
end
-
end
-
-
14
def edit
-
end
-
-
14
def show
-
end
-
-
14
def confirm_destroy
-
@view = "overview"
-
end
-
-
14
def new
-
1
@band = Band.new
-
end
-
-
14
def create
-
2
@band = Band.new(band_params)
-
-
begin
-
2
Band.transaction do
-
2
@band.save!
-
2
Permission.create!(
-
item_type: "Band",
-
item_id: @band.id,
-
user_id: Current.user.id,
-
status: :owned,
-
permission_type: :edit
-
)
-
end
-
1
redirect_to @band, notice: "Band was successfully created"
-
rescue ActiveRecord::RecordInvalid
-
1
render :new, status: :unprocessable_entity
-
end
-
end
-
-
14
def update
-
2
then: 1
if @band.update(band_params)
-
1
redirect_to @band, notice: "Band was successfully updated."
-
else: 1
else
-
1
render :edit
-
end
-
end
-
-
14
def destroy
-
2
then: 1
if @band.destroy
-
1
redirect_to bands_url, notice: "Band deleted!"
-
else: 1
else
-
1
redirect_to @band, alert: @band.errors.full_messages.to_sentence
-
end
-
end
-
-
14
private
-
-
14
def set_events
-
7
@events = @band.events
-
7
.then { |rel| filter_by_period(rel, params[:period]) }
-
7
.then { |rel| sort_results(rel, params[:sort]) }
-
end
-
-
14
def set_view
-
5
@views = %w[overview events shares]
-
5
@views_subtitles = [nil, "(#{@band.events.count})", nil]
-
@view =
-
5
then: 0
@views.include?(params["view"]) ?
-
else: 5
params["view"] :
-
"overview"
-
end
-
-
14
def verify_organiser
-
4
else: 3
then: 1
redirect_to bands_url unless Current.user.organiser?
-
end
-
-
14
def verify_organiser_or_admin
-
2
else: 2
then: 0
redirect_to bands_url unless Current.user.organiser? || Current.user.admin?
-
end
-
-
14
def sort_bands(bands, sort_param)
-
8
then: 4
else: 4
sort_by, sort_order = sort_param&.split
-
8
then: 3
if sort_by.present? && Band.column_names.include?(sort_by)
-
3
then: 2
else: 1
bands.order(sort_by => ((sort_order == "DESC") ? :desc : :asc))
-
else: 5
else
-
5
bands.order(name: :asc)
-
end
-
end
-
-
14
def band_params
-
4
params.require(:band).permit(:name, :description, member_ids: [])
-
end
-
end
-
16
module AccessPermissions
-
16
extend ActiveSupport::Concern
-
-
16
included do
-
41
before_action :get_resources
-
41
before_action :set_resource, except: %i[index new create]
-
41
before_action :deny_read_only, only: %i[edit update destroy]
-
end
-
-
16
private
-
-
16
def get_resources
-
211
resource_class = controller_name.classify.constantize
-
@resources =
-
211
then: 19
Current.user.admin? ?
-
else: 192
resource_class.all :
-
Current.user.send(controller_name)
-
211
instance_variable_set(:"@#{controller_name}", @resources)
-
end
-
-
16
def set_resource
-
33
@resource = @resources.find(params[:id])
-
30
@read_only = @resource.permission_type != "edit"
-
30
instance_variable_set(:"@#{controller_name.singularize}", @resource)
-
end
-
-
16
def deny_read_only
-
26
then: 5
else: 21
raise ActiveRecord::RecordNotFound if @read_only
-
end
-
end
-
16
class EventsController < ApplicationController
-
16
include AccessPermissions
-
16
include EventsHelper
-
-
16
before_action :set_bands, only: %i[new edit create update]
-
16
before_action :set_view, only: %i[show edit update]
-
-
16
def index
-
154
@events = @events
-
154
.then { |rel| filter_by_period(rel, params[:period]) }
-
154
.then { |rel| filter_by_band(rel, params[:band_id]) }
-
154
.then { |rel| sort_results(rel, params[:sort]) }
-
end
-
-
16
def new
-
3
@event = Event.new
-
-
3
param_band_id = Integer(params[:band_id], exception: false)
-
3
else: 1
then: 2
param_band_id = nil unless @bands.collect(&:id).include?(param_band_id)
-
3
then: 1
else: 2
@event.band_ids = [param_band_id] if param_band_id
-
end
-
-
16
def show
-
end
-
-
16
def edit
-
end
-
-
16
def create
-
5
@event = Event.new(event_params)
-
-
4
then: 3
if @event.save
-
3
create_owner_permission(@event)
-
3
redirect_to @event, notice: "Event was successfully created"
-
else: 1
else
-
1
render :new, status: :unprocessable_entity
-
end
-
end
-
-
16
def update
-
10
then: 8
if @event.update(event_params)
-
8
redirect_to @event, notice: "Event was successfully updated."
-
else: 1
else
-
1
render :edit, status: :unprocessable_entity
-
end
-
end
-
-
16
def destroy
-
2
@event.destroy!
-
1
redirect_to events_url, notice: "Event was successfully destroyed."
-
rescue ActiveRecord::RecordNotDestroyed
-
1
redirect_to @event, alert: @event.errors.full_messages.to_sentence
-
end
-
-
16
private
-
-
16
def set_bands
-
18
@bands = Current.user.bands
-
end
-
-
16
def set_view
-
11
@views = %w[overview bands shares]
-
11
@views_subtitles = [nil, "(#{@event.bands.count})", nil]
-
@view =
-
11
then: 0
@views.include?(params["view"]) ?
-
else: 11
params["view"] :
-
"overview"
-
end
-
-
16
def event_params
-
15
permitted = params.require(:event).permit(
-
:name,
-
:description,
-
:start_date,
-
:end_date,
-
band_ids: []
-
)
-
-
15
permitted_band_ids = @bands.pluck(:id)
-
-
15
permitted.tap do |params|
-
15
params[:band_ids] = params[:band_ids].to_a.compact_blank.map(&:to_i)
-
15
invalid_bands = params[:band_ids] - permitted_band_ids
-
15
then: 1
else: 14
if invalid_bands.any?
-
1
raise ArgumentError, "Invalid band_ids: #{invalid_bands}"
-
end
-
end
-
end
-
-
16
def create_owner_permission(event)
-
3
event.permissions.create!(
-
user: Current.user,
-
status: :owned,
-
permission_type: :edit
-
)
-
end
-
end
-
11
class MembersController < ApplicationController
-
11
include AccessPermissions
-
11
include EventsHelper
-
-
11
before_action :set_member_events, only: :show
-
11
before_action :set_view, only: :show
-
-
11
def index
-
# Get skills for the current set of members
-
@skills =
-
1
Skill.joins(:members)
-
.where(members: {id: @members.pluck(:id)})
-
.distinct
-
.order(:name)
-
-
# Filter members by skill if skill_id parameter is present
-
1
then: 0
else: 1
if params[:skill_id].present?
-
@skill = Skill.find(params[:skill_id])
-
@members =
-
@members
-
.joins(:skills)
-
.where(skills: {id: params[:skill_id]})
-
end
-
end
-
-
11
def edit
-
end
-
-
11
def show
-
end
-
-
11
def new
-
1
@member = Member.new
-
end
-
-
11
def create
-
2
@member = Member.new(member_params)
-
2
else: 1
then: 1
return render :new, status: :unprocessable_entity unless @member.save
-
-
1
@permission = Permission.create(
-
item_type: "Member",
-
item_id: @member.id,
-
user: Current.user,
-
status: :owned,
-
permission_type: "edit"
-
)
-
1
else: 1
then: 0
return render :new, status: :unprocessable_entity unless @permission.save
-
-
1
redirect_to @member, notice: "Member was successfully created."
-
end
-
-
11
def update
-
2
Member.transaction do
-
2
@member.assign_attributes(member_params)
-
2
then: 1
if @member.save
-
1
redirect_to @member, notice: "Member was successfully updated."
-
else: 1
else
-
1
logger.warn "members/update error: #{@member.errors.full_messages}"
-
1
render :edit, status: :unprocessable_entity
-
end
-
end
-
rescue ActiveRecord::RecordInvalid => e
-
logger.warn "members/update transaction error: #{e.message}"
-
render :edit, status: :unprocessable_entity
-
end
-
-
11
def destroy
-
1
@member.destroy!
-
1
redirect_to members_url, notice: "Member was successfully destroyed."
-
end
-
-
11
private
-
-
11
def member_params
-
4
params.require(:member).permit(
-
:name, :description, :skills_list, band_ids: []
-
)
-
end
-
-
11
def set_view
-
1
@views = %w[overview events]
-
1
@views_subtitles = [nil, "(#{@events.count})"]
-
@view =
-
1
then: 0
@views.include?(params["view"]) ?
-
else: 1
params["view"] :
-
"overview"
-
end
-
-
11
def set_member_events
-
1
@events = @member.events
-
1
.then { |rel| filter_by_period(rel, params[:period]) }
-
1
.then { |rel| sort_results(rel, params[:sort]) }
-
end
-
end
-
13
class PermissionsController < ApplicationController
-
13
include PermissionsHelper
-
-
13
before_action :set_permission, only: [:update, :destroy]
-
13
before_action :organiser_only, only: [:new, :create]
-
13
before_action :invitee_and_pending_only, only: [:update]
-
13
before_action :bestower_and_admin_only, only: [:destroy]
-
13
before_action :set_form_options, only: [:new, :create]
-
-
13
def index
-
6
then: 1
else: 5
@permissions = Current.user.admin? ? Permission.all : Permission.for_user(Current.user)
-
6
@permissions = sort_permissions(@permissions, params[:direct_sort])
-
-
6
@other_items = fetch_effective_permissions
-
6
@other_items = sort_effective_items(@other_items, params[:effective_sort])
-
-
6
respond_to do |format|
-
6
format.html
-
6
format.json { render json: {permissions: @permissions, effective: @other_items} }
-
end
-
end
-
-
13
def new
-
1
@permission = Permission.new
-
-
1
then: 0
else: 1
if params[:item_type].present? && params[:item_id].present?
-
@preselected_item = "#{params[:item_type].capitalize}_#{params[:item_id]}"
-
end
-
-
1
@no_users_available = potential_users.empty?
-
end
-
-
13
def create
-
5
params = permission_create_params
-
5
item_param = params.delete(:item)
-
-
5
@permission = Permission.new(params)
-
5
@permission.bestowing_user = Current.user
-
5
@permission.status = "pending"
-
5
@permission.item_type, @permission.item_id = item_param.split("_")
-
5
then: 4
else: 1
@permission.item_type&.capitalize!
-
-
5
else: 1
then: 2
return render :new, status: :unprocessable_entity unless @permission.save
-
-
1
redirect_to permissions_path, notice: "Invitation created"
-
end
-
-
13
def update
-
3
status = permission_update_params[:status]
-
-
3
then: 2
if @permission.update(status: status)
-
2
then: 1
if status == "accepted"
-
1
redirect_to @permission.item_path, notice: "Invitation accepted"
-
else: 1
else
-
1
redirect_to permissions_path, alert: "Invitation rejected"
-
end
-
else: 0
else
-
render plain: "Not updated", status: :bad_request
-
end
-
end
-
-
13
def destroy
-
2
@permission.destroy!
-
2
redirect_to root_path, notice: "Invitation deleted"
-
end
-
-
13
private
-
-
13
def sort_permissions(permissions, sort_param)
-
6
then: 6
else: 0
return permissions.order(created_at: :desc) if sort_param.blank?
-
-
column, direction = sort_param.split
-
then: 0
else: 0
then: 0
else: 0
direction_sym = (direction&.downcase == "desc") ? :desc : :asc
-
-
case column
-
when: 0
when "name"
-
joins_sql = <<~SQL
-
LEFT JOIN bands
-
ON bands.id = permissions.item_id
-
AND permissions.item_type = 'Band'
-
LEFT JOIN events
-
ON events.id = permissions.item_id
-
AND permissions.item_type = 'Event'
-
LEFT JOIN members
-
ON members.id = permissions.item_id
-
AND permissions.item_type = 'Member'
-
SQL
-
permissions.joins(joins_sql)
-
.order(Arel.sql(
-
"COALESCE(bands.name, events.name, members.name) #{direction_sym}"
-
))
-
when: 0
when "type"
-
permissions.order(item_type: direction_sym)
-
when: 0
when "access"
-
permissions.order(permission_type: direction_sym)
-
when: 0
when "status"
-
permissions.order(status: direction_sym)
-
when: 0
when "bestower"
-
permissions.joins(:bestowing_user).order(Arel.sql(
-
"users.username #{direction_sym}"
-
))
-
when: 0
when "created"
-
permissions.order(created_at: direction_sym)
-
when: 0
when "last_modified"
-
permissions.order(updated_at: direction_sym)
-
else: 0
else
-
permissions.order(created_at: :desc)
-
end
-
end
-
-
13
def sort_effective_items(items, sort_param)
-
10
then: 6
else: 0
return items.sort_by { |item| item.name.downcase } if sort_param.blank?
-
-
column, direction = sort_param.split
-
then: 0
else: 0
desc = (direction&.downcase == "desc")
-
-
sorted_items = case column
-
when: 0
when "name"
-
items.sort_by { |item| item.name.downcase }
-
when: 0
when "type"
-
items.sort_by { |item| item.class.name }
-
when: 0
when "status"
-
items.sort_by { |item| item.permission_type.to_s }
-
when: 0
when "source"
-
items.sort_by do |item|
-
permission = find_effective_permission_source(Current.user, item)
-
then: 0
if permission.nil?
-
"AAA_Direct" # Sort direct permissions first
-
else: 0
else
-
"#{permission.item_type}_#{permission.item.name}"
-
end
-
end
-
else: 0
else
-
items.sort_by { |item| item.name.downcase }
-
end
-
-
then: 0
else: 0
desc ? sorted_items.reverse : sorted_items
-
end
-
-
13
def fetch_effective_permissions
-
events =
-
6
Event.permitted_for(Current.user.id)
-
.includes(:bands, :permissions)
-
-
bands =
-
6
Band.permitted_for(Current.user.id)
-
.includes(:members, :events, :permission)
-
-
members =
-
6
Member.permitted_for(Current.user.id)
-
.includes(:bands, :skills)
-
-
6
events + bands + members
-
end
-
-
13
def organiser_only
-
8
else: 6
then: 2
unless Current.user.organiser?
-
2
render plain: "Organisers only", status: :forbidden
-
end
-
end
-
-
13
def bestower_and_admin_only
-
3
else: 2
then: 1
unless Current.user.admin? || @permission.bestowing_user == Current.user
-
1
render plain: "Bestower only", status: :forbidden
-
end
-
end
-
-
13
def invitee_and_pending_only
-
7
then: 3
if @permission.user != Current.user
-
3
else: 4
render plain: "Invitee only", status: :forbidden
-
4
then: 1
else: 3
elsif !@permission.pending?
-
1
render plain: "Invite not pending", status: :bad_request
-
end
-
end
-
-
13
def set_form_options
-
6
@users = potential_users
-
6
@items = potential_items(Current.user)
-
6
@permission_types = %w[view edit]
-
end
-
-
13
def set_permission
-
10
@permission = Permission.find(params[:id])
-
end
-
-
13
def permission_create_params
-
5
params.require(:permission).permit(:user_id, :item, :permission_type)
-
end
-
-
13
def permission_update_params
-
3
params.require(:permission).permit(:status)
-
end
-
end
-
16
class SessionsController < ApplicationController
-
16
def new
-
5
then: 3
else: 2
redirect_to events_path if Current.user
-
5
@user = User.new
-
end
-
-
16
def create
-
143
username, password = login_params.values_at(:username, :password)
-
142
logger.debug("Login attempt from #{username}")
-
142
user = User.find_or_create_by(username: username)
-
-
142
then: 1
if Rails.env.development? && params.dig(:user, :debug_skip)
-
1
session[:user_id] = user.id
-
1
logger.debug("Skipped login: #{username}")
-
1
else: 141
redirect_to events_path, notice: "Skipped login."
-
141
then: 139
elsif user.authenticate(password)
-
139
session[:user_id] = user.id
-
139
logger.debug("Login success: #{username}")
-
139
redirect_to events_path, notice: "Logged in successfully."
-
else: 2
else
-
2
@user = User.new
-
2
@user.username = username
-
2
logger.debug "Login failed: #{user.inspect}"
-
2
flash[:alert] = "Invalid username or password"
-
2
render :new, status: :unprocessable_entity
-
end
-
end
-
-
16
def destroy
-
1
session[:user_id] = nil
-
1
session[:impersonation] = nil
-
1
redirect_to login_path, notice: "Logged out successfully."
-
end
-
-
16
private
-
-
16
def login_params
-
143
params.require(:user).permit(:username, :password)
-
end
-
end
-
9
class UserMailsController < ApplicationController
-
9
before_action :set_mail, only: :show
-
9
before_action :can_view_mail, only: :show
-
-
9
def show
-
end
-
-
9
def index
-
@mails =
-
3
then: 1
if Current.user.admin?
-
1
UserMail.order(updated_at: :desc)
-
else: 2
else
-
2
UserMail.where(user_id: Current.user.id).order(updated_at: :desc)
-
end
-
end
-
-
9
def create
-
2
@mail = UserMailer.send_confirmation_registration(Current.user)
-
-
2
then: 1
if @mail.persisted?
-
1
redirect_to user_mails_path, notice: "Confirmation sent successfully"
-
else: 1
else
-
1
redirect_to user_mails_path, alert: "Failed to send confirmation"
-
end
-
end
-
-
9
private
-
-
9
def set_mail
-
5
@mail = UserMail.find(params[:id])
-
end
-
-
9
def can_view_mail
-
5
then: 3
else: 2
return if @mail && (@mail.user_id == Current.user.id || Current.user.admin?)
-
-
2
redirect_to user_mails_path, alert: "Cannot view this mail"
-
end
-
end
-
9
class Users::RegistrationController < ApplicationController
-
9
before_action :set_user, only: %i[
-
edit
-
update
-
]
-
-
9
def edit
-
end
-
-
9
def update
-
1
else: 0
then: 1
UserMailer.send_confirmation_registration(@user) unless @user.confirmed?
-
end
-
-
9
def new
-
4
@token = params[:token]
-
4
verifier = Rails.application.config.message_verifier
-
-
begin
-
4
data = verifier.verify(@token)
-
3
@user = User.find(data["user_id"])
-
3
then: 1
else: 2
if @user.confirmed?
-
1
redirect_to user_path(@user),
-
notice: "Your account is already confirmed"
-
end
-
-
3
then: 1
else: 2
then: 1
else: 2
if data["expires_at"]&.< Time.current.to_i
-
1
flash[:alert] = "Your confirmation link has expired: #{data}."
-
1
redirect_to register_path
-
end
-
rescue ActiveSupport::MessageVerifier::InvalidSignature
-
1
flash[:alert] = "Invalid confirmation link."
-
1
redirect_to register_path
-
end
-
end
-
-
9
def create
-
3
token = params[:token]
-
3
verifier = Rails.application.config.message_verifier
-
-
begin
-
3
data = verifier.verify(token)
-
-
2
then: 2
else: 0
then: 1
if data["expires_at"]&.>= Time.current.to_i
-
1
@user = User.find(data["user_id"])
-
1
@user.update(confirmed: true)
-
1
flash[:notice] = "Your registration has been confirmed."
-
1
redirect_to login_path
-
else: 1
else
-
1
flash[:alert] = "Your confirmation link has expired: #{data}."
-
1
redirect_to register_path
-
end
-
rescue ActiveSupport::MessageVerifier::InvalidSignature
-
1
flash[:alert] = "Invalid confirmation link."
-
1
redirect_to register_path
-
end
-
end
-
-
9
private
-
-
9
def set_user
-
2
@user = User.find(session[:user_id])
-
2
then: 1
else: 1
redirect_to user_path(@user) if @user.confirmed?
-
end
-
end
-
8
class UsersController < ApplicationController
-
8
before_action :check_admin_user, only: %i[
-
create
-
new
-
]
-
-
8
before_action :check_not_logged_in, only: %i[
-
create
-
new
-
]
-
-
8
def new
-
1
@user = User.new
-
end
-
-
8
def create
-
4
@user = User.new(create_user_params)
-
-
4
then: 1
else: 3
if @user.admin? && !@allow_admin_user
-
1
flash.now[:alert] = "No more admin users allowed"
-
1
return render :new, status: :unprocessable_entity
-
end
-
-
3
then: 2
if @user.save
-
2
session[:user_id] = @user.id
-
2
redirect_to account_path, notice: "You registered successfully. Well done!"
-
else: 1
else
-
1
render :new, status: :unprocessable_entity
-
end
-
end
-
-
8
def index
-
1
redirect_to user_path(Current.user)
-
end
-
-
8
def show
-
4
@user = User.find_by(username: params[:id])
-
4
else: 4
then: 0
redirect_to user_path(Current.user) unless @user
-
end
-
-
8
def edit
-
1
@user = Current.user
-
1
else: 0
then: 1
unless params[:id] == @user.username
-
1
redirect_to user_path(@user), alert: "Can't edit other users"
-
end
-
end
-
-
8
def update
-
7
then: 6
else: 1
if params[:user][:password].blank?
-
6
params[:user].delete(:password)
-
6
params[:user].delete(:password_confirmation)
-
end
-
-
7
@user = Current.user
-
7
then: 5
if @user.update(update_user_params)
-
5
redirect_to user_url(@user), notice: "Updated successfully"
-
else: 2
else
-
2
errors = @user.errors.full_messages.join(", ").downcase
-
2
render plain: "Could not update user: #{errors}",
-
status: :forbidden
-
end
-
end
-
-
8
private
-
-
8
def create_user_params
-
4
params.require(:user).permit(
-
:username,
-
:name,
-
:email,
-
:password,
-
:password_confirmation,
-
:time_zone,
-
:user_type
-
)
-
end
-
-
8
def update_user_params
-
7
params.require(:user).permit(
-
:name,
-
:email,
-
:password,
-
:password_confirmation,
-
:time_zone
-
)
-
end
-
-
8
def check_admin_user
-
6
@allow_admin_user = User.admin.count.zero?
-
end
-
-
8
def check_not_logged_in
-
6
else: 5
then: 1
redirect_to account_path unless Current.user.nil?
-
end
-
end
-
1
module ApplicationHelper
-
1
def page_heading(str)
-
171
content_tag(:h1, str, class: "text-gray-900 text-xl")
-
end
-
-
1
def filter_link(name, path, css_class, active)
-
464
link_to_if(!active, name, path, class: "#{css_class} hover:underline") do
-
154
content_tag(:span, name, class: "#{css_class} font-bold")
-
end
-
end
-
-
1
def table_headers(sort_param_name, columns, resource = nil)
-
9
query_params = request.query_parameters.merge({}).except(sort_param_name)
-
-
9
capture do
-
9
columns.each do |column|
-
46
concat(
-
content_tag(:th,
-
table_header_sort(
-
resource || controller_name,
-
column[:name],
-
column[:display],
-
column[:default],
-
query_params,
-
sort_param_name
-
),
-
class: column[:class],
-
46
then: 13
else: 33
style: column[:wide] ? nil : "width: 1%")
-
)
-
end
-
end
-
end
-
-
1
def table_header_sort(
-
resource,
-
column,
-
display_text,
-
default_sort_column = nil,
-
request_params = nil,
-
sort_param_name = :sort,
-
default_sort_direction = "asc"
-
)
-
282
sort_param = sort_param_name.to_sym
-
282
then: 32
else: 250
current_sort_column, current_sort_direction = params[sort_param]&.split
-
282
current_sort_column ||= default_sort_column
-
282
current_sort_direction ||= default_sort_direction
-
-
282
then: 103
if current_sort_column == column
-
103
then: 97
else: 6
new_sort_direction = (current_sort_direction == "asc") ? "desc" : "asc"
-
103
sort_icon = sort_direction_icon(current_sort_direction)
-
else: 179
else
-
179
new_sort_direction = default_sort_direction
-
179
sort_icon = ""
-
end
-
-
282
query_params = request_params || request.query_parameters
-
-
282
new_params = query_params
-
.except(:page, sort_param)
-
.merge(sort_param => "#{column} #{new_sort_direction}")
-
-
# Preserve period parameter if it exists
-
282
then: 55
else: 227
new_params[:period] = request.params[:period] if request.params[:period]
-
-
282
sort_url = url_for(new_params)
-
-
282
link_to "#{display_text}#{sort_icon}".html_safe, sort_url
-
end
-
-
1
def flash_banner(key, msg = nil, &block)
-
159
content_tag :div, class: "#{key} banner" do
-
159
then: 6
else: 153
if block && msg.blank?
-
6
msg = capture(&block)
-
end
-
-
159
concat(content_tag(:div, msg))
-
159
concat(hidden_checkbox_tag(key))
-
159
concat(close_button(key))
-
end
-
end
-
-
1
private
-
-
1
def sort_direction_icon(direction)
-
103
when: 97
case direction
-
97
when: 1
when "asc" then " <span>â–¼</span>"
-
1
else: 5
when "desc" then " <span>â–²</span>"
-
5
else ""
-
end
-
end
-
-
1
def hidden_checkbox_tag(key)
-
159
tag.input type: "checkbox", class: "hidden", id: "banneralert-#{key}"
-
end
-
-
1
def close_button(key)
-
159
tag.label class: "close", style: "width:30px", for: "banneralert-#{key}" do
-
159
tag.svg class: "fill-current h-6 w-6", role: "button", xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 20 20" do
-
159
concat(tag.title("Close"))
-
159
concat(tag.path(d: "M14.348 14.849a1.2 1.2 0 0 1-1.697 0L10 11.819l-2.651 3.029a1.2 1.2 0 1 1-1.697-1.697l2.758-3.15-2.759-3.152a1.2 1.2 0 1 1 1.697-1.697L10 8.183l2.651-3.031a1.2 1.2 0 1 1 1.697 1.697l-2.758 3.152 2.758 3.15a1.2 1.2 0 0 1 0 1.698z"))
-
end
-
end
-
end
-
end
-
1
module EventsHelper
-
1
def filter_by_period(events, period)
-
162
when: 1
case period
-
1
when: 152
when "past" then events.past
-
152
else: 9
when "future", nil then events.future
-
9
else events
-
end
-
end
-
-
1
def filter_by_band(events, band_id)
-
154
else: 1
then: 153
return events unless band_id.present? && band_id.to_i.positive?
-
1
events.joins(:bands).where(bands: {id: band_id})
-
end
-
-
1
def sort_results(events, sort_param)
-
162
column, direction = extract_sort_params(sort_param)
-
162
else: 161
then: 1
unless Event.attribute_names.include?(column.to_s)
-
1
return events.order(:start_date)
-
end
-
161
events.order(column => direction)
-
end
-
-
1
def extract_sort_params(sort_param)
-
162
col, dir = sort_param.to_s.split
-
162
then: 3
else: 159
dir = %w[desc DESC].include?(dir) ? :desc : :asc
-
162
[col.presence || :start_date, dir]
-
end
-
-
1
def convert_seconds_to_duration(seconds)
-
9
then: 2
else: 7
return nil if seconds < 60
-
-
7
days = (seconds / 86400).floor
-
7
remaining = seconds % 86400
-
-
7
hours = (remaining / 3600).floor
-
7
remaining %= 3600
-
-
7
minutes = (remaining / 60).floor
-
-
7
parts = []
-
7
then: 5
else: 2
else: 4
then: 1
parts << "#{days} day#{"s" unless days == 1}" if days > 0
-
7
then: 2
else: 5
else: 1
then: 1
parts << "#{hours} hour#{"s" unless hours == 1}" if hours > 0
-
7
then: 4
else: 3
else: 1
then: 3
parts << "#{minutes} minute#{"s" unless minutes == 1}" if minutes > 0
-
-
7
when: 4
case parts.size
-
4
when 1 then parts.first
-
else: 3
else
-
3
parts[0...-1].join(", ") + " and #{parts.last}"
-
end
-
end
-
end
-
1
module MembersHelper
-
end
-
1
module PermissionsHelper
-
1
include ActionView::Helpers::UrlHelper
-
-
1
def potential_users
-
# Get all non-admin users (both members and organisers) sorted by username,
-
# excluding current user
-
users =
-
7
User
-
.where
-
.not(user_type: :admin)
-
.where
-
.not(id: Current.user.id)
-
.order(:username)
-
.pluck(:username, :id)
-
-
7
if users.any?
-
then: 7
# Add a "Please select" option with empty value as the first option
-
7
[["Please select", ""]] + users
-
else: 0
else
-
[]
-
end
-
end
-
-
1
def potential_items(owner)
-
74
invitable_klasses = [Event, Band, Member]
-
74
invitable_klasses.flat_map do |klass|
-
222
pluck_and_prefix(klass, owner)
-
end
-
end
-
-
1
def permission_status_color(status)
-
case status.to_s
-
when: 0
when 'owned'
-
'primary'
-
when: 0
when 'accepted'
-
'success'
-
when: 0
when 'pending'
-
'warning'
-
when: 0
when 'rejected'
-
'danger'
-
else: 0
else
-
'secondary'
-
end
-
end
-
-
1
def get_access_level(user, item)
-
19
when: 8
case item
-
8
when: 6
when Member then get_ownership(user, item, :members, Member)
-
6
when: 5
when Band then get_ownership(user, item, :bands, Band)
-
5
else: 0
when Event then get_ownership(user, item, :events, Event)
-
else raise "Invalid item type: #{item.class}"
-
end
-
end
-
-
1
def get_ownership(user, item, method, klass)
-
19
then: 18
if (direct_permission = user.send(method).find_by(id: item.id))
-
18
else: 1
direct_permission[:permission_type]
-
1
then: 0
else: 1
elsif klass.permitted_for(user.id).find_by(id: item.id)
-
"view"
-
end
-
end
-
-
1
def find_effective_permission_source(user, item)
-
# Direct permission check
-
4
direct_permission = Permission.where(
-
user_id: user.id,
-
item_type: item.class.to_s,
-
item_id: item.id,
-
status: ['owned', 'accepted']
-
).first
-
-
4
then: 4
else: 0
return nil if direct_permission
-
-
else: 0
case item
-
when: 0
when Band
-
find_band_permission_source(user, item)
-
when: 0
when Event
-
find_event_permission_source(user, item)
-
when: 0
when Member
-
find_member_permission_source(user, item)
-
end
-
end
-
-
1
def find_band_permission_source(user, band)
-
# Check permissions through members
-
band.members.each do |member|
-
permission = Permission.where(
-
user_id: user.id,
-
item_type: 'Member',
-
item_id: member.id,
-
status: ['owned', 'accepted']
-
).first
-
-
then: 0
else: 0
return permission if permission
-
end
-
-
# Check permissions through events
-
band.events.each do |event|
-
permission = Permission.where(
-
user_id: user.id,
-
item_type: 'Event',
-
item_id: event.id,
-
status: ['owned', 'accepted']
-
).first
-
-
then: 0
else: 0
return permission if permission
-
end
-
-
nil
-
end
-
-
1
def find_event_permission_source(user, event)
-
# Check permissions through bands
-
event.bands.each do |band|
-
permission = Permission.where(
-
user_id: user.id,
-
item_type: 'Band',
-
item_id: band.id,
-
status: ['owned', 'accepted']
-
).first
-
-
then: 0
else: 0
return permission if permission
-
end
-
-
# Check permissions through members in bands
-
event.bands.each do |band|
-
band.members.each do |member|
-
permission = Permission.where(
-
user_id: user.id,
-
item_type: 'Member',
-
item_id: member.id,
-
status: ['owned', 'accepted']
-
).first
-
-
then: 0
else: 0
return permission if permission
-
end
-
end
-
-
nil
-
end
-
-
1
def find_member_permission_source(user, member)
-
# Check permissions through bands
-
member.bands.each do |band|
-
permission = Permission.where(
-
user_id: user.id,
-
item_type: 'Band',
-
item_id: band.id,
-
status: ['owned', 'accepted']
-
).first
-
-
then: 0
else: 0
return permission if permission
-
end
-
-
# Check permissions through events via bands
-
member.bands.each do |band|
-
band.events.each do |event|
-
permission = Permission.where(
-
user_id: user.id,
-
item_type: 'Event',
-
item_id: event.id,
-
status: ['owned', 'accepted']
-
).first
-
-
then: 0
else: 0
return permission if permission
-
end
-
end
-
-
nil
-
end
-
-
1
private
-
-
1
def pluck_and_prefix(klass, owner)
-
222
owned_permissions = owner.send(:"#{klass.to_s.downcase.pluralize}")
-
222
owned_item_ids = owned_permissions.pluck(:id)
-
222
items = klass.where(id: owned_item_ids).pluck(:id, :name)
-
222
items.map do |id, name|
-
84
["#{name} (#{klass})", "#{klass}_#{id}"]
-
end
-
end
-
end
-
1
module SessionsHelper
-
end
-
1
module Users::RegistrationHelper
-
end
-
1
class ApplicationJob < ActiveJob::Base
-
# Automatically retry jobs that encountered a deadlock
-
# retry_on ActiveRecord::Deadlocked
-
-
# Most jobs are safe to ignore if the underlying records are no longer available
-
# discard_on ActiveJob::DeserializationError
-
end
-
1
class SendMailJob < ApplicationJob
-
1
queue_as :default
-
1
retry_on StandardError, wait: 1.minute, attempts: 5
-
-
1
def perform(user_mail_id)
-
user_mail = UserMail.find(user_mail_id)
-
user_mail.attempt_send
-
end
-
end
-
13
module FormBuilders
-
13
class NiceFormBuilder < ActionView::Helpers::FormBuilder
-
13
class_attribute :text_field_helpers,
-
default: field_helpers - %i[
-
label
-
check_box
-
radio_button
-
fields_for
-
fields
-
hidden_field
-
file_field
-
]
-
-
13
TEXT_FIELD_STYLE = "text_field".freeze
-
13
TEXT_AREA_STYLE = "textarea_field".freeze
-
13
SELECT_FIELD_STYLE = "select_field".freeze
-
13
SUBMIT_BUTTON_STYLE = "primary_button".freeze
-
13
CHECKBOX_FIELD_STYLE = "checkbox_group".freeze
-
13
DATE_SELECT_STYLE = "date_select".freeze
-
13
TIME_SELECT_STYLE = "time_select".freeze
-
-
13
text_field_helpers.each do |field_method|
-
234
define_method(field_method) do |method, options = {}|
-
104
then: 52
if options.delete(:already_nice)
-
52
super(method, options)
-
else: 52
else
-
52
text_like_field(field_method, method, options)
-
end
-
end
-
end
-
-
13
def submit(value = nil, options = {})
-
27
custom_opts, opts = partition_custom_opts(options)
-
27
classes = apply_style_classes(SUBMIT_BUTTON_STYLE, custom_opts)
-
-
27
super(value, {class: classes}.merge(opts))
-
end
-
-
13
def select(method, choices = nil, options = {}, html_options = {}, &block)
-
15
custom_opts, opts = partition_custom_opts(options)
-
15
classes = apply_style_classes(SELECT_FIELD_STYLE, custom_opts, method)
-
-
15
labels = labels(method, custom_opts[:label], options)
-
15
field = super(
-
method,
-
choices,
-
opts,
-
html_options.merge({class: classes}),
-
&block
-
)
-
-
15
@template.content_tag("div", labels + field, {class: "field"})
-
end
-
-
13
def check_box(method, options = {}, checked_value = "1", unchecked_value = "0")
-
3
custom_opts, opts = partition_custom_opts(options)
-
3
classes = apply_style_classes(CHECKBOX_FIELD_STYLE, custom_opts, method)
-
-
3
labels = labels(method, custom_opts[:label], options)
-
3
field = super(method, opts.merge({class: classes}), checked_value, unchecked_value)
-
-
3
@template.content_tag("div", labels + field, {class: "field"})
-
end
-
-
13
def collection_checkboxes(
-
method,
-
collection,
-
value_method,
-
text_method,
-
options = {},
-
html_options = {},
-
&block
-
)
-
5
custom_opts, opts = partition_custom_opts(options)
-
-
5
classes = apply_style_classes(
-
CHECKBOX_FIELD_STYLE,
-
custom_opts,
-
method
-
)
-
-
5
labels = labels(method, custom_opts[:label], options)
-
-
5
html_options = html_options.merge(class: classes)
-
-
5
check_boxes = super(
-
method,
-
collection,
-
value_method,
-
text_method,
-
opts,
-
html_options,
-
&block
-
)
-
-
5
@template.content_tag(
-
"div",
-
labels + check_boxes,
-
{class: "field"}
-
)
-
end
-
-
13
def date_select(method, options = {}, html_options = {})
-
10
custom_opts, opts = partition_custom_opts(options)
-
10
classes = apply_style_classes(nil, custom_opts, method)
-
-
10
labels = labels(method, custom_opts[:label], options)
-
10
field = @template.content_tag(
-
"div",
-
super(
-
method,
-
opts,
-
html_options.merge(class: classes)
-
),
-
{class: "flex flex-row gap-4"}
-
)
-
-
10
field_classes = ["field"]
-
10
then: 10
else: 0
field_classes << custom_opts[:field_class] if custom_opts[:field_class]
-
-
10
@template.content_tag(
-
"div",
-
labels + field,
-
{class: field_classes.join(" ")}
-
)
-
end
-
-
13
def time_select(method, options = {}, html_options = {})
-
10
custom_opts, opts = partition_custom_opts(options)
-
10
classes = apply_style_classes(nil, custom_opts, method)
-
-
10
labels = labels(method, custom_opts[:label], options)
-
10
field = @template.content_tag(
-
"div",
-
super(
-
method,
-
opts,
-
html_options.merge(class: classes)
-
),
-
{class: "flex flex-row gap-4"}
-
)
-
-
10
field_classes = ["field"]
-
10
then: 10
else: 0
field_classes << custom_opts[:field_class] if custom_opts[:field_class]
-
-
10
@template.content_tag(
-
"div",
-
labels + field,
-
{class: field_classes.join(" ")}
-
)
-
end
-
-
13
private
-
-
13
def text_like_field(field_method, object_method, options = {})
-
52
custom_opts, opts = partition_custom_opts(options)
-
52
then: 0
else: 52
style = (field_method == :text_area) ? TEXT_AREA_STYLE : TEXT_FIELD_STYLE
-
52
classes = apply_style_classes(style, custom_opts, object_method)
-
-
field_options = {
-
52
class: classes,
-
then: 52
else: 0
title: errors_for(object_method)&.join(" ")
-
}.compact.merge(opts).merge(already_nice: true)
-
-
52
field = send(field_method, object_method, field_options)
-
52
labels = labels(object_method, custom_opts[:label], options)
-
-
52
@template.content_tag("div", labels + field, {class: "field"})
-
end
-
-
13
def labels(object_method, label_options, field_options)
-
95
label = nice_label(object_method, label_options, field_options)
-
95
error_label = error_label(object_method, field_options)
-
-
95
label + error_label
-
end
-
-
13
def nice_label(object_method, label_options, field_options)
-
text, label_opts =
-
99
then: 32
label_options.present? ?
-
else: 67
label_options.values_at(:text, :except) :
-
[nil, {}]
-
-
99
label_opts ||= {}
-
-
99
label_classes = label_opts[:class].to_s
-
99
then: 0
else: 99
label_classes += " disabled" if field_options[:disabled]
-
-
99
label(object_method, text, {
-
class: label_classes.strip
-
}.merge(label_opts.except(:class)))
-
end
-
-
13
def error_label(object_method, options)
-
95
errors = errors_for(object_method)
-
-
95
then: 91
else: 4
return if errors.blank?
-
-
4
error_message = errors.collect(&:titleize).join(", ")
-
-
4
nice_label(
-
object_method,
-
{text: error_message, class: "error_message"},
-
options
-
)
-
end
-
-
13
def border_color_classes(object_method)
-
122
else: 95
then: 27
return "" unless object_method
-
95
then: 4
else: 91
"has_error" if errors_for(object_method).present?
-
end
-
-
13
def apply_style_classes(base_class, custom_opts, object_method = nil)
-
[
-
122
base_class,
-
border_color_classes(object_method),
-
custom_opts[:class]
-
].compact.join(" ")
-
end
-
-
13
CUSTOM_OPTS = [:label, :class, :field_class].freeze
-
-
13
def partition_custom_opts(opts)
-
204
opts.partition { |k, _| CUSTOM_OPTS.include?(k) }.map(&:to_h)
-
end
-
-
13
def errors_for(object_method)
-
242
else: 242
then: 0
return unless @object.present? && object_method.present?
-
-
242
@object.errors[object_method]
-
end
-
end
-
end
-
8
class ApplicationMailer < ActionMailer::Base
-
8
layout "mailer"
-
end
-
class TestMailer < ApplicationMailer
-
def test_email(to)
-
Rails.logger.info "Preparing to send test email to #{to}."
-
-
mail(
-
to: to,
-
subject: "Test Email",
-
body: "This is a test email."
-
)
-
end
-
end
-
# app/mailers/user_mailer.rb
-
8
class UserMailer < ApplicationMailer
-
8
CONFIRM_REGISTRATION_SUBJECT = "Thank you for joining Libregig"
-
-
8
def confirm_registration(user_mail)
-
1
@username = user_mail.params["username"]
-
1
@url = user_mail.params["url"]
-
-
1
mail(to: user_mail.recipient, subject: CONFIRM_REGISTRATION_SUBJECT)
-
end
-
-
8
def self.send_confirmation_registration(user)
-
1
confirmation_token = user.confirmation_tokens.create!(token: SecureRandom.urlsafe_base64)
-
-
1
user_mail = UserMail.new(
-
user: user,
-
recipient: user.email,
-
subject: CONFIRM_REGISTRATION_SUBJECT,
-
template: "confirm_registration",
-
params: {
-
username: user.username,
-
url: generate_confirmation_url(confirmation_token)
-
}
-
)
-
-
1
then: 1
if user_mail.save
-
1
SendMailJob.perform_later(user_mail.id)
-
else: 0
else
-
errors = user_mail.errors.full_messages.join(", ")
-
Rails.logger.error("Failed to create UserMail: #{errors}")
-
end
-
-
1
user_mail
-
end
-
-
8
def self.generate_confirmation_url(confirmation_token)
-
2
Rails.application.routes.url_helpers.confirm_registration_url(
-
token: confirmation_token.token,
-
host: Rails.configuration.server_url
-
)
-
end
-
end
-
1
class ApplicationRecord < ActiveRecord::Base
-
1
primary_abstract_class
-
end
-
1
class Band < ApplicationRecord
-
1
include RandomId
-
-
1
has_many :event_bands, dependent: :destroy
-
1
has_many :events, through: :event_bands
-
-
1
has_many :band_members, dependent: :restrict_with_error
-
1
has_many :members, through: :band_members
-
1
has_many :permission, as: :item, dependent: :destroy
-
-
1
validates :description, presence: true
-
1
validates :name, presence: true
-
-
1
include Auditable
-
1
audit_log_columns :name, :description
-
-
1
attribute :permission_type, :string
-
-
1
scope :permitted_for, ->(user_id) {
-
132
select(
-
"bands.*, #{BandPermissionQuery.with_permission_type_sql(user_id)}"
-
)
-
.where(BandPermissionQuery.permission_sql(user_id))
-
}
-
-
1
def owner
-
73
then: 73
else: 0
permission.where(status: :owned).first&.user
-
end
-
-
1
def url
-
1
Rails.application.routes.url_helpers.edit_band_path(self)
-
end
-
-
1
def editable?
-
2
permission_type == "edit"
-
end
-
end
-
16
class BandMember < ApplicationRecord
-
16
include RandomId
-
16
belongs_to :member
-
16
belongs_to :band
-
end
-
3
class BandsAudit < ApplicationRecord
-
3
self.table_name = "bands_audit"
-
-
3
belongs_to :band
-
end
-
1
module Auditable
-
1
extend ActiveSupport::Concern
-
-
1
included do
-
3
after_update_commit :log_changes
-
end
-
-
1
def log_changes
-
17
changes_to_log.each do |column, values|
-
26
audit_class.create!(
-
item_id => id,
-
:column_changed => column,
-
:old_value => values.first,
-
:user_id => Current.user.id
-
)
-
end
-
end
-
-
1
private
-
-
1
def changes_to_log
-
17
saved_changes.slice(*self.class.logged_columns)
-
end
-
-
1
def audit_class
-
26
"#{self.class.name.pluralize}Audit".constantize
-
end
-
-
1
def item_id
-
26
"#{self.class.name.downcase}_id"
-
end
-
-
1
class_methods do
-
1
def audit_log_columns(*columns)
-
3
@logged_columns = columns.map(&:to_s)
-
end
-
-
1
def logged_columns
-
17
@logged_columns || []
-
end
-
end
-
end
-
1
module RandomId
-
1
extend ActiveSupport::Concern
-
-
1
included do
-
37
before_create :randomise_id
-
end
-
-
1
private
-
-
1
def randomise_id
-
2174
loop do
-
2174
self.id = SecureRandom.random_number(4294967295)
-
2174
else: 0
then: 2174
break unless self.class.where(id: id).exists?
-
end
-
end
-
end
-
7
class ConfirmationToken < ApplicationRecord
-
7
belongs_to :user
-
-
7
before_create :set_expires_at, unless: :expires_at?
-
-
7
validates :token, presence: true, uniqueness: true
-
-
11
scope :valid, -> { where("expires_at > ?", Time.current) }
-
-
7
private
-
-
7
def set_expires_at
-
5
self.expires_at = 24.hours.from_now
-
end
-
end
-
1
class Current < ActiveSupport::CurrentAttributes
-
1
attribute :_user
-
1
attribute :_impersonator
-
-
1449
resets { Time.zone = nil }
-
-
1
def user=(user)
-
451
self._user = user
-
451
update_time_zone
-
end
-
-
1
def user
-
3334
_user
-
end
-
-
1
def impersonator=(impersonator)
-
445
self._impersonator = impersonator
-
445
update_time_zone
-
end
-
-
1
def impersonator
-
1
_impersonator
-
end
-
-
1
def impersonating?
-
207
_impersonator.present?
-
end
-
-
1
private
-
-
1
def update_time_zone
-
896
then: 586
else: 310
then: 0
else: 310
Time.zone = _user&.time_zone || _impersonator&.time_zone
-
end
-
end
-
1
class Event < ApplicationRecord
-
1
include EventsHelper
-
1
include RandomId
-
-
1
has_many :event_bands, dependent: :destroy
-
1
has_many :bands, through: :event_bands
-
1
has_many :permissions, as: :item, dependent: :destroy
-
1
validate :end_date_nil_or_after_start
-
1
before_validation :set_defaults
-
-
1
attribute :permission_type, :string
-
-
3
scope :past, -> { where(<<~SQL) }
-
start_date IS NULL OR
-
COALESCE(end_date, start_date) <= CURRENT_TIMESTAMP
-
SQL
-
-
154
scope :future, -> { where(<<~SQL) }
-
start_date IS NULL OR
-
COALESCE(end_date, start_date) >= CURRENT_TIMESTAMP
-
SQL
-
-
1
scope :permitted_for, ->(user_id) {
-
269
select(
-
"events.*, #{EventPermissionQuery.with_permission_type_sql(user_id)}"
-
)
-
.where(EventPermissionQuery.permission_sql(user_id))
-
}
-
-
1
include Auditable
-
1
audit_log_columns :name, :description, :start_date, :end_date
-
-
1
def owner
-
73
then: 73
else: 0
permissions.where(status: :owned).first&.user
-
end
-
-
1
def url
-
1
Rails.application.routes.url_helpers.edit_event_path(self)
-
end
-
-
1
def external_url
-
1
Rails.application.routes.url_helpers.edit_event_url(self, host: Rails.application.config.server_url)
-
end
-
-
1
def editable?
-
1
permission_type == "edit"
-
end
-
-
1
def duration
-
1
then: 1
else: 0
if start_date.present? && end_date.present?
-
1
convert_seconds_to_duration(end_date - start_date)
-
end
-
end
-
-
1
private
-
-
1
def set_defaults
-
185
then: 183
else: 2
if start_date.present?
-
183
self.end_date ||= start_date
-
183
then: 0
else: 183
if end_date && end_date < start_date
-
self.end_date = start_date
-
end
-
end
-
end
-
-
1
def end_date_nil_or_after_start
-
185
then: 0
else: 185
if end_date.present? && start_date.present? && end_date < start_date
-
errors.add(:end_date, "Must be before start")
-
end
-
end
-
end
-
1
class EventBand < ApplicationRecord
-
1
include RandomId
-
1
belongs_to :event
-
1
belongs_to :band
-
end
-
8
class EventsAudit < ApplicationRecord
-
8
self.table_name = "events_audit"
-
8
belongs_to :event
-
end
-
1
class Member < ApplicationRecord
-
1
include RandomId
-
-
1
has_many :band_members, dependent: :destroy
-
1
has_many :bands, through: :band_members
-
-
1
has_many :member_skills, dependent: :destroy
-
1
has_many :skills, through: :member_skills
-
-
253
validates :skills, presence: true, if: -> { skills_list.present? }
-
-
1
has_many :permission, as: :item, dependent: :destroy
-
-
1
include Auditable
-
1
audit_log_columns :name, :description
-
-
1
attribute :permission_type, :string
-
-
1
scope :permitted_for, ->(user_id) {
-
108
select(
-
"members.*, #{MemberPermissionQuery.with_permission_type_sql(user_id)}"
-
)
-
.where(MemberPermissionQuery.permission_sql(user_id))
-
}
-
-
1
def owner
-
then: 0
else: 0
Permission.where(
-
status: :owned,
-
item_type: "Member",
-
item_id: id
-
).first&.user
-
end
-
-
1
def skills_list
-
254
skills.pluck(:name).join(", ")
-
end
-
-
1
def skills_list=(skills_string)
-
then: 0
else: 0
return if skills_string.blank?
-
-
skill_names =
-
skills_string
-
.downcase
-
.split(",")
-
.map(&:strip)
-
.compact_blank
-
.uniq
-
-
then: 0
else: 0
return if skill_names.empty?
-
-
transaction do
-
member_skills.destroy_all
-
skill_names.each do |name|
-
skill = Skill.find_or_create_by!(name: name)
-
else: 0
then: 0
member_skills.create!(skill: skill) unless member_skills.exists?(skill: skill)
-
end
-
end
-
end
-
-
1
def editable?
-
2
permission_type == "edit"
-
end
-
-
1
def events
-
1
Event.joins(:bands)
-
.where(bands: {id: bands.pluck(:id)})
-
.distinct
-
end
-
end
-
16
class MemberSkill < ApplicationRecord
-
16
belongs_to :member
-
16
belongs_to :skill
-
-
16
validates :member_id, uniqueness: {scope: :skill_id}
-
end
-
3
class MembersAudit < ApplicationRecord
-
3
self.table_name = "members_audit"
-
3
belongs_to :member
-
end
-
16
class Permission < ApplicationRecord
-
16
include RandomId
-
16
include PermissionsHelper
-
-
16
belongs_to :user, class_name: "User"
-
16
belongs_to :bestowing_user, class_name: "User", optional: true
-
16
belongs_to :item, polymorphic: true
-
-
16
scope :accepted, -> { where(status: :accepted) }
-
16
scope :accepted_or_owned, -> { where(status: %i[accepted owned]) }
-
16
scope :item_type, ->(type) { where(item_type: type) }
-
21
scope :for_user, ->(user) { where("bestowing_user_id = :user_id OR user_id = :user_id", user_id: user.id) }
-
-
16
def self.join_models(model)
-
table_name = model.to_s.pluralize
-
klass_name = model.to_s.classify
-
joins("
-
JOIN #{table_name}
-
ON #{table_name}.id = permissions.item_id
-
AND permissions.item_type = '#{klass_name}'
-
")
-
end
-
-
16
scope :bands, -> { join_models(:band) }
-
16
scope :events, -> { join_models(:event) }
-
16
scope :members, -> { join_models(:member) }
-
-
16
scope :system_permissions, -> { where(bestowing_user: nil) }
-
-
16
validates :item_type, presence: true
-
16
validates :permission_type, presence: true, inclusion: {in: %w[view edit]}
-
16
validates :status, presence: true, inclusion: {in: %w[owned pending accepted rejected]}
-
16
validate :bestowing_user_must_be_organiser_or_nil, :user_must_be_member_or_organiser
-
-
16
validate :valid_item
-
16
validate :valid_user
-
-
16
enum :status, {
-
owned: "owned",
-
pending: "pending",
-
accepted: "accepted",
-
rejected: "rejected"
-
}
-
-
16
def item_path
-
1
case item_type
-
when: 0
when "Band"
-
Rails.application.routes.url_helpers.band_path(item)
-
when: 1
when "Event"
-
1
Rails.application.routes.url_helpers.event_path(item)
-
when: 0
when "Member"
-
Rails.application.routes.url_helpers.member_path(item)
-
else: 0
else
-
Rails.application.routes.url_helpers.permissions_path
-
end
-
end
-
-
16
private
-
-
16
def valid_item
-
581
then: 513
else: 68
return true if bestowing_user.nil?
-
-
68
item_ids = potential_items(bestowing_user).pluck(1)
-
68
item_str = "#{item_type}_#{item_id}"
-
-
68
else: 65
then: 3
unless item_ids.include?(item_str)
-
3
errors.add(:item, "is not a valid selection for #{bestowing_user}. Valid options: #{item_ids}")
-
end
-
end
-
-
16
def valid_user
-
# TODO: make this fancier
-
581
else: 579
then: 0
unless User.find(user_id)
-
errors.add(:user, "is not a valid selection")
-
end
-
end
-
-
16
def bestowing_user_must_be_organiser_or_nil
-
581
else: 580
then: 1
unless bestowing_user.nil? || bestowing_user.organiser?
-
1
errors.add(:bestowing_user, "must be an organiser")
-
end
-
end
-
-
16
def user_must_be_member_or_organiser
-
581
then: 579
else: 2
then: 164
else: 2
else: 578
then: 3
unless user&.organiser? || user&.member?
-
3
errors.add(:user, "must be a member or organiser")
-
end
-
end
-
end
-
1
class Skill < ApplicationRecord
-
1
has_many :member_skills, dependent: :destroy
-
1
has_many :members, through: :member_skills
-
-
1
validates :name, presence: true
-
end
-
1
class User < ApplicationRecord
-
1
include RandomId
-
1
include PermissionsHelper
-
-
1
enum :user_type, {admin: 0, member: 1, organiser: 2}
-
-
1
has_secure_password
-
1
has_many :permissions,
-
dependent: :destroy,
-
inverse_of: :user
-
-
1
def members
-
101
Member.permitted_for(id)
-
end
-
-
1
def bands
-
126
Band.permitted_for(id)
-
end
-
-
1
def events
-
263
Event.permitted_for(id)
-
end
-
-
1
has_many :confirmation_tokens, dependent: :destroy
-
-
572
then: 568
else: 3
before_save { email&.downcase! }
-
1
before_validation :set_default_time_zone
-
-
1
validates :username,
-
presence: true,
-
uniqueness: true
-
-
1
validates :email,
-
presence: {message: "can't be blank"},
-
uniqueness: true,
-
format: {with: URI::MailTo::EMAIL_REGEXP, message: "is invalid", allow_blank: true}
-
-
1
validates :password,
-
presence: true,
-
length: {minimum: 6},
-
if: :password_required?,
-
allow_blank: true
-
-
1
validates :password_confirmation,
-
presence: true,
-
allow_blank: true
-
-
1
validates :time_zone,
-
inclusion: {
-
151
in: ActiveSupport::TimeZone.all.map { |t| t.tzinfo.name },
-
message: "%{value} is not a valid time zone"
-
}, allow_blank: true
-
-
1
def confirmed?
-
308
confirmed || admin?
-
end
-
-
1
def to_param
-
449
username
-
end
-
-
1
def owned_links
-
2
then: 1
else: 1
return @owned_links if defined?(@owned_links)
-
@owned_links =
-
1
bands.select(:name, :id) +
-
events.select(:name, :id) +
-
members.select(:name, :id)
-
end
-
-
1
private
-
-
1
def password_required?
-
1193
new_record? || password.present?
-
end
-
-
1
def set_default_time_zone
-
595
then: 8
else: 587
self.time_zone = "Etc/UTC" if time_zone.blank?
-
end
-
end
-
1
class UserMail < ApplicationRecord
-
1
belongs_to :user
-
-
1
enum :state, {pending: 0, sending: 1, sent: 2, failed: 3}
-
-
1
validates :subject, presence: true, length: {maximum: 120}
-
1
validates :recipient, presence: true, length: {maximum: 255}
-
1
validates :template, presence: true, length: {maximum: 50}
-
1
validates :params, presence: true
-
-
1
def html_content
-
4
content(:html)
-
end
-
-
1
def text_content
-
4
content(:text)
-
end
-
-
1
def attempt_send
-
3
else: 2
then: 1
return unless pending?
-
-
2
update!(state: :sending)
-
-
begin
-
2
UserMailer.confirm_registration(self).deliver_now
-
1
update!(state: :sent)
-
1
Rails.logger.info("Mail sent successfully to #{recipient}.")
-
rescue => e
-
1
then: 0
else: 1
raise e if Rails.env.development?
-
1
update!(state: :failed)
-
1
Rails.logger.error("Failed to send mail to #{recipient}: #{e}")
-
end
-
end
-
-
1
private
-
-
1
def content(format)
-
8
ApplicationController.render(
-
layout: false,
-
template: "user_mailer/#{template}",
-
assigns: params,
-
formats: [format]
-
)
-
end
-
end
-
16
class BandPermissionQuery
-
16
class << self
-
16
def permission_sql(user_id)
-
132
<<-SQL
-
EXISTS (
-
SELECT 1 FROM permissions
-
WHERE permissions.user_id = #{user_id}
-
AND permissions.status IN ('owned', 'accepted')
-
AND (
-
/* Check three ways you might directly have permission */
-
(#{direct_permission_sql})
-
OR
-
(#{through_member_sql})
-
OR
-
(#{through_event_sql})
-
)
-
)
-
/* Or check indirect permissions through event relationships */
-
OR bands.id IN (
-
/* Can see bands at events you have have permission for other bands
-
at */
-
#{shares_event_with_permitted_band_sql(user_id)}
-
)
-
OR bands.id IN (
-
/* Can see bands at events you have have permission for members in
-
bands at */
-
#{shares_event_with_band_with_permitted_member_sql(user_id)}
-
)
-
SQL
-
end
-
-
16
def with_permission_type_sql(user_id)
-
132
<<~SQL
-
CASE
-
WHEN EXISTS (
-
SELECT 1 FROM permissions
-
WHERE permissions.user_id = #{user_id}
-
AND permissions.status IN ('owned', 'accepted')
-
AND permissions.item_type = 'Band'
-
AND permissions.item_id = bands.id
-
) THEN (
-
SELECT permission_type FROM permissions
-
WHERE permissions.user_id = #{user_id}
-
AND permissions.status IN ('owned', 'accepted')
-
AND permissions.item_type = 'Band'
-
AND permissions.item_id = bands.id
-
LIMIT 1
-
)
-
ELSE 'view'
-
END as permission_type
-
SQL
-
end
-
-
16
private
-
-
16
def direct_permission_sql
-
132
<<-SQL
-
/* Simplest case: you have direct permission to the band */
-
permissions.item_type = 'Band'
-
AND permissions.item_id = bands.id
-
SQL
-
end
-
-
16
def through_member_sql
-
132
<<-SQL
-
/* You have permission to a member who is in this band */
-
permissions.item_type = 'Member'
-
AND permissions.item_id IN (
-
/* Find all members in this band */
-
SELECT member_id
-
FROM band_members
-
WHERE band_id = bands.id
-
)
-
SQL
-
end
-
-
16
def through_event_sql
-
132
<<-SQL
-
/* You have permission to an event this band is playing at */
-
permissions.item_type = 'Event'
-
AND permissions.item_id IN (
-
/* Find all events this band is playing at */
-
SELECT event_id
-
FROM event_bands
-
WHERE band_id = bands.id
-
)
-
SQL
-
end
-
-
16
def shares_event_with_permitted_band_sql(user_id)
-
132
<<~SQL
-
/* Find bands that are playing at the same events as bands you can access */
-
SELECT band.id
-
FROM bands AS band
-
/* First, find events this band is playing at */
-
JOIN event_bands AS event_band
-
ON event_band.band_id = band.id
-
/* Then find other bands playing at those same events */
-
JOIN event_bands AS other_event_band
-
ON other_event_band.event_id = event_band.event_id
-
/* Check if you have permission to any of those other bands */
-
JOIN permissions AS p
-
ON p.item_type = 'Band'
-
AND p.item_id = other_event_band.band_id
-
AND p.user_id = #{user_id}
-
AND p.status IN ('owned', 'accepted')
-
SQL
-
end
-
-
16
def shares_event_with_band_with_permitted_member_sql(user_id)
-
132
<<-SQL
-
/* Find bands that are playing at events where there's another band
-
that has a member you have permission to */
-
SELECT band.id
-
FROM bands AS band
-
/* First, find events this band is playing at */
-
JOIN event_bands AS event_band
-
ON event_band.band_id = band.id
-
/* Then find other bands playing at those same events */
-
JOIN event_bands AS other_event_band
-
ON other_event_band.event_id = event_band.event_id
-
/* Find members in those other bands */
-
JOIN band_members AS bm
-
ON bm.band_id = other_event_band.band_id
-
/* Check if you have permission to any of those members */
-
JOIN permissions AS p
-
ON p.item_type = 'Member'
-
AND p.item_id = bm.member_id
-
AND p.user_id = #{user_id}
-
AND p.status IN ('owned', 'accepted')
-
SQL
-
end
-
end
-
end
-
16
class EventPermissionQuery
-
16
class << self
-
16
def permission_sql(user_id)
-
269
<<-SQL
-
EXISTS (
-
SELECT 1 FROM permissions
-
WHERE permissions.user_id = #{user_id}
-
AND permissions.status IN ('owned', 'accepted')
-
AND (
-
/* Check three ways you might have permission */
-
(#{direct_permission_sql})
-
OR
-
(#{through_band_sql})
-
OR
-
(#{through_member_sql})
-
)
-
)
-
SQL
-
end
-
-
16
def with_permission_type_sql(user_id)
-
269
<<~SQL
-
CASE
-
WHEN EXISTS (
-
SELECT 1 FROM permissions
-
WHERE permissions.user_id = #{user_id}
-
AND permissions.status IN ('owned', 'accepted')
-
AND permissions.item_type = 'Event'
-
AND permissions.item_id = events.id
-
) THEN (
-
SELECT permission_type FROM permissions
-
WHERE permissions.user_id = #{user_id}
-
AND permissions.status IN ('owned', 'accepted')
-
AND permissions.item_type = 'Event'
-
AND permissions.item_id = events.id
-
LIMIT 1
-
)
-
ELSE 'view'
-
END as permission_type
-
SQL
-
end
-
-
16
private
-
-
16
def direct_permission_sql
-
269
<<-SQL
-
/* Simplest case: you have direct permission to the event */
-
permissions.item_type = 'Event'
-
AND permissions.item_id = events.id
-
SQL
-
end
-
-
16
def through_band_sql
-
269
<<-SQL
-
/* You have permission to a band that's playing at this event */
-
permissions.item_type = 'Band'
-
AND permissions.item_id IN (
-
/* Find all bands playing at this event */
-
SELECT band_id
-
FROM event_bands
-
WHERE event_id = events.id
-
)
-
SQL
-
end
-
-
16
def through_member_sql
-
269
<<-SQL
-
/* You have permission to a member who's playing at this event */
-
permissions.item_type = 'Member'
-
AND permissions.item_id IN (
-
/* Find all members playing at this event */
-
SELECT members.id
-
FROM members
-
/* Connect members to their bands */
-
JOIN band_members
-
ON band_members.member_id = members.id
-
/* Connect bands to the events they're playing */
-
JOIN event_bands
-
ON event_bands.band_id = band_members.band_id
-
WHERE event_bands.event_id = events.id
-
)
-
SQL
-
end
-
end
-
end
-
16
class MemberPermissionQuery
-
16
class << self
-
16
def permission_sql(user_id)
-
108
<<~SQL
-
EXISTS (
-
SELECT 1 FROM permissions
-
WHERE (
-
permissions.user_id = #{user_id}
-
AND permissions.status IN ('owned', 'accepted')
-
AND (
-
/* Check three ways you might directly have permission */
-
(#{direct_permission_sql})
-
OR
-
(#{through_band_sql})
-
OR
-
(#{through_event_sql})
-
)
-
)
-
/* Or check indirect permissions through various relationships */
-
OR members.id IN (
-
/* Can see members who are in bands with members you can access */
-
#{shares_band_with_permitted_member_sql(user_id)}
-
)
-
OR members.id IN (
-
/* Can see members who are playing at events with members you can access */
-
#{shares_event_with_permitted_member_sql(user_id)}
-
)
-
OR members.id IN (
-
/* Can see members who are in bands playing at events with bands you can access */
-
#{shares_event_with_permitted_band_sql(user_id)}
-
)
-
)
-
SQL
-
end
-
-
16
def with_permission_type_sql(user_id)
-
108
<<~SQL
-
CASE
-
WHEN EXISTS (
-
SELECT 1 FROM permissions
-
WHERE permissions.user_id = #{user_id}
-
AND permissions.status IN ('owned', 'accepted')
-
AND permissions.item_type = 'Member'
-
AND permissions.item_id = members.id
-
) THEN (
-
SELECT permission_type FROM permissions
-
WHERE permissions.user_id = #{user_id}
-
AND permissions.status IN ('owned', 'accepted')
-
AND permissions.item_type = 'Member'
-
AND permissions.item_id = members.id
-
LIMIT 1
-
)
-
ELSE 'view'
-
END as permission_type
-
SQL
-
end
-
-
16
def direct_permission_sql
-
108
<<~SQL
-
/* Simplest case: you have direct permission to see this member */
-
permissions.item_type = 'Member'
-
AND permissions.item_id = members.id
-
SQL
-
end
-
-
16
def through_band_sql
-
108
<<~SQL
-
/* You have permission to a band this member plays in */
-
permissions.item_type = 'Band'
-
AND permissions.item_id IN (
-
/* Find all bands this member is in */
-
SELECT band_id FROM band_members
-
WHERE member_id = members.id
-
)
-
SQL
-
end
-
-
16
def through_event_sql
-
108
<<~SQL
-
/* You have permission to an event where this member is playing */
-
permissions.item_type = 'Event'
-
AND permissions.item_id IN (
-
/* Find all events where this member is playing */
-
SELECT events.id FROM events
-
/* Connect events to bands playing at them */
-
JOIN event_bands ON event_bands.event_id = events.id
-
/* Connect bands to their members */
-
JOIN band_members ON band_members.band_id = event_bands.band_id
-
WHERE band_members.member_id = members.id
-
)
-
SQL
-
end
-
-
16
def shares_band_with_permitted_member_sql(user_id)
-
113
<<~SQL
-
/* Find members who are in the same bands as members you can access */
-
SELECT DISTINCT other_members.member_id AS id
-
FROM band_members AS other_members
-
/* Find bands where there's at least one member you have permission for */
-
WHERE other_members.band_id IN (
-
SELECT band_members.band_id
-
FROM band_members
-
JOIN permissions AS p
-
ON p.item_type = 'Member'
-
AND p.item_id = band_members.member_id
-
WHERE p.user_id = #{user_id}
-
AND p.status IN ('owned', 'accepted')
-
)
-
SQL
-
end
-
-
16
def shares_event_with_permitted_member_sql(user_id)
-
108
<<~SQL
-
/* Find members who are playing at the same events as members you can access */
-
SELECT band_member.member_id AS id
-
/* Start with a member in a band */
-
FROM band_members AS band_member
-
/* Find events where their band is playing */
-
JOIN event_bands AS event_band
-
ON event_band.band_id = band_member.band_id
-
/* Find other bands at those same events */
-
JOIN event_bands AS other_event_band
-
ON other_event_band.event_id = event_band.event_id
-
/* Find members in those other bands */
-
JOIN band_members AS other_band_member
-
ON other_band_member.band_id = other_event_band.band_id
-
/* Check that you have permission to one of those other members */
-
JOIN permissions AS p
-
ON p.item_type = 'Member'
-
AND p.item_id = other_band_member.member_id
-
AND p.user_id = #{user_id}
-
AND p.status IN ('owned', 'accepted')
-
SQL
-
end
-
-
16
def shares_event_with_permitted_band_sql(user_id)
-
108
<<~SQL
-
/* Find members who are playing at events where you have permission to another band */
-
SELECT band_member.member_id AS id
-
/* Start with a member in a band */
-
FROM band_members AS band_member
-
/* Find events where their band is playing */
-
JOIN event_bands AS event_band
-
ON event_band.band_id = band_member.band_id
-
/* Find other bands at those same events */
-
JOIN event_bands AS other_event_band
-
ON other_event_band.event_id = event_band.event_id
-
/* Check that you have permission to one of those other bands */
-
JOIN permissions AS p
-
ON p.item_type = 'Band'
-
AND p.item_id = other_event_band.band_id
-
AND p.user_id = #{user_id}
-
AND p.status IN ('owned', 'accepted')
-
SQL
-
end
-
end
-
end