loading
Generated 2025-03-05T04:46:00+00:00

All Files ( 87.21% covered at 45.66 hits/line )

48 files in total.
1134 relevant lines, 989 lines covered and 145 lines missed. ( 87.21% )
330 total branches, 231 branches covered and 99 branches missed. ( 70.0% )
File % covered Lines Relevant Lines Lines covered Lines missed Avg. Hits / Line Branch Coverage Branches Covered branches Missed branches
app/controllers/admin/admin_controller.rb 100.00 % 18 9 9 0 12.67 100.00 % 2 2 0
app/controllers/admin/bands_controller.rb 0.00 % 45 37 0 37 0.00 100.00 % 0 0 0
app/controllers/admin/impersonation_controller.rb 100.00 % 27 14 14 0 2.64 100.00 % 2 2 0
app/controllers/admin/users_controller.rb 100.00 % 63 29 29 0 5.48 90.00 % 10 9 1
app/controllers/application_controller.rb 84.93 % 153 73 62 11 90.22 71.88 % 32 23 9
app/controllers/bands_controller.rb 98.18 % 109 55 54 1 7.82 90.00 % 20 18 2
app/controllers/concerns/access_permissions.rb 100.00 % 30 17 17 0 58.06 100.00 % 4 4 0
app/controllers/events_controller.rb 100.00 % 97 48 48 0 22.58 91.67 % 12 11 1
app/controllers/members_controller.rb 90.70 % 94 43 39 4 4.81 70.00 % 10 7 3
app/controllers/permissions_controller.rb 71.88 % 193 96 69 27 5.25 42.55 % 47 20 27
app/controllers/sessions_controller.rb 100.00 % 40 28 28 0 49.71 100.00 % 6 6 0
app/controllers/user_mails_controller.rb 100.00 % 38 19 19 0 5.53 100.00 % 6 6 0
app/controllers/users/registration_controller.rb 100.00 % 64 34 34 0 3.59 85.71 % 14 12 2
app/controllers/users_controller.rb 100.00 % 95 41 41 0 5.07 85.71 % 14 12 2
app/helpers/application_helper.rb 100.00 % 104 47 47 0 129.87 100.00 % 15 15 0
app/helpers/bands_helper.rb 100.00 % 2 1 1 0 1.00 100.00 % 0 0 0
app/helpers/events_helper.rb 100.00 % 51 32 32 0 48.22 100.00 % 25 25 0
app/helpers/members_helper.rb 100.00 % 2 1 1 0 1.00 100.00 % 0 0 0
app/helpers/permissions_helper.rb 50.00 % 182 66 33 33 22.12 24.24 % 33 8 25
app/helpers/sessions_helper.rb 100.00 % 2 1 1 0 1.00 100.00 % 0 0 0
app/helpers/users/registration_helper.rb 100.00 % 2 1 1 0 1.00 100.00 % 0 0 0
app/jobs/application_job.rb 100.00 % 7 1 1 0 1.00 100.00 % 0 0 0
app/jobs/send_mail_job.rb 66.67 % 9 6 4 2 0.67 100.00 % 0 0 0
app/lib/form_builders/nice_form_builder.rb 100.00 % 233 89 89 0 39.36 72.73 % 22 16 6
app/mailers/application_mailer.rb 100.00 % 3 2 2 0 8.00 100.00 % 0 0 0
app/mailers/test_mailer.rb 0.00 % 11 10 0 10 0.00 100.00 % 0 0 0
app/mailers/user_mailer.rb 87.50 % 42 16 14 2 3.13 50.00 % 2 1 1
app/models/application_record.rb 100.00 % 3 2 2 0 1.00 100.00 % 0 0 0
app/models/band.rb 100.00 % 37 20 20 0 11.20 50.00 % 2 1 1
app/models/band_member.rb 100.00 % 5 4 4 0 16.00 100.00 % 0 0 0
app/models/bands_audit.rb 100.00 % 5 3 3 0 3.00 100.00 % 0 0 0
app/models/concerns/auditable.rb 100.00 % 42 19 19 0 7.68 100.00 % 0 0 0
app/models/concerns/random_id.rb 100.00 % 16 9 9 0 729.33 50.00 % 2 1 1
app/models/confirmation_token.rb 100.00 % 15 8 8 0 7.25 100.00 % 0 0 0
app/models/current.rb 100.00 % 34 19 19 0 404.68 75.00 % 4 3 1
app/models/event.rb 94.29 % 71 35 33 2 36.00 60.00 % 10 6 4
app/models/event_band.rb 100.00 % 5 4 4 0 1.00 100.00 % 0 0 0
app/models/events_audit.rb 100.00 % 4 3 3 0 8.00 100.00 % 0 0 0
app/models/member.rb 70.00 % 69 30 21 9 21.13 0.00 % 8 0 8
app/models/member_skill.rb 100.00 % 6 4 4 0 16.00 100.00 % 0 0 0
app/models/members_audit.rb 100.00 % 4 3 3 0 3.00 100.00 % 0 0 0
app/models/permission.rb 85.11 % 89 47 40 7 63.62 77.78 % 18 14 4
app/models/skill.rb 100.00 % 6 4 4 0 1.00 100.00 % 0 0 0
app/models/user.rb 100.00 % 79 33 33 0 114.64 100.00 % 6 6 0
app/models/user_mail.rb 100.00 % 45 23 23 0 1.74 75.00 % 4 3 1
app/queries/band_permission_query.rb 100.00 % 134 17 17 0 63.76 100.00 % 0 0 0
app/queries/event_permission_query.rb 100.00 % 85 13 13 0 113.31 100.00 % 0 0 0
app/queries/member_permission_query.rb 100.00 % 157 18 18 0 57.17 100.00 % 0 0 0

Controllers ( 85.27% covered at 21.98 hits/line )

14 files in total.
543 relevant lines, 463 lines covered and 80 lines missed. ( 85.27% )
179 total branches, 132 branches covered and 47 branches missed. ( 73.74% )
File % covered Lines Relevant Lines Lines covered Lines missed Avg. Hits / Line Branch Coverage Branches Covered branches Missed branches
app/controllers/admin/admin_controller.rb 100.00 % 18 9 9 0 12.67 100.00 % 2 2 0
app/controllers/admin/bands_controller.rb 0.00 % 45 37 0 37 0.00 100.00 % 0 0 0
app/controllers/admin/impersonation_controller.rb 100.00 % 27 14 14 0 2.64 100.00 % 2 2 0
app/controllers/admin/users_controller.rb 100.00 % 63 29 29 0 5.48 90.00 % 10 9 1
app/controllers/application_controller.rb 84.93 % 153 73 62 11 90.22 71.88 % 32 23 9
app/controllers/bands_controller.rb 98.18 % 109 55 54 1 7.82 90.00 % 20 18 2
app/controllers/concerns/access_permissions.rb 100.00 % 30 17 17 0 58.06 100.00 % 4 4 0
app/controllers/events_controller.rb 100.00 % 97 48 48 0 22.58 91.67 % 12 11 1
app/controllers/members_controller.rb 90.70 % 94 43 39 4 4.81 70.00 % 10 7 3
app/controllers/permissions_controller.rb 71.88 % 193 96 69 27 5.25 42.55 % 47 20 27
app/controllers/sessions_controller.rb 100.00 % 40 28 28 0 49.71 100.00 % 6 6 0
app/controllers/user_mails_controller.rb 100.00 % 38 19 19 0 5.53 100.00 % 6 6 0
app/controllers/users/registration_controller.rb 100.00 % 64 34 34 0 3.59 85.71 % 14 12 2
app/controllers/users_controller.rb 100.00 % 95 41 41 0 5.07 85.71 % 14 12 2

Channels ( 100.0% covered at 0.0 hits/line )

0 files in total.
0 relevant lines, 0 lines covered and 0 lines missed. ( 100.0% )
0 total branches, 0 branches covered and 0 branches missed. ( 100.0% )
File % covered Lines Relevant Lines Lines covered Lines missed Avg. Hits / Line Branch Coverage Branches Covered branches Missed branches

Models ( 93.33% covered at 87.29 hits/line )

18 files in total.
270 relevant lines, 252 lines covered and 18 lines missed. ( 93.33% )
54 total branches, 34 branches covered and 20 branches missed. ( 62.96% )
File % covered Lines Relevant Lines Lines covered Lines missed Avg. Hits / Line Branch Coverage Branches Covered branches Missed branches
app/models/application_record.rb 100.00 % 3 2 2 0 1.00 100.00 % 0 0 0
app/models/band.rb 100.00 % 37 20 20 0 11.20 50.00 % 2 1 1
app/models/band_member.rb 100.00 % 5 4 4 0 16.00 100.00 % 0 0 0
app/models/bands_audit.rb 100.00 % 5 3 3 0 3.00 100.00 % 0 0 0
app/models/concerns/auditable.rb 100.00 % 42 19 19 0 7.68 100.00 % 0 0 0
app/models/concerns/random_id.rb 100.00 % 16 9 9 0 729.33 50.00 % 2 1 1
app/models/confirmation_token.rb 100.00 % 15 8 8 0 7.25 100.00 % 0 0 0
app/models/current.rb 100.00 % 34 19 19 0 404.68 75.00 % 4 3 1
app/models/event.rb 94.29 % 71 35 33 2 36.00 60.00 % 10 6 4
app/models/event_band.rb 100.00 % 5 4 4 0 1.00 100.00 % 0 0 0
app/models/events_audit.rb 100.00 % 4 3 3 0 8.00 100.00 % 0 0 0
app/models/member.rb 70.00 % 69 30 21 9 21.13 0.00 % 8 0 8
app/models/member_skill.rb 100.00 % 6 4 4 0 16.00 100.00 % 0 0 0
app/models/members_audit.rb 100.00 % 4 3 3 0 3.00 100.00 % 0 0 0
app/models/permission.rb 85.11 % 89 47 40 7 63.62 77.78 % 18 14 4
app/models/skill.rb 100.00 % 6 4 4 0 1.00 100.00 % 0 0 0
app/models/user.rb 100.00 % 79 33 33 0 114.64 100.00 % 6 6 0
app/models/user_mail.rb 100.00 % 45 23 23 0 1.74 75.00 % 4 3 1

Mailers ( 57.14% covered at 2.36 hits/line )

3 files in total.
28 relevant lines, 16 lines covered and 12 lines missed. ( 57.14% )
2 total branches, 1 branches covered and 1 branches missed. ( 50.0% )
File % covered Lines Relevant Lines Lines covered Lines missed Avg. Hits / Line Branch Coverage Branches Covered branches Missed branches
app/mailers/application_mailer.rb 100.00 % 3 2 2 0 8.00 100.00 % 0 0 0
app/mailers/test_mailer.rb 0.00 % 11 10 0 10 0.00 100.00 % 0 0 0
app/mailers/user_mailer.rb 87.50 % 42 16 14 2 3.13 50.00 % 2 1 1

Helpers ( 77.85% covered at 61.15 hits/line )

7 files in total.
149 relevant lines, 116 lines covered and 33 lines missed. ( 77.85% )
73 total branches, 48 branches covered and 25 branches missed. ( 65.75% )
File % covered Lines Relevant Lines Lines covered Lines missed Avg. Hits / Line Branch Coverage Branches Covered branches Missed branches
app/helpers/application_helper.rb 100.00 % 104 47 47 0 129.87 100.00 % 15 15 0
app/helpers/bands_helper.rb 100.00 % 2 1 1 0 1.00 100.00 % 0 0 0
app/helpers/events_helper.rb 100.00 % 51 32 32 0 48.22 100.00 % 25 25 0
app/helpers/members_helper.rb 100.00 % 2 1 1 0 1.00 100.00 % 0 0 0
app/helpers/permissions_helper.rb 50.00 % 182 66 33 33 22.12 24.24 % 33 8 25
app/helpers/sessions_helper.rb 100.00 % 2 1 1 0 1.00 100.00 % 0 0 0
app/helpers/users/registration_helper.rb 100.00 % 2 1 1 0 1.00 100.00 % 0 0 0

Jobs ( 71.43% covered at 0.71 hits/line )

2 files in total.
7 relevant lines, 5 lines covered and 2 lines missed. ( 71.43% )
0 total branches, 0 branches covered and 0 branches missed. ( 100.0% )
File % covered Lines Relevant Lines Lines covered Lines missed Avg. Hits / Line Branch Coverage Branches Covered branches Missed branches
app/jobs/application_job.rb 100.00 % 7 1 1 0 1.00 100.00 % 0 0 0
app/jobs/send_mail_job.rb 66.67 % 9 6 4 2 0.67 100.00 % 0 0 0

Libraries ( 100.0% covered at 39.36 hits/line )

1 files in total.
89 relevant lines, 89 lines covered and 0 lines missed. ( 100.0% )
22 total branches, 16 branches covered and 6 branches missed. ( 72.73% )
File % covered Lines Relevant Lines Lines covered Lines missed Avg. Hits / Line Branch Coverage Branches Covered branches Missed branches
app/lib/form_builders/nice_form_builder.rb 100.00 % 233 89 89 0 39.36 72.73 % 22 16 6

Ungrouped ( 100.0% covered at 74.71 hits/line )

3 files in total.
48 relevant lines, 48 lines covered and 0 lines missed. ( 100.0% )
0 total branches, 0 branches covered and 0 branches missed. ( 100.0% )
File % covered Lines Relevant Lines Lines covered Lines missed Avg. Hits / Line Branch Coverage Branches Covered branches Missed branches
app/queries/band_permission_query.rb 100.00 % 134 17 17 0 63.76 100.00 % 0 0 0
app/queries/event_permission_query.rb 100.00 % 85 13 13 0 113.31 100.00 % 0 0 0
app/queries/member_permission_query.rb 100.00 % 157 18 18 0 57.17 100.00 % 0 0 0

app/controllers/admin/admin_controller.rb

100.0% lines covered

100.0% branches covered

9 relevant lines. 9 lines covered and 0 lines missed.
2 total branches, 2 branches covered and 0 branches missed.
    
  1. 13 module Admin
  2. 13 class AdminController < ApplicationController
  3. 13 before_action :require_admin!, :set_admin_border
  4. 13 private
  5. 13 def require_admin!
  6. 18 else: 16 then: 2 unless Current.user.admin? || Current.impersonating?
  7. 2 render plain: "Admins only",
  8. status: :forbidden
  9. end
  10. end
  11. 13 def set_admin_border
  12. 16 @admin_border = true
  13. end
  14. end
  15. end

app/controllers/admin/bands_controller.rb

0.0% lines covered

100.0% branches covered

37 relevant lines. 0 lines covered and 37 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. module Admin
  2. class BandsController < AdminController
  3. before_action :set_band, except: %i[
  4. index
  5. ]
  6. def index
  7. @bands = Band.all
  8. end
  9. def show
  10. end
  11. def edit
  12. end
  13. def update
  14. updated = @band.update(edit_band_params)
  15. if updated
  16. redirect_to edit_admin_band_path(@band), notice: "Band updated"
  17. else
  18. flash[:alert] = @band.errors.full_messages
  19. render :edit, status: :unprocessable_entity
  20. end
  21. end
  22. def destroy
  23. @band.destroy!
  24. redirect_to admin_bands_path, notice: "Band deleted."
  25. end
  26. private
  27. def set_band
  28. @band = Band.find(params[:id])
  29. redirect_to admin_bands_path, alert: "Band not found" unless @band
  30. end
  31. def edit_band_params
  32. params.require(:band).permit(
  33. :name
  34. )
  35. end
  36. end
  37. end

app/controllers/admin/impersonation_controller.rb

100.0% lines covered

100.0% branches covered

14 relevant lines. 14 lines covered and 0 lines missed.
2 total branches, 2 branches covered and 0 branches missed.
    
  1. 5 module Admin
  2. 5 class ImpersonationController < AdminController
  3. 5 def create
  4. 3 target_user = User.find(params[:user_id])
  5. 3 session[:impersonation] = {
  6. original_user_id: Current.user.id,
  7. target_user_id: target_user.id,
  8. started_at: Time.current.utc
  9. }
  10. 3 redirect_to(events_path, notice: "Impersonating '#{target_user.username}'")
  11. end
  12. 5 def destroy
  13. 2 else: 1 then: 1 return redirect_to(admin_users_path, alert: "No active impersonation session") unless session[:impersonation]
  14. 1 target_user = User.find(session[:impersonation]["target_user_id"])
  15. 1 original_user = User.find(session[:impersonation]["original_user_id"])
  16. 1 username = target_user.username
  17. 1 session[:user_id] = original_user.id
  18. 1 session.delete(:impersonation)
  19. 1 redirect_to(admin_users_path, notice: "Ended impersonation of #{username}")
  20. end
  21. end
  22. end

app/controllers/admin/users_controller.rb

100.0% lines covered

90.0% branches covered

29 relevant lines. 29 lines covered and 0 lines missed.
10 total branches, 9 branches covered and 1 branches missed.
    
  1. 10 module Admin
  2. 10 class UsersController < AdminController
  3. 10 before_action :set_user, except: %i[
  4. index
  5. ]
  6. 10 def index
  7. 4 then: 2 else: 2 @user_type = params[:user_type]&.to_sym
  8. 4 then: 1 else: 3 if @user_type && !User.user_types[@user_type]
  9. 1 flash[:alert] = "Invalid user type"
  10. 1 redirect_to admin_users_path
  11. end
  12. 4 @user_types = User.user_types.keys
  13. @users =
  14. 4 then: 2 if @user_type
  15. 2 User.where(user_type: @user_type)
  16. else: 2 else
  17. 2 User.all
  18. end
  19. end
  20. 10 def show
  21. end
  22. 10 def edit
  23. end
  24. 10 def update
  25. 3 updated = @user.update(edit_user_params)
  26. 2 then: 1 if updated
  27. 1 redirect_to edit_admin_user_path(@user), notice: "User updated"
  28. else: 1 else
  29. 1 flash[:alert] = @user.errors.full_messages
  30. 1 render :edit, status: :unprocessable_entity
  31. end
  32. end
  33. 10 def destroy
  34. 1 @user.destroy!
  35. 1 redirect_to admin_users_path, notice: "User deleted."
  36. end
  37. 10 private
  38. 10 def set_user
  39. 7 @user = User.find_by(username: params[:username])
  40. 7 else: 7 then: 0 redirect_to admin_users_path, alert: "User not found" unless @user
  41. end
  42. 10 def edit_user_params
  43. 3 params.require(:user).permit(
  44. :username,
  45. :name,
  46. :email,
  47. :user_type,
  48. :confirmed
  49. )
  50. end
  51. end
  52. end

app/controllers/application_controller.rb

84.93% lines covered

71.88% branches covered

73 relevant lines. 62 lines covered and 11 lines missed.
32 total branches, 23 branches covered and 9 branches missed.
    
  1. # frozen_string_literal: true
  2. 16 class ApplicationController < ActionController::Base
  3. 16 prepend_before_action :set_current_user
  4. 16 before_action :require_login
  5. 16 helper_method :show_tabs, :nav_tabs, :my_pending_invites, :body_class, :set_current_user_by_session
  6. 16 def nav_tabs
  7. 416 else: 408 then: 8 return unless show_tabs
  8. 408 @nav_tabs ||= build_nav_tabs
  9. end
  10. 16 def my_pending_invites
  11. 212 then: 204 else: 8 else: 70 then: 142 return unless Current.user&.member?
  12. 70 @my_pending_invites ||= Permission.where(user_id: Current.user.id, status: "pending")
  13. end
  14. 16 def body_class
  15. 212 then: 7 else: 205 @admin_border ? "admin-background" : ""
  16. end
  17. 16 def show_tabs
  18. 620 @show_tabs ||= Current.user.present?
  19. end
  20. 16 private
  21. 16 def set_current_user
  22. 444 set_current_user_by_session(session)
  23. end
  24. 16 def set_current_user_by_session(session)
  25. 445 then: 2 if session[:impersonation]
  26. 2 else: 443 handle_impersonation(session)
  27. 443 then: 288 elsif session[:user_id]
  28. 288 set_regular_user(session[:user_id])
  29. else: 155 else
  30. 155 clear_current_user
  31. end
  32. end
  33. 16 def handle_impersonation(session)
  34. 2 impersonation = session[:impersonation]
  35. original_user_id =
  36. 2 impersonation["original_user_id"] ||
  37. impersonation[:original_user_id]
  38. target_user_id =
  39. 2 impersonation["target_user_id"] ||
  40. impersonation[:target_user_id]
  41. started_at =
  42. 2 impersonation["started_at"] ||
  43. impersonation[:started_at]
  44. 2 original_user = User.find_by(id: original_user_id)
  45. 2 target_user = User.find_by(id: target_user_id)
  46. 2 then: 2 if valid_impersonation?(original_user, target_user, started_at)
  47. 2 set_impersonated_user(original_user, target_user)
  48. else: 0 else
  49. end_impersonation(session)
  50. end
  51. end
  52. 16 def valid_impersonation?(original_user, target_user, started_at)
  53. failure_reasons = {
  54. 2 then: 2 else: 0 original_not_admin: !original_user&.admin?,
  55. no_target_user: !target_user,
  56. missing_time: !started_at,
  57. too_old: started_at && started_at.to_time < 12.hours.ago
  58. 8 }.select! { |k, v| !!v }.keys
  59. 2 then: 0 if failure_reasons.any?
  60. then: 0 else: 0 if request
  61. flash[:alert] = "Impersonation failed: #{failure_reasons.join(", ")}"
  62. end
  63. false
  64. else: 2 else
  65. 2 true
  66. end
  67. end
  68. 16 def set_impersonated_user(original_user, target_user)
  69. 2 Current.user = target_user
  70. 2 Current.impersonator = original_user
  71. end
  72. 16 def set_regular_user(user_id)
  73. 288 user = User.find_by(id: user_id)
  74. 288 then: 288 if user
  75. 288 Current.user = user
  76. 288 Current.impersonator = nil
  77. else: 0 else
  78. clear_current_user
  79. end
  80. end
  81. 16 def clear_current_user
  82. 155 Current.user = nil
  83. 155 Current.impersonator = nil
  84. end
  85. 16 def end_impersonation(session)
  86. session.delete(:impersonation)
  87. set_regular_user(session[:user_id])
  88. end
  89. 16 def require_login
  90. 444 then: 289 else: 155 then: 440 else: 4 return if Current.user&.confirmed? || allowed_path?
  91. 4 then: 0 if Current.user && !Current.user.confirmed?
  92. handle_unconfirmed_user
  93. else: 4 else
  94. 4 redirect_to_login
  95. end
  96. end
  97. 16 def allowed_path?
  98. [
  99. 155 login_path,
  100. logout_path,
  101. register_path,
  102. not_confirmed_path,
  103. resend_confirmation_path,
  104. confirm_registration_path,
  105. confirm_registration_submit_path
  106. ].include?(request.path)
  107. end
  108. 16 def handle_unconfirmed_user
  109. else: 0 then: 0 unless [not_confirmed_path, resend_confirmation_path].include?(request.path)
  110. flash[:alert] = "Your account is not confirmed"
  111. redirect_to not_confirmed_path
  112. end
  113. end
  114. 16 def redirect_to_login
  115. 4 flash[:alert] = "You must be logged in"
  116. 4 redirect_to login_url
  117. end
  118. 16 def build_nav_tabs
  119. shares_name =
  120. 204 then: 134 else: 70 (Current.user.admin? || Current.user.organiser?) ? "Sharing" : "Shares"
  121. [
  122. 204 {display_name: "Events", url: events_path},
  123. {display_name: "Bands", url: bands_path},
  124. {display_name: "Members", url: members_path},
  125. {display_name: shares_name, url: permissions_path}
  126. ]
  127. end
  128. end

app/controllers/bands_controller.rb

98.18% lines covered

90.0% branches covered

55 relevant lines. 54 lines covered and 1 lines missed.
20 total branches, 18 branches covered and 2 branches missed.
    
  1. 14 class BandsController < ApplicationController
  2. 14 include AccessPermissions
  3. 14 include EventsHelper
  4. 14 before_action :set_events, except: %i[index new create]
  5. 14 before_action :set_view, only: %i[show edit update confirm_destroy]
  6. 14 before_action :verify_organiser, only: %i[new create]
  7. 14 before_action :verify_organiser_or_admin, only: %i[confirm_destroy destroy]
  8. 14 def index
  9. 8 @bands = sort_bands(@bands, params[:sort])
  10. 8 bands_count = @bands.to_a.size
  11. 8 then: 1 if bands_count == 0
  12. 1 else: 7 redirect_to action: :new
  13. 7 then: 1 else: 6 elsif Current.user.member? && bands_count == 1
  14. 1 redirect_to @bands.first
  15. end
  16. end
  17. 14 def edit
  18. end
  19. 14 def show
  20. end
  21. 14 def confirm_destroy
  22. @view = "overview"
  23. end
  24. 14 def new
  25. 1 @band = Band.new
  26. end
  27. 14 def create
  28. 2 @band = Band.new(band_params)
  29. begin
  30. 2 Band.transaction do
  31. 2 @band.save!
  32. 2 Permission.create!(
  33. item_type: "Band",
  34. item_id: @band.id,
  35. user_id: Current.user.id,
  36. status: :owned,
  37. permission_type: :edit
  38. )
  39. end
  40. 1 redirect_to @band, notice: "Band was successfully created"
  41. rescue ActiveRecord::RecordInvalid
  42. 1 render :new, status: :unprocessable_entity
  43. end
  44. end
  45. 14 def update
  46. 2 then: 1 if @band.update(band_params)
  47. 1 redirect_to @band, notice: "Band was successfully updated."
  48. else: 1 else
  49. 1 render :edit
  50. end
  51. end
  52. 14 def destroy
  53. 2 then: 1 if @band.destroy
  54. 1 redirect_to bands_url, notice: "Band deleted!"
  55. else: 1 else
  56. 1 redirect_to @band, alert: @band.errors.full_messages.to_sentence
  57. end
  58. end
  59. 14 private
  60. 14 def set_events
  61. 7 @events = @band.events
  62. 7 .then { |rel| filter_by_period(rel, params[:period]) }
  63. 7 .then { |rel| sort_results(rel, params[:sort]) }
  64. end
  65. 14 def set_view
  66. 5 @views = %w[overview events shares]
  67. 5 @views_subtitles = [nil, "(#{@band.events.count})", nil]
  68. @view =
  69. 5 then: 0 @views.include?(params["view"]) ?
  70. else: 5 params["view"] :
  71. "overview"
  72. end
  73. 14 def verify_organiser
  74. 4 else: 3 then: 1 redirect_to bands_url unless Current.user.organiser?
  75. end
  76. 14 def verify_organiser_or_admin
  77. 2 else: 2 then: 0 redirect_to bands_url unless Current.user.organiser? || Current.user.admin?
  78. end
  79. 14 def sort_bands(bands, sort_param)
  80. 8 then: 4 else: 4 sort_by, sort_order = sort_param&.split
  81. 8 then: 3 if sort_by.present? && Band.column_names.include?(sort_by)
  82. 3 then: 2 else: 1 bands.order(sort_by => ((sort_order == "DESC") ? :desc : :asc))
  83. else: 5 else
  84. 5 bands.order(name: :asc)
  85. end
  86. end
  87. 14 def band_params
  88. 4 params.require(:band).permit(:name, :description, member_ids: [])
  89. end
  90. end

app/controllers/concerns/access_permissions.rb

100.0% lines covered

100.0% branches covered

17 relevant lines. 17 lines covered and 0 lines missed.
4 total branches, 4 branches covered and 0 branches missed.
    
  1. 16 module AccessPermissions
  2. 16 extend ActiveSupport::Concern
  3. 16 included do
  4. 41 before_action :get_resources
  5. 41 before_action :set_resource, except: %i[index new create]
  6. 41 before_action :deny_read_only, only: %i[edit update destroy]
  7. end
  8. 16 private
  9. 16 def get_resources
  10. 211 resource_class = controller_name.classify.constantize
  11. @resources =
  12. 211 then: 19 Current.user.admin? ?
  13. else: 192 resource_class.all :
  14. Current.user.send(controller_name)
  15. 211 instance_variable_set(:"@#{controller_name}", @resources)
  16. end
  17. 16 def set_resource
  18. 33 @resource = @resources.find(params[:id])
  19. 30 @read_only = @resource.permission_type != "edit"
  20. 30 instance_variable_set(:"@#{controller_name.singularize}", @resource)
  21. end
  22. 16 def deny_read_only
  23. 26 then: 5 else: 21 raise ActiveRecord::RecordNotFound if @read_only
  24. end
  25. end

app/controllers/events_controller.rb

100.0% lines covered

91.67% branches covered

48 relevant lines. 48 lines covered and 0 lines missed.
12 total branches, 11 branches covered and 1 branches missed.
    
  1. 16 class EventsController < ApplicationController
  2. 16 include AccessPermissions
  3. 16 include EventsHelper
  4. 16 before_action :set_bands, only: %i[new edit create update]
  5. 16 before_action :set_view, only: %i[show edit update]
  6. 16 def index
  7. 154 @events = @events
  8. 154 .then { |rel| filter_by_period(rel, params[:period]) }
  9. 154 .then { |rel| filter_by_band(rel, params[:band_id]) }
  10. 154 .then { |rel| sort_results(rel, params[:sort]) }
  11. end
  12. 16 def new
  13. 3 @event = Event.new
  14. 3 param_band_id = Integer(params[:band_id], exception: false)
  15. 3 else: 1 then: 2 param_band_id = nil unless @bands.collect(&:id).include?(param_band_id)
  16. 3 then: 1 else: 2 @event.band_ids = [param_band_id] if param_band_id
  17. end
  18. 16 def show
  19. end
  20. 16 def edit
  21. end
  22. 16 def create
  23. 5 @event = Event.new(event_params)
  24. 4 then: 3 if @event.save
  25. 3 create_owner_permission(@event)
  26. 3 redirect_to @event, notice: "Event was successfully created"
  27. else: 1 else
  28. 1 render :new, status: :unprocessable_entity
  29. end
  30. end
  31. 16 def update
  32. 10 then: 8 if @event.update(event_params)
  33. 8 redirect_to @event, notice: "Event was successfully updated."
  34. else: 1 else
  35. 1 render :edit, status: :unprocessable_entity
  36. end
  37. end
  38. 16 def destroy
  39. 2 @event.destroy!
  40. 1 redirect_to events_url, notice: "Event was successfully destroyed."
  41. rescue ActiveRecord::RecordNotDestroyed
  42. 1 redirect_to @event, alert: @event.errors.full_messages.to_sentence
  43. end
  44. 16 private
  45. 16 def set_bands
  46. 18 @bands = Current.user.bands
  47. end
  48. 16 def set_view
  49. 11 @views = %w[overview bands shares]
  50. 11 @views_subtitles = [nil, "(#{@event.bands.count})", nil]
  51. @view =
  52. 11 then: 0 @views.include?(params["view"]) ?
  53. else: 11 params["view"] :
  54. "overview"
  55. end
  56. 16 def event_params
  57. 15 permitted = params.require(:event).permit(
  58. :name,
  59. :description,
  60. :start_date,
  61. :end_date,
  62. band_ids: []
  63. )
  64. 15 permitted_band_ids = @bands.pluck(:id)
  65. 15 permitted.tap do |params|
  66. 15 params[:band_ids] = params[:band_ids].to_a.compact_blank.map(&:to_i)
  67. 15 invalid_bands = params[:band_ids] - permitted_band_ids
  68. 15 then: 1 else: 14 if invalid_bands.any?
  69. 1 raise ArgumentError, "Invalid band_ids: #{invalid_bands}"
  70. end
  71. end
  72. end
  73. 16 def create_owner_permission(event)
  74. 3 event.permissions.create!(
  75. user: Current.user,
  76. status: :owned,
  77. permission_type: :edit
  78. )
  79. end
  80. end

app/controllers/members_controller.rb

90.7% lines covered

70.0% branches covered

43 relevant lines. 39 lines covered and 4 lines missed.
10 total branches, 7 branches covered and 3 branches missed.
    
  1. 11 class MembersController < ApplicationController
  2. 11 include AccessPermissions
  3. 11 include EventsHelper
  4. 11 before_action :set_member_events, only: :show
  5. 11 before_action :set_view, only: :show
  6. 11 def index
  7. # Get skills for the current set of members
  8. @skills =
  9. 1 Skill.joins(:members)
  10. .where(members: {id: @members.pluck(:id)})
  11. .distinct
  12. .order(:name)
  13. # Filter members by skill if skill_id parameter is present
  14. 1 then: 0 else: 1 if params[:skill_id].present?
  15. @skill = Skill.find(params[:skill_id])
  16. @members =
  17. @members
  18. .joins(:skills)
  19. .where(skills: {id: params[:skill_id]})
  20. end
  21. end
  22. 11 def edit
  23. end
  24. 11 def show
  25. end
  26. 11 def new
  27. 1 @member = Member.new
  28. end
  29. 11 def create
  30. 2 @member = Member.new(member_params)
  31. 2 else: 1 then: 1 return render :new, status: :unprocessable_entity unless @member.save
  32. 1 @permission = Permission.create(
  33. item_type: "Member",
  34. item_id: @member.id,
  35. user: Current.user,
  36. status: :owned,
  37. permission_type: "edit"
  38. )
  39. 1 else: 1 then: 0 return render :new, status: :unprocessable_entity unless @permission.save
  40. 1 redirect_to @member, notice: "Member was successfully created."
  41. end
  42. 11 def update
  43. 2 Member.transaction do
  44. 2 @member.assign_attributes(member_params)
  45. 2 then: 1 if @member.save
  46. 1 redirect_to @member, notice: "Member was successfully updated."
  47. else: 1 else
  48. 1 logger.warn "members/update error: #{@member.errors.full_messages}"
  49. 1 render :edit, status: :unprocessable_entity
  50. end
  51. end
  52. rescue ActiveRecord::RecordInvalid => e
  53. logger.warn "members/update transaction error: #{e.message}"
  54. render :edit, status: :unprocessable_entity
  55. end
  56. 11 def destroy
  57. 1 @member.destroy!
  58. 1 redirect_to members_url, notice: "Member was successfully destroyed."
  59. end
  60. 11 private
  61. 11 def member_params
  62. 4 params.require(:member).permit(
  63. :name, :description, :skills_list, band_ids: []
  64. )
  65. end
  66. 11 def set_view
  67. 1 @views = %w[overview events]
  68. 1 @views_subtitles = [nil, "(#{@events.count})"]
  69. @view =
  70. 1 then: 0 @views.include?(params["view"]) ?
  71. else: 1 params["view"] :
  72. "overview"
  73. end
  74. 11 def set_member_events
  75. 1 @events = @member.events
  76. 1 .then { |rel| filter_by_period(rel, params[:period]) }
  77. 1 .then { |rel| sort_results(rel, params[:sort]) }
  78. end
  79. end

app/controllers/permissions_controller.rb

71.88% lines covered

42.55% branches covered

96 relevant lines. 69 lines covered and 27 lines missed.
47 total branches, 20 branches covered and 27 branches missed.
    
  1. 13 class PermissionsController < ApplicationController
  2. 13 include PermissionsHelper
  3. 13 before_action :set_permission, only: [:update, :destroy]
  4. 13 before_action :organiser_only, only: [:new, :create]
  5. 13 before_action :invitee_and_pending_only, only: [:update]
  6. 13 before_action :bestower_and_admin_only, only: [:destroy]
  7. 13 before_action :set_form_options, only: [:new, :create]
  8. 13 def index
  9. 6 then: 1 else: 5 @permissions = Current.user.admin? ? Permission.all : Permission.for_user(Current.user)
  10. 6 @permissions = sort_permissions(@permissions, params[:direct_sort])
  11. 6 @other_items = fetch_effective_permissions
  12. 6 @other_items = sort_effective_items(@other_items, params[:effective_sort])
  13. 6 respond_to do |format|
  14. 6 format.html
  15. 6 format.json { render json: {permissions: @permissions, effective: @other_items} }
  16. end
  17. end
  18. 13 def new
  19. 1 @permission = Permission.new
  20. 1 then: 0 else: 1 if params[:item_type].present? && params[:item_id].present?
  21. @preselected_item = "#{params[:item_type].capitalize}_#{params[:item_id]}"
  22. end
  23. 1 @no_users_available = potential_users.empty?
  24. end
  25. 13 def create
  26. 5 params = permission_create_params
  27. 5 item_param = params.delete(:item)
  28. 5 @permission = Permission.new(params)
  29. 5 @permission.bestowing_user = Current.user
  30. 5 @permission.status = "pending"
  31. 5 @permission.item_type, @permission.item_id = item_param.split("_")
  32. 5 then: 4 else: 1 @permission.item_type&.capitalize!
  33. 5 else: 1 then: 2 return render :new, status: :unprocessable_entity unless @permission.save
  34. 1 redirect_to permissions_path, notice: "Invitation created"
  35. end
  36. 13 def update
  37. 3 status = permission_update_params[:status]
  38. 3 then: 2 if @permission.update(status: status)
  39. 2 then: 1 if status == "accepted"
  40. 1 redirect_to @permission.item_path, notice: "Invitation accepted"
  41. else: 1 else
  42. 1 redirect_to permissions_path, alert: "Invitation rejected"
  43. end
  44. else: 0 else
  45. render plain: "Not updated", status: :bad_request
  46. end
  47. end
  48. 13 def destroy
  49. 2 @permission.destroy!
  50. 2 redirect_to root_path, notice: "Invitation deleted"
  51. end
  52. 13 private
  53. 13 def sort_permissions(permissions, sort_param)
  54. 6 then: 6 else: 0 return permissions.order(created_at: :desc) if sort_param.blank?
  55. column, direction = sort_param.split
  56. then: 0 else: 0 then: 0 else: 0 direction_sym = (direction&.downcase == "desc") ? :desc : :asc
  57. case column
  58. when: 0 when "name"
  59. joins_sql = <<~SQL
  60. LEFT JOIN bands
  61. ON bands.id = permissions.item_id
  62. AND permissions.item_type = 'Band'
  63. LEFT JOIN events
  64. ON events.id = permissions.item_id
  65. AND permissions.item_type = 'Event'
  66. LEFT JOIN members
  67. ON members.id = permissions.item_id
  68. AND permissions.item_type = 'Member'
  69. SQL
  70. permissions.joins(joins_sql)
  71. .order(Arel.sql(
  72. "COALESCE(bands.name, events.name, members.name) #{direction_sym}"
  73. ))
  74. when: 0 when "type"
  75. permissions.order(item_type: direction_sym)
  76. when: 0 when "access"
  77. permissions.order(permission_type: direction_sym)
  78. when: 0 when "status"
  79. permissions.order(status: direction_sym)
  80. when: 0 when "bestower"
  81. permissions.joins(:bestowing_user).order(Arel.sql(
  82. "users.username #{direction_sym}"
  83. ))
  84. when: 0 when "created"
  85. permissions.order(created_at: direction_sym)
  86. when: 0 when "last_modified"
  87. permissions.order(updated_at: direction_sym)
  88. else: 0 else
  89. permissions.order(created_at: :desc)
  90. end
  91. end
  92. 13 def sort_effective_items(items, sort_param)
  93. 10 then: 6 else: 0 return items.sort_by { |item| item.name.downcase } if sort_param.blank?
  94. column, direction = sort_param.split
  95. then: 0 else: 0 desc = (direction&.downcase == "desc")
  96. sorted_items = case column
  97. when: 0 when "name"
  98. items.sort_by { |item| item.name.downcase }
  99. when: 0 when "type"
  100. items.sort_by { |item| item.class.name }
  101. when: 0 when "status"
  102. items.sort_by { |item| item.permission_type.to_s }
  103. when: 0 when "source"
  104. items.sort_by do |item|
  105. permission = find_effective_permission_source(Current.user, item)
  106. then: 0 if permission.nil?
  107. "AAA_Direct" # Sort direct permissions first
  108. else: 0 else
  109. "#{permission.item_type}_#{permission.item.name}"
  110. end
  111. end
  112. else: 0 else
  113. items.sort_by { |item| item.name.downcase }
  114. end
  115. then: 0 else: 0 desc ? sorted_items.reverse : sorted_items
  116. end
  117. 13 def fetch_effective_permissions
  118. events =
  119. 6 Event.permitted_for(Current.user.id)
  120. .includes(:bands, :permissions)
  121. bands =
  122. 6 Band.permitted_for(Current.user.id)
  123. .includes(:members, :events, :permission)
  124. members =
  125. 6 Member.permitted_for(Current.user.id)
  126. .includes(:bands, :skills)
  127. 6 events + bands + members
  128. end
  129. 13 def organiser_only
  130. 8 else: 6 then: 2 unless Current.user.organiser?
  131. 2 render plain: "Organisers only", status: :forbidden
  132. end
  133. end
  134. 13 def bestower_and_admin_only
  135. 3 else: 2 then: 1 unless Current.user.admin? || @permission.bestowing_user == Current.user
  136. 1 render plain: "Bestower only", status: :forbidden
  137. end
  138. end
  139. 13 def invitee_and_pending_only
  140. 7 then: 3 if @permission.user != Current.user
  141. 3 else: 4 render plain: "Invitee only", status: :forbidden
  142. 4 then: 1 else: 3 elsif !@permission.pending?
  143. 1 render plain: "Invite not pending", status: :bad_request
  144. end
  145. end
  146. 13 def set_form_options
  147. 6 @users = potential_users
  148. 6 @items = potential_items(Current.user)
  149. 6 @permission_types = %w[view edit]
  150. end
  151. 13 def set_permission
  152. 10 @permission = Permission.find(params[:id])
  153. end
  154. 13 def permission_create_params
  155. 5 params.require(:permission).permit(:user_id, :item, :permission_type)
  156. end
  157. 13 def permission_update_params
  158. 3 params.require(:permission).permit(:status)
  159. end
  160. end

app/controllers/sessions_controller.rb

100.0% lines covered

100.0% branches covered

28 relevant lines. 28 lines covered and 0 lines missed.
6 total branches, 6 branches covered and 0 branches missed.
    
  1. 16 class SessionsController < ApplicationController
  2. 16 def new
  3. 5 then: 3 else: 2 redirect_to events_path if Current.user
  4. 5 @user = User.new
  5. end
  6. 16 def create
  7. 143 username, password = login_params.values_at(:username, :password)
  8. 142 logger.debug("Login attempt from #{username}")
  9. 142 user = User.find_or_create_by(username: username)
  10. 142 then: 1 if Rails.env.development? && params.dig(:user, :debug_skip)
  11. 1 session[:user_id] = user.id
  12. 1 logger.debug("Skipped login: #{username}")
  13. 1 else: 141 redirect_to events_path, notice: "Skipped login."
  14. 141 then: 139 elsif user.authenticate(password)
  15. 139 session[:user_id] = user.id
  16. 139 logger.debug("Login success: #{username}")
  17. 139 redirect_to events_path, notice: "Logged in successfully."
  18. else: 2 else
  19. 2 @user = User.new
  20. 2 @user.username = username
  21. 2 logger.debug "Login failed: #{user.inspect}"
  22. 2 flash[:alert] = "Invalid username or password"
  23. 2 render :new, status: :unprocessable_entity
  24. end
  25. end
  26. 16 def destroy
  27. 1 session[:user_id] = nil
  28. 1 session[:impersonation] = nil
  29. 1 redirect_to login_path, notice: "Logged out successfully."
  30. end
  31. 16 private
  32. 16 def login_params
  33. 143 params.require(:user).permit(:username, :password)
  34. end
  35. end

app/controllers/user_mails_controller.rb

100.0% lines covered

100.0% branches covered

19 relevant lines. 19 lines covered and 0 lines missed.
6 total branches, 6 branches covered and 0 branches missed.
    
  1. 9 class UserMailsController < ApplicationController
  2. 9 before_action :set_mail, only: :show
  3. 9 before_action :can_view_mail, only: :show
  4. 9 def show
  5. end
  6. 9 def index
  7. @mails =
  8. 3 then: 1 if Current.user.admin?
  9. 1 UserMail.order(updated_at: :desc)
  10. else: 2 else
  11. 2 UserMail.where(user_id: Current.user.id).order(updated_at: :desc)
  12. end
  13. end
  14. 9 def create
  15. 2 @mail = UserMailer.send_confirmation_registration(Current.user)
  16. 2 then: 1 if @mail.persisted?
  17. 1 redirect_to user_mails_path, notice: "Confirmation sent successfully"
  18. else: 1 else
  19. 1 redirect_to user_mails_path, alert: "Failed to send confirmation"
  20. end
  21. end
  22. 9 private
  23. 9 def set_mail
  24. 5 @mail = UserMail.find(params[:id])
  25. end
  26. 9 def can_view_mail
  27. 5 then: 3 else: 2 return if @mail && (@mail.user_id == Current.user.id || Current.user.admin?)
  28. 2 redirect_to user_mails_path, alert: "Cannot view this mail"
  29. end
  30. end

app/controllers/users/registration_controller.rb

100.0% lines covered

85.71% branches covered

34 relevant lines. 34 lines covered and 0 lines missed.
14 total branches, 12 branches covered and 2 branches missed.
    
  1. 9 class Users::RegistrationController < ApplicationController
  2. 9 before_action :set_user, only: %i[
  3. edit
  4. update
  5. ]
  6. 9 def edit
  7. end
  8. 9 def update
  9. 1 else: 0 then: 1 UserMailer.send_confirmation_registration(@user) unless @user.confirmed?
  10. end
  11. 9 def new
  12. 4 @token = params[:token]
  13. 4 verifier = Rails.application.config.message_verifier
  14. begin
  15. 4 data = verifier.verify(@token)
  16. 3 @user = User.find(data["user_id"])
  17. 3 then: 1 else: 2 if @user.confirmed?
  18. 1 redirect_to user_path(@user),
  19. notice: "Your account is already confirmed"
  20. end
  21. 3 then: 1 else: 2 then: 1 else: 2 if data["expires_at"]&.< Time.current.to_i
  22. 1 flash[:alert] = "Your confirmation link has expired: #{data}."
  23. 1 redirect_to register_path
  24. end
  25. rescue ActiveSupport::MessageVerifier::InvalidSignature
  26. 1 flash[:alert] = "Invalid confirmation link."
  27. 1 redirect_to register_path
  28. end
  29. end
  30. 9 def create
  31. 3 token = params[:token]
  32. 3 verifier = Rails.application.config.message_verifier
  33. begin
  34. 3 data = verifier.verify(token)
  35. 2 then: 2 else: 0 then: 1 if data["expires_at"]&.>= Time.current.to_i
  36. 1 @user = User.find(data["user_id"])
  37. 1 @user.update(confirmed: true)
  38. 1 flash[:notice] = "Your registration has been confirmed."
  39. 1 redirect_to login_path
  40. else: 1 else
  41. 1 flash[:alert] = "Your confirmation link has expired: #{data}."
  42. 1 redirect_to register_path
  43. end
  44. rescue ActiveSupport::MessageVerifier::InvalidSignature
  45. 1 flash[:alert] = "Invalid confirmation link."
  46. 1 redirect_to register_path
  47. end
  48. end
  49. 9 private
  50. 9 def set_user
  51. 2 @user = User.find(session[:user_id])
  52. 2 then: 1 else: 1 redirect_to user_path(@user) if @user.confirmed?
  53. end
  54. end

app/controllers/users_controller.rb

100.0% lines covered

85.71% branches covered

41 relevant lines. 41 lines covered and 0 lines missed.
14 total branches, 12 branches covered and 2 branches missed.
    
  1. 8 class UsersController < ApplicationController
  2. 8 before_action :check_admin_user, only: %i[
  3. create
  4. new
  5. ]
  6. 8 before_action :check_not_logged_in, only: %i[
  7. create
  8. new
  9. ]
  10. 8 def new
  11. 1 @user = User.new
  12. end
  13. 8 def create
  14. 4 @user = User.new(create_user_params)
  15. 4 then: 1 else: 3 if @user.admin? && !@allow_admin_user
  16. 1 flash.now[:alert] = "No more admin users allowed"
  17. 1 return render :new, status: :unprocessable_entity
  18. end
  19. 3 then: 2 if @user.save
  20. 2 session[:user_id] = @user.id
  21. 2 redirect_to account_path, notice: "You registered successfully. Well done!"
  22. else: 1 else
  23. 1 render :new, status: :unprocessable_entity
  24. end
  25. end
  26. 8 def index
  27. 1 redirect_to user_path(Current.user)
  28. end
  29. 8 def show
  30. 4 @user = User.find_by(username: params[:id])
  31. 4 else: 4 then: 0 redirect_to user_path(Current.user) unless @user
  32. end
  33. 8 def edit
  34. 1 @user = Current.user
  35. 1 else: 0 then: 1 unless params[:id] == @user.username
  36. 1 redirect_to user_path(@user), alert: "Can't edit other users"
  37. end
  38. end
  39. 8 def update
  40. 7 then: 6 else: 1 if params[:user][:password].blank?
  41. 6 params[:user].delete(:password)
  42. 6 params[:user].delete(:password_confirmation)
  43. end
  44. 7 @user = Current.user
  45. 7 then: 5 if @user.update(update_user_params)
  46. 5 redirect_to user_url(@user), notice: "Updated successfully"
  47. else: 2 else
  48. 2 errors = @user.errors.full_messages.join(", ").downcase
  49. 2 render plain: "Could not update user: #{errors}",
  50. status: :forbidden
  51. end
  52. end
  53. 8 private
  54. 8 def create_user_params
  55. 4 params.require(:user).permit(
  56. :username,
  57. :name,
  58. :email,
  59. :password,
  60. :password_confirmation,
  61. :time_zone,
  62. :user_type
  63. )
  64. end
  65. 8 def update_user_params
  66. 7 params.require(:user).permit(
  67. :name,
  68. :email,
  69. :password,
  70. :password_confirmation,
  71. :time_zone
  72. )
  73. end
  74. 8 def check_admin_user
  75. 6 @allow_admin_user = User.admin.count.zero?
  76. end
  77. 8 def check_not_logged_in
  78. 6 else: 5 then: 1 redirect_to account_path unless Current.user.nil?
  79. end
  80. end

app/helpers/application_helper.rb

100.0% lines covered

100.0% branches covered

47 relevant lines. 47 lines covered and 0 lines missed.
15 total branches, 15 branches covered and 0 branches missed.
    
  1. 1 module ApplicationHelper
  2. 1 def page_heading(str)
  3. 171 content_tag(:h1, str, class: "text-gray-900 text-xl")
  4. end
  5. 1 def filter_link(name, path, css_class, active)
  6. 464 link_to_if(!active, name, path, class: "#{css_class} hover:underline") do
  7. 154 content_tag(:span, name, class: "#{css_class} font-bold")
  8. end
  9. end
  10. 1 def table_headers(sort_param_name, columns, resource = nil)
  11. 9 query_params = request.query_parameters.merge({}).except(sort_param_name)
  12. 9 capture do
  13. 9 columns.each do |column|
  14. 46 concat(
  15. content_tag(:th,
  16. table_header_sort(
  17. resource || controller_name,
  18. column[:name],
  19. column[:display],
  20. column[:default],
  21. query_params,
  22. sort_param_name
  23. ),
  24. class: column[:class],
  25. 46 then: 13 else: 33 style: column[:wide] ? nil : "width: 1%")
  26. )
  27. end
  28. end
  29. end
  30. 1 def table_header_sort(
  31. resource,
  32. column,
  33. display_text,
  34. default_sort_column = nil,
  35. request_params = nil,
  36. sort_param_name = :sort,
  37. default_sort_direction = "asc"
  38. )
  39. 282 sort_param = sort_param_name.to_sym
  40. 282 then: 32 else: 250 current_sort_column, current_sort_direction = params[sort_param]&.split
  41. 282 current_sort_column ||= default_sort_column
  42. 282 current_sort_direction ||= default_sort_direction
  43. 282 then: 103 if current_sort_column == column
  44. 103 then: 97 else: 6 new_sort_direction = (current_sort_direction == "asc") ? "desc" : "asc"
  45. 103 sort_icon = sort_direction_icon(current_sort_direction)
  46. else: 179 else
  47. 179 new_sort_direction = default_sort_direction
  48. 179 sort_icon = ""
  49. end
  50. 282 query_params = request_params || request.query_parameters
  51. 282 new_params = query_params
  52. .except(:page, sort_param)
  53. .merge(sort_param => "#{column} #{new_sort_direction}")
  54. # Preserve period parameter if it exists
  55. 282 then: 55 else: 227 new_params[:period] = request.params[:period] if request.params[:period]
  56. 282 sort_url = url_for(new_params)
  57. 282 link_to "#{display_text}#{sort_icon}".html_safe, sort_url
  58. end
  59. 1 def flash_banner(key, msg = nil, &block)
  60. 159 content_tag :div, class: "#{key} banner" do
  61. 159 then: 6 else: 153 if block && msg.blank?
  62. 6 msg = capture(&block)
  63. end
  64. 159 concat(content_tag(:div, msg))
  65. 159 concat(hidden_checkbox_tag(key))
  66. 159 concat(close_button(key))
  67. end
  68. end
  69. 1 private
  70. 1 def sort_direction_icon(direction)
  71. 103 when: 97 case direction
  72. 97 when: 1 when "asc" then " <span>â–¼</span>"
  73. 1 else: 5 when "desc" then " <span>â–²</span>"
  74. 5 else ""
  75. end
  76. end
  77. 1 def hidden_checkbox_tag(key)
  78. 159 tag.input type: "checkbox", class: "hidden", id: "banneralert-#{key}"
  79. end
  80. 1 def close_button(key)
  81. 159 tag.label class: "close", style: "width:30px", for: "banneralert-#{key}" do
  82. 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
  83. 159 concat(tag.title("Close"))
  84. 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"))
  85. end
  86. end
  87. end
  88. end

app/helpers/bands_helper.rb

100.0% lines covered

100.0% branches covered

1 relevant lines. 1 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. 1 module BandsHelper
  2. end

app/helpers/events_helper.rb

100.0% lines covered

100.0% branches covered

32 relevant lines. 32 lines covered and 0 lines missed.
25 total branches, 25 branches covered and 0 branches missed.
    
  1. 1 module EventsHelper
  2. 1 def filter_by_period(events, period)
  3. 162 when: 1 case period
  4. 1 when: 152 when "past" then events.past
  5. 152 else: 9 when "future", nil then events.future
  6. 9 else events
  7. end
  8. end
  9. 1 def filter_by_band(events, band_id)
  10. 154 else: 1 then: 153 return events unless band_id.present? && band_id.to_i.positive?
  11. 1 events.joins(:bands).where(bands: {id: band_id})
  12. end
  13. 1 def sort_results(events, sort_param)
  14. 162 column, direction = extract_sort_params(sort_param)
  15. 162 else: 161 then: 1 unless Event.attribute_names.include?(column.to_s)
  16. 1 return events.order(:start_date)
  17. end
  18. 161 events.order(column => direction)
  19. end
  20. 1 def extract_sort_params(sort_param)
  21. 162 col, dir = sort_param.to_s.split
  22. 162 then: 3 else: 159 dir = %w[desc DESC].include?(dir) ? :desc : :asc
  23. 162 [col.presence || :start_date, dir]
  24. end
  25. 1 def convert_seconds_to_duration(seconds)
  26. 9 then: 2 else: 7 return nil if seconds < 60
  27. 7 days = (seconds / 86400).floor
  28. 7 remaining = seconds % 86400
  29. 7 hours = (remaining / 3600).floor
  30. 7 remaining %= 3600
  31. 7 minutes = (remaining / 60).floor
  32. 7 parts = []
  33. 7 then: 5 else: 2 else: 4 then: 1 parts << "#{days} day#{"s" unless days == 1}" if days > 0
  34. 7 then: 2 else: 5 else: 1 then: 1 parts << "#{hours} hour#{"s" unless hours == 1}" if hours > 0
  35. 7 then: 4 else: 3 else: 1 then: 3 parts << "#{minutes} minute#{"s" unless minutes == 1}" if minutes > 0
  36. 7 when: 4 case parts.size
  37. 4 when 1 then parts.first
  38. else: 3 else
  39. 3 parts[0...-1].join(", ") + " and #{parts.last}"
  40. end
  41. end
  42. end

app/helpers/members_helper.rb

100.0% lines covered

100.0% branches covered

1 relevant lines. 1 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. 1 module MembersHelper
  2. end

app/helpers/permissions_helper.rb

50.0% lines covered

24.24% branches covered

66 relevant lines. 33 lines covered and 33 lines missed.
33 total branches, 8 branches covered and 25 branches missed.
    
  1. 1 module PermissionsHelper
  2. 1 include ActionView::Helpers::UrlHelper
  3. 1 def potential_users
  4. # Get all non-admin users (both members and organisers) sorted by username,
  5. # excluding current user
  6. users =
  7. 7 User
  8. .where
  9. .not(user_type: :admin)
  10. .where
  11. .not(id: Current.user.id)
  12. .order(:username)
  13. .pluck(:username, :id)
  14. 7 if users.any?
  15. then: 7 # Add a "Please select" option with empty value as the first option
  16. 7 [["Please select", ""]] + users
  17. else: 0 else
  18. []
  19. end
  20. end
  21. 1 def potential_items(owner)
  22. 74 invitable_klasses = [Event, Band, Member]
  23. 74 invitable_klasses.flat_map do |klass|
  24. 222 pluck_and_prefix(klass, owner)
  25. end
  26. end
  27. 1 def permission_status_color(status)
  28. case status.to_s
  29. when: 0 when 'owned'
  30. 'primary'
  31. when: 0 when 'accepted'
  32. 'success'
  33. when: 0 when 'pending'
  34. 'warning'
  35. when: 0 when 'rejected'
  36. 'danger'
  37. else: 0 else
  38. 'secondary'
  39. end
  40. end
  41. 1 def get_access_level(user, item)
  42. 19 when: 8 case item
  43. 8 when: 6 when Member then get_ownership(user, item, :members, Member)
  44. 6 when: 5 when Band then get_ownership(user, item, :bands, Band)
  45. 5 else: 0 when Event then get_ownership(user, item, :events, Event)
  46. else raise "Invalid item type: #{item.class}"
  47. end
  48. end
  49. 1 def get_ownership(user, item, method, klass)
  50. 19 then: 18 if (direct_permission = user.send(method).find_by(id: item.id))
  51. 18 else: 1 direct_permission[:permission_type]
  52. 1 then: 0 else: 1 elsif klass.permitted_for(user.id).find_by(id: item.id)
  53. "view"
  54. end
  55. end
  56. 1 def find_effective_permission_source(user, item)
  57. # Direct permission check
  58. 4 direct_permission = Permission.where(
  59. user_id: user.id,
  60. item_type: item.class.to_s,
  61. item_id: item.id,
  62. status: ['owned', 'accepted']
  63. ).first
  64. 4 then: 4 else: 0 return nil if direct_permission
  65. else: 0 case item
  66. when: 0 when Band
  67. find_band_permission_source(user, item)
  68. when: 0 when Event
  69. find_event_permission_source(user, item)
  70. when: 0 when Member
  71. find_member_permission_source(user, item)
  72. end
  73. end
  74. 1 def find_band_permission_source(user, band)
  75. # Check permissions through members
  76. band.members.each do |member|
  77. permission = Permission.where(
  78. user_id: user.id,
  79. item_type: 'Member',
  80. item_id: member.id,
  81. status: ['owned', 'accepted']
  82. ).first
  83. then: 0 else: 0 return permission if permission
  84. end
  85. # Check permissions through events
  86. band.events.each do |event|
  87. permission = Permission.where(
  88. user_id: user.id,
  89. item_type: 'Event',
  90. item_id: event.id,
  91. status: ['owned', 'accepted']
  92. ).first
  93. then: 0 else: 0 return permission if permission
  94. end
  95. nil
  96. end
  97. 1 def find_event_permission_source(user, event)
  98. # Check permissions through bands
  99. event.bands.each do |band|
  100. permission = Permission.where(
  101. user_id: user.id,
  102. item_type: 'Band',
  103. item_id: band.id,
  104. status: ['owned', 'accepted']
  105. ).first
  106. then: 0 else: 0 return permission if permission
  107. end
  108. # Check permissions through members in bands
  109. event.bands.each do |band|
  110. band.members.each do |member|
  111. permission = Permission.where(
  112. user_id: user.id,
  113. item_type: 'Member',
  114. item_id: member.id,
  115. status: ['owned', 'accepted']
  116. ).first
  117. then: 0 else: 0 return permission if permission
  118. end
  119. end
  120. nil
  121. end
  122. 1 def find_member_permission_source(user, member)
  123. # Check permissions through bands
  124. member.bands.each do |band|
  125. permission = Permission.where(
  126. user_id: user.id,
  127. item_type: 'Band',
  128. item_id: band.id,
  129. status: ['owned', 'accepted']
  130. ).first
  131. then: 0 else: 0 return permission if permission
  132. end
  133. # Check permissions through events via bands
  134. member.bands.each do |band|
  135. band.events.each do |event|
  136. permission = Permission.where(
  137. user_id: user.id,
  138. item_type: 'Event',
  139. item_id: event.id,
  140. status: ['owned', 'accepted']
  141. ).first
  142. then: 0 else: 0 return permission if permission
  143. end
  144. end
  145. nil
  146. end
  147. 1 private
  148. 1 def pluck_and_prefix(klass, owner)
  149. 222 owned_permissions = owner.send(:"#{klass.to_s.downcase.pluralize}")
  150. 222 owned_item_ids = owned_permissions.pluck(:id)
  151. 222 items = klass.where(id: owned_item_ids).pluck(:id, :name)
  152. 222 items.map do |id, name|
  153. 84 ["#{name} (#{klass})", "#{klass}_#{id}"]
  154. end
  155. end
  156. end

app/helpers/sessions_helper.rb

100.0% lines covered

100.0% branches covered

1 relevant lines. 1 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. 1 module SessionsHelper
  2. end

app/helpers/users/registration_helper.rb

100.0% lines covered

100.0% branches covered

1 relevant lines. 1 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. 1 module Users::RegistrationHelper
  2. end

app/jobs/application_job.rb

100.0% lines covered

100.0% branches covered

1 relevant lines. 1 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. 1 class ApplicationJob < ActiveJob::Base
  2. # Automatically retry jobs that encountered a deadlock
  3. # retry_on ActiveRecord::Deadlocked
  4. # Most jobs are safe to ignore if the underlying records are no longer available
  5. # discard_on ActiveJob::DeserializationError
  6. end

app/jobs/send_mail_job.rb

66.67% lines covered

100.0% branches covered

6 relevant lines. 4 lines covered and 2 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. 1 class SendMailJob < ApplicationJob
  2. 1 queue_as :default
  3. 1 retry_on StandardError, wait: 1.minute, attempts: 5
  4. 1 def perform(user_mail_id)
  5. user_mail = UserMail.find(user_mail_id)
  6. user_mail.attempt_send
  7. end
  8. end

app/lib/form_builders/nice_form_builder.rb

100.0% lines covered

72.73% branches covered

89 relevant lines. 89 lines covered and 0 lines missed.
22 total branches, 16 branches covered and 6 branches missed.
    
  1. 13 module FormBuilders
  2. 13 class NiceFormBuilder < ActionView::Helpers::FormBuilder
  3. 13 class_attribute :text_field_helpers,
  4. default: field_helpers - %i[
  5. label
  6. check_box
  7. radio_button
  8. fields_for
  9. fields
  10. hidden_field
  11. file_field
  12. ]
  13. 13 TEXT_FIELD_STYLE = "text_field".freeze
  14. 13 TEXT_AREA_STYLE = "textarea_field".freeze
  15. 13 SELECT_FIELD_STYLE = "select_field".freeze
  16. 13 SUBMIT_BUTTON_STYLE = "primary_button".freeze
  17. 13 CHECKBOX_FIELD_STYLE = "checkbox_group".freeze
  18. 13 DATE_SELECT_STYLE = "date_select".freeze
  19. 13 TIME_SELECT_STYLE = "time_select".freeze
  20. 13 text_field_helpers.each do |field_method|
  21. 234 define_method(field_method) do |method, options = {}|
  22. 104 then: 52 if options.delete(:already_nice)
  23. 52 super(method, options)
  24. else: 52 else
  25. 52 text_like_field(field_method, method, options)
  26. end
  27. end
  28. end
  29. 13 def submit(value = nil, options = {})
  30. 27 custom_opts, opts = partition_custom_opts(options)
  31. 27 classes = apply_style_classes(SUBMIT_BUTTON_STYLE, custom_opts)
  32. 27 super(value, {class: classes}.merge(opts))
  33. end
  34. 13 def select(method, choices = nil, options = {}, html_options = {}, &block)
  35. 15 custom_opts, opts = partition_custom_opts(options)
  36. 15 classes = apply_style_classes(SELECT_FIELD_STYLE, custom_opts, method)
  37. 15 labels = labels(method, custom_opts[:label], options)
  38. 15 field = super(
  39. method,
  40. choices,
  41. opts,
  42. html_options.merge({class: classes}),
  43. &block
  44. )
  45. 15 @template.content_tag("div", labels + field, {class: "field"})
  46. end
  47. 13 def check_box(method, options = {}, checked_value = "1", unchecked_value = "0")
  48. 3 custom_opts, opts = partition_custom_opts(options)
  49. 3 classes = apply_style_classes(CHECKBOX_FIELD_STYLE, custom_opts, method)
  50. 3 labels = labels(method, custom_opts[:label], options)
  51. 3 field = super(method, opts.merge({class: classes}), checked_value, unchecked_value)
  52. 3 @template.content_tag("div", labels + field, {class: "field"})
  53. end
  54. 13 def collection_checkboxes(
  55. method,
  56. collection,
  57. value_method,
  58. text_method,
  59. options = {},
  60. html_options = {},
  61. &block
  62. )
  63. 5 custom_opts, opts = partition_custom_opts(options)
  64. 5 classes = apply_style_classes(
  65. CHECKBOX_FIELD_STYLE,
  66. custom_opts,
  67. method
  68. )
  69. 5 labels = labels(method, custom_opts[:label], options)
  70. 5 html_options = html_options.merge(class: classes)
  71. 5 check_boxes = super(
  72. method,
  73. collection,
  74. value_method,
  75. text_method,
  76. opts,
  77. html_options,
  78. &block
  79. )
  80. 5 @template.content_tag(
  81. "div",
  82. labels + check_boxes,
  83. {class: "field"}
  84. )
  85. end
  86. 13 def date_select(method, options = {}, html_options = {})
  87. 10 custom_opts, opts = partition_custom_opts(options)
  88. 10 classes = apply_style_classes(nil, custom_opts, method)
  89. 10 labels = labels(method, custom_opts[:label], options)
  90. 10 field = @template.content_tag(
  91. "div",
  92. super(
  93. method,
  94. opts,
  95. html_options.merge(class: classes)
  96. ),
  97. {class: "flex flex-row gap-4"}
  98. )
  99. 10 field_classes = ["field"]
  100. 10 then: 10 else: 0 field_classes << custom_opts[:field_class] if custom_opts[:field_class]
  101. 10 @template.content_tag(
  102. "div",
  103. labels + field,
  104. {class: field_classes.join(" ")}
  105. )
  106. end
  107. 13 def time_select(method, options = {}, html_options = {})
  108. 10 custom_opts, opts = partition_custom_opts(options)
  109. 10 classes = apply_style_classes(nil, custom_opts, method)
  110. 10 labels = labels(method, custom_opts[:label], options)
  111. 10 field = @template.content_tag(
  112. "div",
  113. super(
  114. method,
  115. opts,
  116. html_options.merge(class: classes)
  117. ),
  118. {class: "flex flex-row gap-4"}
  119. )
  120. 10 field_classes = ["field"]
  121. 10 then: 10 else: 0 field_classes << custom_opts[:field_class] if custom_opts[:field_class]
  122. 10 @template.content_tag(
  123. "div",
  124. labels + field,
  125. {class: field_classes.join(" ")}
  126. )
  127. end
  128. 13 private
  129. 13 def text_like_field(field_method, object_method, options = {})
  130. 52 custom_opts, opts = partition_custom_opts(options)
  131. 52 then: 0 else: 52 style = (field_method == :text_area) ? TEXT_AREA_STYLE : TEXT_FIELD_STYLE
  132. 52 classes = apply_style_classes(style, custom_opts, object_method)
  133. field_options = {
  134. 52 class: classes,
  135. then: 52 else: 0 title: errors_for(object_method)&.join(" ")
  136. }.compact.merge(opts).merge(already_nice: true)
  137. 52 field = send(field_method, object_method, field_options)
  138. 52 labels = labels(object_method, custom_opts[:label], options)
  139. 52 @template.content_tag("div", labels + field, {class: "field"})
  140. end
  141. 13 def labels(object_method, label_options, field_options)
  142. 95 label = nice_label(object_method, label_options, field_options)
  143. 95 error_label = error_label(object_method, field_options)
  144. 95 label + error_label
  145. end
  146. 13 def nice_label(object_method, label_options, field_options)
  147. text, label_opts =
  148. 99 then: 32 label_options.present? ?
  149. else: 67 label_options.values_at(:text, :except) :
  150. [nil, {}]
  151. 99 label_opts ||= {}
  152. 99 label_classes = label_opts[:class].to_s
  153. 99 then: 0 else: 99 label_classes += " disabled" if field_options[:disabled]
  154. 99 label(object_method, text, {
  155. class: label_classes.strip
  156. }.merge(label_opts.except(:class)))
  157. end
  158. 13 def error_label(object_method, options)
  159. 95 errors = errors_for(object_method)
  160. 95 then: 91 else: 4 return if errors.blank?
  161. 4 error_message = errors.collect(&:titleize).join(", ")
  162. 4 nice_label(
  163. object_method,
  164. {text: error_message, class: "error_message"},
  165. options
  166. )
  167. end
  168. 13 def border_color_classes(object_method)
  169. 122 else: 95 then: 27 return "" unless object_method
  170. 95 then: 4 else: 91 "has_error" if errors_for(object_method).present?
  171. end
  172. 13 def apply_style_classes(base_class, custom_opts, object_method = nil)
  173. [
  174. 122 base_class,
  175. border_color_classes(object_method),
  176. custom_opts[:class]
  177. ].compact.join(" ")
  178. end
  179. 13 CUSTOM_OPTS = [:label, :class, :field_class].freeze
  180. 13 def partition_custom_opts(opts)
  181. 204 opts.partition { |k, _| CUSTOM_OPTS.include?(k) }.map(&:to_h)
  182. end
  183. 13 def errors_for(object_method)
  184. 242 else: 242 then: 0 return unless @object.present? && object_method.present?
  185. 242 @object.errors[object_method]
  186. end
  187. end
  188. end

app/mailers/application_mailer.rb

100.0% lines covered

100.0% branches covered

2 relevant lines. 2 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. 8 class ApplicationMailer < ActionMailer::Base
  2. 8 layout "mailer"
  3. end

app/mailers/test_mailer.rb

0.0% lines covered

100.0% branches covered

10 relevant lines. 0 lines covered and 10 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. class TestMailer < ApplicationMailer
  2. def test_email(to)
  3. Rails.logger.info "Preparing to send test email to #{to}."
  4. mail(
  5. to: to,
  6. subject: "Test Email",
  7. body: "This is a test email."
  8. )
  9. end
  10. end

app/mailers/user_mailer.rb

87.5% lines covered

50.0% branches covered

16 relevant lines. 14 lines covered and 2 lines missed.
2 total branches, 1 branches covered and 1 branches missed.
    
  1. # app/mailers/user_mailer.rb
  2. 8 class UserMailer < ApplicationMailer
  3. 8 CONFIRM_REGISTRATION_SUBJECT = "Thank you for joining Libregig"
  4. 8 def confirm_registration(user_mail)
  5. 1 @username = user_mail.params["username"]
  6. 1 @url = user_mail.params["url"]
  7. 1 mail(to: user_mail.recipient, subject: CONFIRM_REGISTRATION_SUBJECT)
  8. end
  9. 8 def self.send_confirmation_registration(user)
  10. 1 confirmation_token = user.confirmation_tokens.create!(token: SecureRandom.urlsafe_base64)
  11. 1 user_mail = UserMail.new(
  12. user: user,
  13. recipient: user.email,
  14. subject: CONFIRM_REGISTRATION_SUBJECT,
  15. template: "confirm_registration",
  16. params: {
  17. username: user.username,
  18. url: generate_confirmation_url(confirmation_token)
  19. }
  20. )
  21. 1 then: 1 if user_mail.save
  22. 1 SendMailJob.perform_later(user_mail.id)
  23. else: 0 else
  24. errors = user_mail.errors.full_messages.join(", ")
  25. Rails.logger.error("Failed to create UserMail: #{errors}")
  26. end
  27. 1 user_mail
  28. end
  29. 8 def self.generate_confirmation_url(confirmation_token)
  30. 2 Rails.application.routes.url_helpers.confirm_registration_url(
  31. token: confirmation_token.token,
  32. host: Rails.configuration.server_url
  33. )
  34. end
  35. end

app/models/application_record.rb

100.0% lines covered

100.0% branches covered

2 relevant lines. 2 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. 1 class ApplicationRecord < ActiveRecord::Base
  2. 1 primary_abstract_class
  3. end

app/models/band.rb

100.0% lines covered

50.0% branches covered

20 relevant lines. 20 lines covered and 0 lines missed.
2 total branches, 1 branches covered and 1 branches missed.
    
  1. 1 class Band < ApplicationRecord
  2. 1 include RandomId
  3. 1 has_many :event_bands, dependent: :destroy
  4. 1 has_many :events, through: :event_bands
  5. 1 has_many :band_members, dependent: :restrict_with_error
  6. 1 has_many :members, through: :band_members
  7. 1 has_many :permission, as: :item, dependent: :destroy
  8. 1 validates :description, presence: true
  9. 1 validates :name, presence: true
  10. 1 include Auditable
  11. 1 audit_log_columns :name, :description
  12. 1 attribute :permission_type, :string
  13. 1 scope :permitted_for, ->(user_id) {
  14. 132 select(
  15. "bands.*, #{BandPermissionQuery.with_permission_type_sql(user_id)}"
  16. )
  17. .where(BandPermissionQuery.permission_sql(user_id))
  18. }
  19. 1 def owner
  20. 73 then: 73 else: 0 permission.where(status: :owned).first&.user
  21. end
  22. 1 def url
  23. 1 Rails.application.routes.url_helpers.edit_band_path(self)
  24. end
  25. 1 def editable?
  26. 2 permission_type == "edit"
  27. end
  28. end

app/models/band_member.rb

100.0% lines covered

100.0% branches covered

4 relevant lines. 4 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. 16 class BandMember < ApplicationRecord
  2. 16 include RandomId
  3. 16 belongs_to :member
  4. 16 belongs_to :band
  5. end

app/models/bands_audit.rb

100.0% lines covered

100.0% branches covered

3 relevant lines. 3 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. 3 class BandsAudit < ApplicationRecord
  2. 3 self.table_name = "bands_audit"
  3. 3 belongs_to :band
  4. end

app/models/concerns/auditable.rb

100.0% lines covered

100.0% branches covered

19 relevant lines. 19 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. 1 module Auditable
  2. 1 extend ActiveSupport::Concern
  3. 1 included do
  4. 3 after_update_commit :log_changes
  5. end
  6. 1 def log_changes
  7. 17 changes_to_log.each do |column, values|
  8. 26 audit_class.create!(
  9. item_id => id,
  10. :column_changed => column,
  11. :old_value => values.first,
  12. :user_id => Current.user.id
  13. )
  14. end
  15. end
  16. 1 private
  17. 1 def changes_to_log
  18. 17 saved_changes.slice(*self.class.logged_columns)
  19. end
  20. 1 def audit_class
  21. 26 "#{self.class.name.pluralize}Audit".constantize
  22. end
  23. 1 def item_id
  24. 26 "#{self.class.name.downcase}_id"
  25. end
  26. 1 class_methods do
  27. 1 def audit_log_columns(*columns)
  28. 3 @logged_columns = columns.map(&:to_s)
  29. end
  30. 1 def logged_columns
  31. 17 @logged_columns || []
  32. end
  33. end
  34. end

app/models/concerns/random_id.rb

100.0% lines covered

50.0% branches covered

9 relevant lines. 9 lines covered and 0 lines missed.
2 total branches, 1 branches covered and 1 branches missed.
    
  1. 1 module RandomId
  2. 1 extend ActiveSupport::Concern
  3. 1 included do
  4. 37 before_create :randomise_id
  5. end
  6. 1 private
  7. 1 def randomise_id
  8. 2174 loop do
  9. 2174 self.id = SecureRandom.random_number(4294967295)
  10. 2174 else: 0 then: 2174 break unless self.class.where(id: id).exists?
  11. end
  12. end
  13. end

app/models/confirmation_token.rb

100.0% lines covered

100.0% branches covered

8 relevant lines. 8 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. 7 class ConfirmationToken < ApplicationRecord
  2. 7 belongs_to :user
  3. 7 before_create :set_expires_at, unless: :expires_at?
  4. 7 validates :token, presence: true, uniqueness: true
  5. 11 scope :valid, -> { where("expires_at > ?", Time.current) }
  6. 7 private
  7. 7 def set_expires_at
  8. 5 self.expires_at = 24.hours.from_now
  9. end
  10. end

app/models/current.rb

100.0% lines covered

75.0% branches covered

19 relevant lines. 19 lines covered and 0 lines missed.
4 total branches, 3 branches covered and 1 branches missed.
    
  1. 1 class Current < ActiveSupport::CurrentAttributes
  2. 1 attribute :_user
  3. 1 attribute :_impersonator
  4. 1449 resets { Time.zone = nil }
  5. 1 def user=(user)
  6. 451 self._user = user
  7. 451 update_time_zone
  8. end
  9. 1 def user
  10. 3334 _user
  11. end
  12. 1 def impersonator=(impersonator)
  13. 445 self._impersonator = impersonator
  14. 445 update_time_zone
  15. end
  16. 1 def impersonator
  17. 1 _impersonator
  18. end
  19. 1 def impersonating?
  20. 207 _impersonator.present?
  21. end
  22. 1 private
  23. 1 def update_time_zone
  24. 896 then: 586 else: 310 then: 0 else: 310 Time.zone = _user&.time_zone || _impersonator&.time_zone
  25. end
  26. end

app/models/event.rb

94.29% lines covered

60.0% branches covered

35 relevant lines. 33 lines covered and 2 lines missed.
10 total branches, 6 branches covered and 4 branches missed.
    
  1. 1 class Event < ApplicationRecord
  2. 1 include EventsHelper
  3. 1 include RandomId
  4. 1 has_many :event_bands, dependent: :destroy
  5. 1 has_many :bands, through: :event_bands
  6. 1 has_many :permissions, as: :item, dependent: :destroy
  7. 1 validate :end_date_nil_or_after_start
  8. 1 before_validation :set_defaults
  9. 1 attribute :permission_type, :string
  10. 3 scope :past, -> { where(<<~SQL) }
  11. start_date IS NULL OR
  12. COALESCE(end_date, start_date) <= CURRENT_TIMESTAMP
  13. SQL
  14. 154 scope :future, -> { where(<<~SQL) }
  15. start_date IS NULL OR
  16. COALESCE(end_date, start_date) >= CURRENT_TIMESTAMP
  17. SQL
  18. 1 scope :permitted_for, ->(user_id) {
  19. 269 select(
  20. "events.*, #{EventPermissionQuery.with_permission_type_sql(user_id)}"
  21. )
  22. .where(EventPermissionQuery.permission_sql(user_id))
  23. }
  24. 1 include Auditable
  25. 1 audit_log_columns :name, :description, :start_date, :end_date
  26. 1 def owner
  27. 73 then: 73 else: 0 permissions.where(status: :owned).first&.user
  28. end
  29. 1 def url
  30. 1 Rails.application.routes.url_helpers.edit_event_path(self)
  31. end
  32. 1 def external_url
  33. 1 Rails.application.routes.url_helpers.edit_event_url(self, host: Rails.application.config.server_url)
  34. end
  35. 1 def editable?
  36. 1 permission_type == "edit"
  37. end
  38. 1 def duration
  39. 1 then: 1 else: 0 if start_date.present? && end_date.present?
  40. 1 convert_seconds_to_duration(end_date - start_date)
  41. end
  42. end
  43. 1 private
  44. 1 def set_defaults
  45. 185 then: 183 else: 2 if start_date.present?
  46. 183 self.end_date ||= start_date
  47. 183 then: 0 else: 183 if end_date && end_date < start_date
  48. self.end_date = start_date
  49. end
  50. end
  51. end
  52. 1 def end_date_nil_or_after_start
  53. 185 then: 0 else: 185 if end_date.present? && start_date.present? && end_date < start_date
  54. errors.add(:end_date, "Must be before start")
  55. end
  56. end
  57. end

app/models/event_band.rb

100.0% lines covered

100.0% branches covered

4 relevant lines. 4 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. 1 class EventBand < ApplicationRecord
  2. 1 include RandomId
  3. 1 belongs_to :event
  4. 1 belongs_to :band
  5. end

app/models/events_audit.rb

100.0% lines covered

100.0% branches covered

3 relevant lines. 3 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. 8 class EventsAudit < ApplicationRecord
  2. 8 self.table_name = "events_audit"
  3. 8 belongs_to :event
  4. end

app/models/member.rb

70.0% lines covered

0.0% branches covered

30 relevant lines. 21 lines covered and 9 lines missed.
8 total branches, 0 branches covered and 8 branches missed.
    
  1. 1 class Member < ApplicationRecord
  2. 1 include RandomId
  3. 1 has_many :band_members, dependent: :destroy
  4. 1 has_many :bands, through: :band_members
  5. 1 has_many :member_skills, dependent: :destroy
  6. 1 has_many :skills, through: :member_skills
  7. 253 validates :skills, presence: true, if: -> { skills_list.present? }
  8. 1 has_many :permission, as: :item, dependent: :destroy
  9. 1 include Auditable
  10. 1 audit_log_columns :name, :description
  11. 1 attribute :permission_type, :string
  12. 1 scope :permitted_for, ->(user_id) {
  13. 108 select(
  14. "members.*, #{MemberPermissionQuery.with_permission_type_sql(user_id)}"
  15. )
  16. .where(MemberPermissionQuery.permission_sql(user_id))
  17. }
  18. 1 def owner
  19. then: 0 else: 0 Permission.where(
  20. status: :owned,
  21. item_type: "Member",
  22. item_id: id
  23. ).first&.user
  24. end
  25. 1 def skills_list
  26. 254 skills.pluck(:name).join(", ")
  27. end
  28. 1 def skills_list=(skills_string)
  29. then: 0 else: 0 return if skills_string.blank?
  30. skill_names =
  31. skills_string
  32. .downcase
  33. .split(",")
  34. .map(&:strip)
  35. .compact_blank
  36. .uniq
  37. then: 0 else: 0 return if skill_names.empty?
  38. transaction do
  39. member_skills.destroy_all
  40. skill_names.each do |name|
  41. skill = Skill.find_or_create_by!(name: name)
  42. else: 0 then: 0 member_skills.create!(skill: skill) unless member_skills.exists?(skill: skill)
  43. end
  44. end
  45. end
  46. 1 def editable?
  47. 2 permission_type == "edit"
  48. end
  49. 1 def events
  50. 1 Event.joins(:bands)
  51. .where(bands: {id: bands.pluck(:id)})
  52. .distinct
  53. end
  54. end

app/models/member_skill.rb

100.0% lines covered

100.0% branches covered

4 relevant lines. 4 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. 16 class MemberSkill < ApplicationRecord
  2. 16 belongs_to :member
  3. 16 belongs_to :skill
  4. 16 validates :member_id, uniqueness: {scope: :skill_id}
  5. end

app/models/members_audit.rb

100.0% lines covered

100.0% branches covered

3 relevant lines. 3 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. 3 class MembersAudit < ApplicationRecord
  2. 3 self.table_name = "members_audit"
  3. 3 belongs_to :member
  4. end

app/models/permission.rb

85.11% lines covered

77.78% branches covered

47 relevant lines. 40 lines covered and 7 lines missed.
18 total branches, 14 branches covered and 4 branches missed.
    
  1. 16 class Permission < ApplicationRecord
  2. 16 include RandomId
  3. 16 include PermissionsHelper
  4. 16 belongs_to :user, class_name: "User"
  5. 16 belongs_to :bestowing_user, class_name: "User", optional: true
  6. 16 belongs_to :item, polymorphic: true
  7. 16 scope :accepted, -> { where(status: :accepted) }
  8. 16 scope :accepted_or_owned, -> { where(status: %i[accepted owned]) }
  9. 16 scope :item_type, ->(type) { where(item_type: type) }
  10. 21 scope :for_user, ->(user) { where("bestowing_user_id = :user_id OR user_id = :user_id", user_id: user.id) }
  11. 16 def self.join_models(model)
  12. table_name = model.to_s.pluralize
  13. klass_name = model.to_s.classify
  14. joins("
  15. JOIN #{table_name}
  16. ON #{table_name}.id = permissions.item_id
  17. AND permissions.item_type = '#{klass_name}'
  18. ")
  19. end
  20. 16 scope :bands, -> { join_models(:band) }
  21. 16 scope :events, -> { join_models(:event) }
  22. 16 scope :members, -> { join_models(:member) }
  23. 16 scope :system_permissions, -> { where(bestowing_user: nil) }
  24. 16 validates :item_type, presence: true
  25. 16 validates :permission_type, presence: true, inclusion: {in: %w[view edit]}
  26. 16 validates :status, presence: true, inclusion: {in: %w[owned pending accepted rejected]}
  27. 16 validate :bestowing_user_must_be_organiser_or_nil, :user_must_be_member_or_organiser
  28. 16 validate :valid_item
  29. 16 validate :valid_user
  30. 16 enum :status, {
  31. owned: "owned",
  32. pending: "pending",
  33. accepted: "accepted",
  34. rejected: "rejected"
  35. }
  36. 16 def item_path
  37. 1 case item_type
  38. when: 0 when "Band"
  39. Rails.application.routes.url_helpers.band_path(item)
  40. when: 1 when "Event"
  41. 1 Rails.application.routes.url_helpers.event_path(item)
  42. when: 0 when "Member"
  43. Rails.application.routes.url_helpers.member_path(item)
  44. else: 0 else
  45. Rails.application.routes.url_helpers.permissions_path
  46. end
  47. end
  48. 16 private
  49. 16 def valid_item
  50. 581 then: 513 else: 68 return true if bestowing_user.nil?
  51. 68 item_ids = potential_items(bestowing_user).pluck(1)
  52. 68 item_str = "#{item_type}_#{item_id}"
  53. 68 else: 65 then: 3 unless item_ids.include?(item_str)
  54. 3 errors.add(:item, "is not a valid selection for #{bestowing_user}. Valid options: #{item_ids}")
  55. end
  56. end
  57. 16 def valid_user
  58. # TODO: make this fancier
  59. 581 else: 579 then: 0 unless User.find(user_id)
  60. errors.add(:user, "is not a valid selection")
  61. end
  62. end
  63. 16 def bestowing_user_must_be_organiser_or_nil
  64. 581 else: 580 then: 1 unless bestowing_user.nil? || bestowing_user.organiser?
  65. 1 errors.add(:bestowing_user, "must be an organiser")
  66. end
  67. end
  68. 16 def user_must_be_member_or_organiser
  69. 581 then: 579 else: 2 then: 164 else: 2 else: 578 then: 3 unless user&.organiser? || user&.member?
  70. 3 errors.add(:user, "must be a member or organiser")
  71. end
  72. end
  73. end

app/models/skill.rb

100.0% lines covered

100.0% branches covered

4 relevant lines. 4 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. 1 class Skill < ApplicationRecord
  2. 1 has_many :member_skills, dependent: :destroy
  3. 1 has_many :members, through: :member_skills
  4. 1 validates :name, presence: true
  5. end

app/models/user.rb

100.0% lines covered

100.0% branches covered

33 relevant lines. 33 lines covered and 0 lines missed.
6 total branches, 6 branches covered and 0 branches missed.
    
  1. 1 class User < ApplicationRecord
  2. 1 include RandomId
  3. 1 include PermissionsHelper
  4. 1 enum :user_type, {admin: 0, member: 1, organiser: 2}
  5. 1 has_secure_password
  6. 1 has_many :permissions,
  7. dependent: :destroy,
  8. inverse_of: :user
  9. 1 def members
  10. 101 Member.permitted_for(id)
  11. end
  12. 1 def bands
  13. 126 Band.permitted_for(id)
  14. end
  15. 1 def events
  16. 263 Event.permitted_for(id)
  17. end
  18. 1 has_many :confirmation_tokens, dependent: :destroy
  19. 572 then: 568 else: 3 before_save { email&.downcase! }
  20. 1 before_validation :set_default_time_zone
  21. 1 validates :username,
  22. presence: true,
  23. uniqueness: true
  24. 1 validates :email,
  25. presence: {message: "can't be blank"},
  26. uniqueness: true,
  27. format: {with: URI::MailTo::EMAIL_REGEXP, message: "is invalid", allow_blank: true}
  28. 1 validates :password,
  29. presence: true,
  30. length: {minimum: 6},
  31. if: :password_required?,
  32. allow_blank: true
  33. 1 validates :password_confirmation,
  34. presence: true,
  35. allow_blank: true
  36. 1 validates :time_zone,
  37. inclusion: {
  38. 151 in: ActiveSupport::TimeZone.all.map { |t| t.tzinfo.name },
  39. message: "%{value} is not a valid time zone"
  40. }, allow_blank: true
  41. 1 def confirmed?
  42. 308 confirmed || admin?
  43. end
  44. 1 def to_param
  45. 449 username
  46. end
  47. 1 def owned_links
  48. 2 then: 1 else: 1 return @owned_links if defined?(@owned_links)
  49. @owned_links =
  50. 1 bands.select(:name, :id) +
  51. events.select(:name, :id) +
  52. members.select(:name, :id)
  53. end
  54. 1 private
  55. 1 def password_required?
  56. 1193 new_record? || password.present?
  57. end
  58. 1 def set_default_time_zone
  59. 595 then: 8 else: 587 self.time_zone = "Etc/UTC" if time_zone.blank?
  60. end
  61. end

app/models/user_mail.rb

100.0% lines covered

75.0% branches covered

23 relevant lines. 23 lines covered and 0 lines missed.
4 total branches, 3 branches covered and 1 branches missed.
    
  1. 1 class UserMail < ApplicationRecord
  2. 1 belongs_to :user
  3. 1 enum :state, {pending: 0, sending: 1, sent: 2, failed: 3}
  4. 1 validates :subject, presence: true, length: {maximum: 120}
  5. 1 validates :recipient, presence: true, length: {maximum: 255}
  6. 1 validates :template, presence: true, length: {maximum: 50}
  7. 1 validates :params, presence: true
  8. 1 def html_content
  9. 4 content(:html)
  10. end
  11. 1 def text_content
  12. 4 content(:text)
  13. end
  14. 1 def attempt_send
  15. 3 else: 2 then: 1 return unless pending?
  16. 2 update!(state: :sending)
  17. begin
  18. 2 UserMailer.confirm_registration(self).deliver_now
  19. 1 update!(state: :sent)
  20. 1 Rails.logger.info("Mail sent successfully to #{recipient}.")
  21. rescue => e
  22. 1 then: 0 else: 1 raise e if Rails.env.development?
  23. 1 update!(state: :failed)
  24. 1 Rails.logger.error("Failed to send mail to #{recipient}: #{e}")
  25. end
  26. end
  27. 1 private
  28. 1 def content(format)
  29. 8 ApplicationController.render(
  30. layout: false,
  31. template: "user_mailer/#{template}",
  32. assigns: params,
  33. formats: [format]
  34. )
  35. end
  36. end

app/queries/band_permission_query.rb

100.0% lines covered

100.0% branches covered

17 relevant lines. 17 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. 16 class BandPermissionQuery
  2. 16 class << self
  3. 16 def permission_sql(user_id)
  4. 132 <<-SQL
  5. EXISTS (
  6. SELECT 1 FROM permissions
  7. WHERE permissions.user_id = #{user_id}
  8. AND permissions.status IN ('owned', 'accepted')
  9. AND (
  10. /* Check three ways you might directly have permission */
  11. (#{direct_permission_sql})
  12. OR
  13. (#{through_member_sql})
  14. OR
  15. (#{through_event_sql})
  16. )
  17. )
  18. /* Or check indirect permissions through event relationships */
  19. OR bands.id IN (
  20. /* Can see bands at events you have have permission for other bands
  21. at */
  22. #{shares_event_with_permitted_band_sql(user_id)}
  23. )
  24. OR bands.id IN (
  25. /* Can see bands at events you have have permission for members in
  26. bands at */
  27. #{shares_event_with_band_with_permitted_member_sql(user_id)}
  28. )
  29. SQL
  30. end
  31. 16 def with_permission_type_sql(user_id)
  32. 132 <<~SQL
  33. CASE
  34. WHEN EXISTS (
  35. SELECT 1 FROM permissions
  36. WHERE permissions.user_id = #{user_id}
  37. AND permissions.status IN ('owned', 'accepted')
  38. AND permissions.item_type = 'Band'
  39. AND permissions.item_id = bands.id
  40. ) THEN (
  41. SELECT permission_type FROM permissions
  42. WHERE permissions.user_id = #{user_id}
  43. AND permissions.status IN ('owned', 'accepted')
  44. AND permissions.item_type = 'Band'
  45. AND permissions.item_id = bands.id
  46. LIMIT 1
  47. )
  48. ELSE 'view'
  49. END as permission_type
  50. SQL
  51. end
  52. 16 private
  53. 16 def direct_permission_sql
  54. 132 <<-SQL
  55. /* Simplest case: you have direct permission to the band */
  56. permissions.item_type = 'Band'
  57. AND permissions.item_id = bands.id
  58. SQL
  59. end
  60. 16 def through_member_sql
  61. 132 <<-SQL
  62. /* You have permission to a member who is in this band */
  63. permissions.item_type = 'Member'
  64. AND permissions.item_id IN (
  65. /* Find all members in this band */
  66. SELECT member_id
  67. FROM band_members
  68. WHERE band_id = bands.id
  69. )
  70. SQL
  71. end
  72. 16 def through_event_sql
  73. 132 <<-SQL
  74. /* You have permission to an event this band is playing at */
  75. permissions.item_type = 'Event'
  76. AND permissions.item_id IN (
  77. /* Find all events this band is playing at */
  78. SELECT event_id
  79. FROM event_bands
  80. WHERE band_id = bands.id
  81. )
  82. SQL
  83. end
  84. 16 def shares_event_with_permitted_band_sql(user_id)
  85. 132 <<~SQL
  86. /* Find bands that are playing at the same events as bands you can access */
  87. SELECT band.id
  88. FROM bands AS band
  89. /* First, find events this band is playing at */
  90. JOIN event_bands AS event_band
  91. ON event_band.band_id = band.id
  92. /* Then find other bands playing at those same events */
  93. JOIN event_bands AS other_event_band
  94. ON other_event_band.event_id = event_band.event_id
  95. /* Check if you have permission to any of those other bands */
  96. JOIN permissions AS p
  97. ON p.item_type = 'Band'
  98. AND p.item_id = other_event_band.band_id
  99. AND p.user_id = #{user_id}
  100. AND p.status IN ('owned', 'accepted')
  101. SQL
  102. end
  103. 16 def shares_event_with_band_with_permitted_member_sql(user_id)
  104. 132 <<-SQL
  105. /* Find bands that are playing at events where there's another band
  106. that has a member you have permission to */
  107. SELECT band.id
  108. FROM bands AS band
  109. /* First, find events this band is playing at */
  110. JOIN event_bands AS event_band
  111. ON event_band.band_id = band.id
  112. /* Then find other bands playing at those same events */
  113. JOIN event_bands AS other_event_band
  114. ON other_event_band.event_id = event_band.event_id
  115. /* Find members in those other bands */
  116. JOIN band_members AS bm
  117. ON bm.band_id = other_event_band.band_id
  118. /* Check if you have permission to any of those members */
  119. JOIN permissions AS p
  120. ON p.item_type = 'Member'
  121. AND p.item_id = bm.member_id
  122. AND p.user_id = #{user_id}
  123. AND p.status IN ('owned', 'accepted')
  124. SQL
  125. end
  126. end
  127. end

app/queries/event_permission_query.rb

100.0% lines covered

100.0% branches covered

13 relevant lines. 13 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. 16 class EventPermissionQuery
  2. 16 class << self
  3. 16 def permission_sql(user_id)
  4. 269 <<-SQL
  5. EXISTS (
  6. SELECT 1 FROM permissions
  7. WHERE permissions.user_id = #{user_id}
  8. AND permissions.status IN ('owned', 'accepted')
  9. AND (
  10. /* Check three ways you might have permission */
  11. (#{direct_permission_sql})
  12. OR
  13. (#{through_band_sql})
  14. OR
  15. (#{through_member_sql})
  16. )
  17. )
  18. SQL
  19. end
  20. 16 def with_permission_type_sql(user_id)
  21. 269 <<~SQL
  22. CASE
  23. WHEN EXISTS (
  24. SELECT 1 FROM permissions
  25. WHERE permissions.user_id = #{user_id}
  26. AND permissions.status IN ('owned', 'accepted')
  27. AND permissions.item_type = 'Event'
  28. AND permissions.item_id = events.id
  29. ) THEN (
  30. SELECT permission_type FROM permissions
  31. WHERE permissions.user_id = #{user_id}
  32. AND permissions.status IN ('owned', 'accepted')
  33. AND permissions.item_type = 'Event'
  34. AND permissions.item_id = events.id
  35. LIMIT 1
  36. )
  37. ELSE 'view'
  38. END as permission_type
  39. SQL
  40. end
  41. 16 private
  42. 16 def direct_permission_sql
  43. 269 <<-SQL
  44. /* Simplest case: you have direct permission to the event */
  45. permissions.item_type = 'Event'
  46. AND permissions.item_id = events.id
  47. SQL
  48. end
  49. 16 def through_band_sql
  50. 269 <<-SQL
  51. /* You have permission to a band that's playing at this event */
  52. permissions.item_type = 'Band'
  53. AND permissions.item_id IN (
  54. /* Find all bands playing at this event */
  55. SELECT band_id
  56. FROM event_bands
  57. WHERE event_id = events.id
  58. )
  59. SQL
  60. end
  61. 16 def through_member_sql
  62. 269 <<-SQL
  63. /* You have permission to a member who's playing at this event */
  64. permissions.item_type = 'Member'
  65. AND permissions.item_id IN (
  66. /* Find all members playing at this event */
  67. SELECT members.id
  68. FROM members
  69. /* Connect members to their bands */
  70. JOIN band_members
  71. ON band_members.member_id = members.id
  72. /* Connect bands to the events they're playing */
  73. JOIN event_bands
  74. ON event_bands.band_id = band_members.band_id
  75. WHERE event_bands.event_id = events.id
  76. )
  77. SQL
  78. end
  79. end
  80. end

app/queries/member_permission_query.rb

100.0% lines covered

100.0% branches covered

18 relevant lines. 18 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. 16 class MemberPermissionQuery
  2. 16 class << self
  3. 16 def permission_sql(user_id)
  4. 108 <<~SQL
  5. EXISTS (
  6. SELECT 1 FROM permissions
  7. WHERE (
  8. permissions.user_id = #{user_id}
  9. AND permissions.status IN ('owned', 'accepted')
  10. AND (
  11. /* Check three ways you might directly have permission */
  12. (#{direct_permission_sql})
  13. OR
  14. (#{through_band_sql})
  15. OR
  16. (#{through_event_sql})
  17. )
  18. )
  19. /* Or check indirect permissions through various relationships */
  20. OR members.id IN (
  21. /* Can see members who are in bands with members you can access */
  22. #{shares_band_with_permitted_member_sql(user_id)}
  23. )
  24. OR members.id IN (
  25. /* Can see members who are playing at events with members you can access */
  26. #{shares_event_with_permitted_member_sql(user_id)}
  27. )
  28. OR members.id IN (
  29. /* Can see members who are in bands playing at events with bands you can access */
  30. #{shares_event_with_permitted_band_sql(user_id)}
  31. )
  32. )
  33. SQL
  34. end
  35. 16 def with_permission_type_sql(user_id)
  36. 108 <<~SQL
  37. CASE
  38. WHEN EXISTS (
  39. SELECT 1 FROM permissions
  40. WHERE permissions.user_id = #{user_id}
  41. AND permissions.status IN ('owned', 'accepted')
  42. AND permissions.item_type = 'Member'
  43. AND permissions.item_id = members.id
  44. ) THEN (
  45. SELECT permission_type FROM permissions
  46. WHERE permissions.user_id = #{user_id}
  47. AND permissions.status IN ('owned', 'accepted')
  48. AND permissions.item_type = 'Member'
  49. AND permissions.item_id = members.id
  50. LIMIT 1
  51. )
  52. ELSE 'view'
  53. END as permission_type
  54. SQL
  55. end
  56. 16 def direct_permission_sql
  57. 108 <<~SQL
  58. /* Simplest case: you have direct permission to see this member */
  59. permissions.item_type = 'Member'
  60. AND permissions.item_id = members.id
  61. SQL
  62. end
  63. 16 def through_band_sql
  64. 108 <<~SQL
  65. /* You have permission to a band this member plays in */
  66. permissions.item_type = 'Band'
  67. AND permissions.item_id IN (
  68. /* Find all bands this member is in */
  69. SELECT band_id FROM band_members
  70. WHERE member_id = members.id
  71. )
  72. SQL
  73. end
  74. 16 def through_event_sql
  75. 108 <<~SQL
  76. /* You have permission to an event where this member is playing */
  77. permissions.item_type = 'Event'
  78. AND permissions.item_id IN (
  79. /* Find all events where this member is playing */
  80. SELECT events.id FROM events
  81. /* Connect events to bands playing at them */
  82. JOIN event_bands ON event_bands.event_id = events.id
  83. /* Connect bands to their members */
  84. JOIN band_members ON band_members.band_id = event_bands.band_id
  85. WHERE band_members.member_id = members.id
  86. )
  87. SQL
  88. end
  89. 16 def shares_band_with_permitted_member_sql(user_id)
  90. 113 <<~SQL
  91. /* Find members who are in the same bands as members you can access */
  92. SELECT DISTINCT other_members.member_id AS id
  93. FROM band_members AS other_members
  94. /* Find bands where there's at least one member you have permission for */
  95. WHERE other_members.band_id IN (
  96. SELECT band_members.band_id
  97. FROM band_members
  98. JOIN permissions AS p
  99. ON p.item_type = 'Member'
  100. AND p.item_id = band_members.member_id
  101. WHERE p.user_id = #{user_id}
  102. AND p.status IN ('owned', 'accepted')
  103. )
  104. SQL
  105. end
  106. 16 def shares_event_with_permitted_member_sql(user_id)
  107. 108 <<~SQL
  108. /* Find members who are playing at the same events as members you can access */
  109. SELECT band_member.member_id AS id
  110. /* Start with a member in a band */
  111. FROM band_members AS band_member
  112. /* Find events where their band is playing */
  113. JOIN event_bands AS event_band
  114. ON event_band.band_id = band_member.band_id
  115. /* Find other bands at those same events */
  116. JOIN event_bands AS other_event_band
  117. ON other_event_band.event_id = event_band.event_id
  118. /* Find members in those other bands */
  119. JOIN band_members AS other_band_member
  120. ON other_band_member.band_id = other_event_band.band_id
  121. /* Check that you have permission to one of those other members */
  122. JOIN permissions AS p
  123. ON p.item_type = 'Member'
  124. AND p.item_id = other_band_member.member_id
  125. AND p.user_id = #{user_id}
  126. AND p.status IN ('owned', 'accepted')
  127. SQL
  128. end
  129. 16 def shares_event_with_permitted_band_sql(user_id)
  130. 108 <<~SQL
  131. /* Find members who are playing at events where you have permission to another band */
  132. SELECT band_member.member_id AS id
  133. /* Start with a member in a band */
  134. FROM band_members AS band_member
  135. /* Find events where their band is playing */
  136. JOIN event_bands AS event_band
  137. ON event_band.band_id = band_member.band_id
  138. /* Find other bands at those same events */
  139. JOIN event_bands AS other_event_band
  140. ON other_event_band.event_id = event_band.event_id
  141. /* Check that you have permission to one of those other bands */
  142. JOIN permissions AS p
  143. ON p.item_type = 'Band'
  144. AND p.item_id = other_event_band.band_id
  145. AND p.user_id = #{user_id}
  146. AND p.status IN ('owned', 'accepted')
  147. SQL
  148. end
  149. end
  150. end