munificent-admin 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENCE +2 -0
- data/README.md +83 -0
- data/Rakefile +12 -0
- data/app/abilities/munificent/admin/ability.rb +51 -0
- data/app/abilities/munificent/admin/application_ability.rb +37 -0
- data/app/assets/config/munificent_admin_manifest.js +4 -0
- data/app/assets/javascripts/munificent_admin/application.js +6 -0
- data/app/assets/stylesheets/munificent/admin/application.scss +2 -0
- data/app/assets/stylesheets/munificent/admin/layout.scss +64 -0
- data/app/assets/stylesheets/munificent/admin/records.scss +7 -0
- data/app/controllers/munificent/admin/application_controller.rb +61 -0
- data/app/controllers/munificent/admin/bundle_tiers_controller.rb +12 -0
- data/app/controllers/munificent/admin/bundles_controller.rb +86 -0
- data/app/controllers/munificent/admin/charities_controller.rb +60 -0
- data/app/controllers/munificent/admin/dashboard_controller.rb +9 -0
- data/app/controllers/munificent/admin/donations_controller.rb +9 -0
- data/app/controllers/munificent/admin/donator_bundles_controller.rb +9 -0
- data/app/controllers/munificent/admin/donators_controller.rb +9 -0
- data/app/controllers/munificent/admin/fundraisers_controller.rb +68 -0
- data/app/controllers/munificent/admin/games_controller.rb +65 -0
- data/app/controllers/munificent/admin/otp_controller.rb +57 -0
- data/app/controllers/munificent/admin/user_sessions_controller.rb +44 -0
- data/app/controllers/munificent/admin/users_controller.rb +77 -0
- data/app/form_builders/munificent/admin/form_builder.rb +112 -0
- data/app/helpers/munificent/admin/application_helper.rb +6 -0
- data/app/helpers/munificent/admin/form_helper.rb +9 -0
- data/app/helpers/munificent/admin/layout_helper.rb +11 -0
- data/app/helpers/munificent/admin/state_machine_helper.rb +49 -0
- data/app/mailers/munificent/admin/application_mailer.rb +8 -0
- data/app/mailers/munificent/admin/panic_mailer.rb +19 -0
- data/app/models/concerns/authenticable.rb +9 -0
- data/app/models/munificent/admin/application_record.rb +7 -0
- data/app/models/munificent/admin/user.rb +54 -0
- data/app/models/munificent/admin/user_session.rb +19 -0
- data/app/presenters/munificent/admin/application_presenter.rb +29 -0
- data/app/presenters/munificent/admin/user_presenter.rb +24 -0
- data/app/presenters/munificent/bundle_presenter.rb +27 -0
- data/app/presenters/munificent/bundle_tier_presenter.rb +20 -0
- data/app/presenters/munificent/charity_presenter.rb +14 -0
- data/app/presenters/munificent/donation_presenter.rb +39 -0
- data/app/presenters/munificent/donator_bundle_presenter.rb +12 -0
- data/app/presenters/munificent/donator_presenter.rb +12 -0
- data/app/presenters/munificent/fundraiser_presenter.rb +41 -0
- data/app/presenters/munificent/game_presenter.rb +20 -0
- data/app/views/layouts/munificent/admin/application.html.erb +87 -0
- data/app/views/munificent/admin/bundles/_form.html.erb +15 -0
- data/app/views/munificent/admin/bundles/_tier_form.html.erb +13 -0
- data/app/views/munificent/admin/bundles/edit.html.erb +12 -0
- data/app/views/munificent/admin/bundles/index.html.erb +15 -0
- data/app/views/munificent/admin/bundles/new.html.erb +9 -0
- data/app/views/munificent/admin/bundles/show.html.erb +44 -0
- data/app/views/munificent/admin/charities/_form.html.erb +11 -0
- data/app/views/munificent/admin/charities/edit.html.erb +3 -0
- data/app/views/munificent/admin/charities/index.html.erb +11 -0
- data/app/views/munificent/admin/charities/new.html.erb +3 -0
- data/app/views/munificent/admin/charities/show.html.erb +23 -0
- data/app/views/munificent/admin/common/_record.html.erb +18 -0
- data/app/views/munificent/admin/common/_records.html.erb +38 -0
- data/app/views/munificent/admin/dashboard/index.html.erb +3 -0
- data/app/views/munificent/admin/donations/index.html.erb +15 -0
- data/app/views/munificent/admin/donator_bundles/index.html.erb +10 -0
- data/app/views/munificent/admin/donators/index.html.erb +11 -0
- data/app/views/munificent/admin/fundraisers/_form.html.erb +16 -0
- data/app/views/munificent/admin/fundraisers/edit.html.erb +3 -0
- data/app/views/munificent/admin/fundraisers/index.html.erb +17 -0
- data/app/views/munificent/admin/fundraisers/new.html.erb +3 -0
- data/app/views/munificent/admin/fundraisers/show.html.erb +41 -0
- data/app/views/munificent/admin/games/_form.html.erb +16 -0
- data/app/views/munificent/admin/games/csv_upload.html.erb +10 -0
- data/app/views/munificent/admin/games/edit.html.erb +3 -0
- data/app/views/munificent/admin/games/index.html.erb +15 -0
- data/app/views/munificent/admin/games/new.html.erb +3 -0
- data/app/views/munificent/admin/games/show.html.erb +25 -0
- data/app/views/munificent/admin/otp/_input_form.html.erb +6 -0
- data/app/views/munificent/admin/otp/input.html.erb +3 -0
- data/app/views/munificent/admin/otp/setup.html.erb +7 -0
- data/app/views/munificent/admin/panic_mailer/missing_key.html.erb +6 -0
- data/app/views/munificent/admin/panic_mailer/missing_key.text.erb +4 -0
- data/app/views/munificent/admin/user_sessions/new.html.erb +9 -0
- data/app/views/munificent/admin/users/_form.html.erb +29 -0
- data/app/views/munificent/admin/users/edit.html.erb +3 -0
- data/app/views/munificent/admin/users/index.html.erb +15 -0
- data/app/views/munificent/admin/users/new.html.erb +3 -0
- data/app/views/munificent/admin/users/show.html.erb +20 -0
- data/config/cucumber.yml +9 -0
- data/config/importmap.rb +4 -0
- data/config/initializers/inflections.rb +24 -0
- data/config/locales/en.yml +4 -0
- data/config/routes.rb +40 -0
- data/db/migrate/20220524130247_create_munificent_admin_user.rb +50 -0
- data/lib/munificent/admin/engine.rb +32 -0
- data/lib/munificent/admin/seeds.rb +19 -0
- data/lib/munificent/admin/version.rb +5 -0
- data/lib/munificent/admin.rb +7 -0
- data/lib/tasks/munificent/admin_tasks.rake +4 -0
- data/lib/tasks/munificent/cucumber.rake +75 -0
- data/lib/tasks/munificent/default.rake +32 -0
- metadata +354 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
require "rotp"
|
|
2
|
+
require "rqrcode"
|
|
3
|
+
|
|
4
|
+
module Munificent
|
|
5
|
+
module Admin
|
|
6
|
+
class OTPController < ApplicationController
|
|
7
|
+
skip_authorization_check
|
|
8
|
+
skip_before_action :enforce_2sv
|
|
9
|
+
|
|
10
|
+
before_action :require_setup, only: %i[input]
|
|
11
|
+
before_action :prevent_double_setup, only: %i[setup]
|
|
12
|
+
before_action :require_otp_issuer
|
|
13
|
+
|
|
14
|
+
def input; end
|
|
15
|
+
|
|
16
|
+
def setup
|
|
17
|
+
session[:otp_secret] = ROTP::Base32.random
|
|
18
|
+
totp = ROTP::TOTP.new(session[:otp_secret], issuer: ENV.fetch("OTP_ISSUER", nil))
|
|
19
|
+
@otp_url = totp.provisioning_uri(current_user.email_address)
|
|
20
|
+
@qr_code = RQRCode::QRCode.new(@otp_url).as_svg(standalone: false, module_size: 5)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def verify
|
|
24
|
+
otp_secret = current_user.otp_secret || session[:otp_secret]
|
|
25
|
+
raise "Missing OTP secret" if otp_secret.blank?
|
|
26
|
+
|
|
27
|
+
totp = ROTP::TOTP.new(otp_secret, issuer: ENV.fetch("OTP_ISSUER", nil), after: current_user.last_otp_at)
|
|
28
|
+
|
|
29
|
+
if totp.verify(params[:otp_code], drift_behind: 3)
|
|
30
|
+
current_user.otp_secret ||= session[:otp_secret]
|
|
31
|
+
current_user.update(last_otp_at: (session[:last_otp_at] = Time.zone.now))
|
|
32
|
+
session[:otp_secret] = nil
|
|
33
|
+
|
|
34
|
+
redirect_to root_path
|
|
35
|
+
elsif current_user.has_2sv?
|
|
36
|
+
redirect_to otp_input_path
|
|
37
|
+
else
|
|
38
|
+
redirect_to otp_setup_path
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
def require_setup
|
|
45
|
+
redirect_to otp_setup_path unless current_user&.has_2sv?
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def prevent_double_setup
|
|
49
|
+
redirect_to otp_input_path if current_user&.has_2sv?
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def require_otp_issuer
|
|
53
|
+
raise "Missing OTP issuer" if ENV["OTP_ISSUER"].blank?
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
module Munificent
|
|
2
|
+
module Admin
|
|
3
|
+
class UserSessionsController < ApplicationController
|
|
4
|
+
skip_authorization_check
|
|
5
|
+
|
|
6
|
+
skip_before_action :require_login, only: %i[new create]
|
|
7
|
+
skip_before_action :enforce_2sv
|
|
8
|
+
|
|
9
|
+
def new
|
|
10
|
+
@user_session = UserSession.new
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def create
|
|
14
|
+
@user_session = UserSession.new(user_session_params.to_h)
|
|
15
|
+
|
|
16
|
+
if @user_session.save
|
|
17
|
+
redirect_to root_path
|
|
18
|
+
else
|
|
19
|
+
flash[:alert] = @user_session.errors.full_messages.join(", ")
|
|
20
|
+
redirect_to new_user_session_path
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def destroy
|
|
25
|
+
current_user_session.destroy
|
|
26
|
+
|
|
27
|
+
redirect_to new_user_session_path
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def user_session_params
|
|
33
|
+
params.require(:user_session).permit(
|
|
34
|
+
*%i[
|
|
35
|
+
email_address
|
|
36
|
+
password
|
|
37
|
+
password_confirmation
|
|
38
|
+
remember_me
|
|
39
|
+
],
|
|
40
|
+
)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
module Munificent
|
|
2
|
+
module Admin
|
|
3
|
+
class UsersController < ApplicationController
|
|
4
|
+
load_and_authorize_resource class: "Munificent::Admin::User"
|
|
5
|
+
|
|
6
|
+
def index
|
|
7
|
+
authorize!(:read, :user_accounts)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def show; end
|
|
11
|
+
def new; end
|
|
12
|
+
def edit; end
|
|
13
|
+
|
|
14
|
+
def create
|
|
15
|
+
if @user.save
|
|
16
|
+
flash[:notice] = "User created"
|
|
17
|
+
redirect_to edit_user_path(@user)
|
|
18
|
+
else
|
|
19
|
+
flash[:alert] = @user.errors.full_messages.join(", ")
|
|
20
|
+
redirect_to new_user_path
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def update
|
|
25
|
+
model = :user
|
|
26
|
+
if params[model][:password].blank?
|
|
27
|
+
%w[password password_confirmation].each { |p| params[model].delete(p) }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
@user.assign_attributes(user_params)
|
|
31
|
+
|
|
32
|
+
if @user.save
|
|
33
|
+
flash[:notice] = "User saved"
|
|
34
|
+
else
|
|
35
|
+
flash[:alert] = @user.errors.full_messages.join(", ")
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
redirect_to edit_user_path(@user)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def destroy
|
|
42
|
+
@user.destroy
|
|
43
|
+
redirect_to users_path
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
def user_params
|
|
49
|
+
params.require(:user).permit(
|
|
50
|
+
*%i[
|
|
51
|
+
active
|
|
52
|
+
approved
|
|
53
|
+
confirmed
|
|
54
|
+
data_entry
|
|
55
|
+
email_address
|
|
56
|
+
full_access
|
|
57
|
+
manages_users
|
|
58
|
+
name
|
|
59
|
+
password
|
|
60
|
+
password_confirmation
|
|
61
|
+
support
|
|
62
|
+
],
|
|
63
|
+
)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def resource
|
|
67
|
+
@user
|
|
68
|
+
end
|
|
69
|
+
helper_method :resource
|
|
70
|
+
|
|
71
|
+
def presenter
|
|
72
|
+
@presenter ||= Munificent::Admin::ApplicationPresenter.present(@user)
|
|
73
|
+
end
|
|
74
|
+
helper_method :presenter
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
module Munificent
|
|
2
|
+
module Admin
|
|
3
|
+
class FormBuilder < ActionView::Helpers::FormBuilder
|
|
4
|
+
include ActionView::Helpers::TagHelper
|
|
5
|
+
include ActionView::Context
|
|
6
|
+
|
|
7
|
+
TEXT_FIELDS = %i[
|
|
8
|
+
email_field
|
|
9
|
+
number_field
|
|
10
|
+
password_field
|
|
11
|
+
text_area
|
|
12
|
+
text_field
|
|
13
|
+
].freeze
|
|
14
|
+
|
|
15
|
+
UNFLOATABLE_TYPES = %i[
|
|
16
|
+
select
|
|
17
|
+
].freeze
|
|
18
|
+
|
|
19
|
+
def field(type, name, label: name.to_s.humanize, **opts)
|
|
20
|
+
field_tag = case type
|
|
21
|
+
when *TEXT_FIELDS
|
|
22
|
+
public_send(type, name, class: "form-control", placeholder: (label unless table_mode?))
|
|
23
|
+
when :select
|
|
24
|
+
select(name, opts[:source], select_field_opts(opts), select_html_opts(opts))
|
|
25
|
+
else
|
|
26
|
+
send(type, name, class: "form-control")
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
wrap(
|
|
30
|
+
label(name, label, class: "form-label"),
|
|
31
|
+
field_tag,
|
|
32
|
+
field_name: name,
|
|
33
|
+
float: UNFLOATABLE_TYPES.exclude?(type),
|
|
34
|
+
)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def check_box(name, label: name.to_s.humanize, **opts)
|
|
38
|
+
check_block = tag.div(class: "form-check") {
|
|
39
|
+
super(name, class: "form-check-input", **check_box_opts(opts)) +
|
|
40
|
+
label(name, label, class: "form-check-label")
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
wrap(check_block, field_name: name)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def table(css_class: nil, &block)
|
|
47
|
+
@table_mode = true
|
|
48
|
+
contents = @template.capture(&block)
|
|
49
|
+
@table_mode = false
|
|
50
|
+
|
|
51
|
+
css_class = (%w[table table-bordered] + Array(css_class)).compact.join(" ")
|
|
52
|
+
tag.table(contents, class: css_class)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def money(name, default_currency: nil)
|
|
56
|
+
field(:select, "#{name}_currency",
|
|
57
|
+
label: "Currency",
|
|
58
|
+
source: Munificent::Currency.present_all,
|
|
59
|
+
selected: (object.public_send("#{name}_currency").presence || default_currency),
|
|
60
|
+
) + field(:text_field, "human_#{name}", label: name.to_s.humanize)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
private
|
|
64
|
+
|
|
65
|
+
def wrap(*args, field_name: nil, float: true)
|
|
66
|
+
row_class = ["field-#{field_name}"]
|
|
67
|
+
|
|
68
|
+
args.reverse! if float
|
|
69
|
+
|
|
70
|
+
if table_mode?
|
|
71
|
+
tag.tr(class: row_class.compact.join(" ")) do
|
|
72
|
+
args.compact.map { |arg| tag.td(arg) }.reverse.reduce(:+)
|
|
73
|
+
end
|
|
74
|
+
else
|
|
75
|
+
row_class << "mb-3"
|
|
76
|
+
row_class += %w[form-floating mb-3] if float
|
|
77
|
+
|
|
78
|
+
tag.div(class: row_class.compact.join(" ")) do
|
|
79
|
+
args.compact.reduce(:+)
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def table_mode?
|
|
85
|
+
!!@table_mode
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def select_field_opts(opts)
|
|
89
|
+
{ include_blank: opts[:optional] }.tap do |field_opts|
|
|
90
|
+
field_opts[:selected] = opts[:selected] if opts[:selected]
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def select_html_opts(opts)
|
|
95
|
+
{ class: "form-select" }.tap do |html_opts|
|
|
96
|
+
if opts[:multiple]
|
|
97
|
+
html_opts.merge!(
|
|
98
|
+
multiple: true,
|
|
99
|
+
aria: { label: "multiple select" },
|
|
100
|
+
)
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def check_box_opts(opts)
|
|
106
|
+
{}.tap do |field_opts|
|
|
107
|
+
field_opts[:checked] = opts[:checked] unless opts[:checked].nil?
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
module Munificent
|
|
2
|
+
module Admin
|
|
3
|
+
module StateMachineHelper
|
|
4
|
+
module_function
|
|
5
|
+
|
|
6
|
+
def aasm_buttons(resource, tag_name: "li")
|
|
7
|
+
resource.aasm.permitted_transitions.each do |transition|
|
|
8
|
+
event = transition.fetch(:event).to_s
|
|
9
|
+
|
|
10
|
+
content_for(:aasm_buttons) do
|
|
11
|
+
tag.public_send(tag_name) do
|
|
12
|
+
button_to(
|
|
13
|
+
event.humanize,
|
|
14
|
+
send("#{event}_#{resource.class.name.split('::').last.underscore}_path", resource),
|
|
15
|
+
method: :post,
|
|
16
|
+
data: { confirm: "Are you sure?" },
|
|
17
|
+
)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
content_for(:aasm_buttons).presence
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def define_routes(router, resource_class)
|
|
26
|
+
router.send(:member) do
|
|
27
|
+
resource_class.aasm.events.each do |event|
|
|
28
|
+
router.send(:post, event.to_s)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def define_actions(controller, resource_class)
|
|
34
|
+
singular_name = resource_class.name.split("::").last.underscore
|
|
35
|
+
|
|
36
|
+
resource_class.aasm.events.each do |event|
|
|
37
|
+
controller.define_method(event.name) do
|
|
38
|
+
resource.public_send("#{event.name}!")
|
|
39
|
+
redirect_to(send("#{singular_name}_path", resource),
|
|
40
|
+
notice: "#{event.name.to_s.humanize} was successful",
|
|
41
|
+
)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
nil
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module Munificent
|
|
2
|
+
module Admin
|
|
3
|
+
class PanicMailer < ApplicationMailer
|
|
4
|
+
# Subject can be set in your I18n file at config/locales/en.yml
|
|
5
|
+
# with the following lookup:
|
|
6
|
+
#
|
|
7
|
+
# en.panic_mailer.missing_key.subject
|
|
8
|
+
#
|
|
9
|
+
def missing_key(donator, game)
|
|
10
|
+
return if User.none?
|
|
11
|
+
|
|
12
|
+
@donator = donator
|
|
13
|
+
@game = game
|
|
14
|
+
|
|
15
|
+
mail to: User.pluck(:email_address)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
module Munificent
|
|
2
|
+
module Admin
|
|
3
|
+
class User < Munificent::ApplicationRecord
|
|
4
|
+
include Authenticable
|
|
5
|
+
|
|
6
|
+
attr_accessor :password_confirmation
|
|
7
|
+
attr_writer :require_password
|
|
8
|
+
|
|
9
|
+
def require_password?
|
|
10
|
+
!!@require_password
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
validates :password,
|
|
14
|
+
presence: true,
|
|
15
|
+
confirmation: true,
|
|
16
|
+
length: { minimum: 10 },
|
|
17
|
+
if: :require_password?
|
|
18
|
+
validates :email_address, presence: true
|
|
19
|
+
|
|
20
|
+
def has_2sv?
|
|
21
|
+
otp_secret.present?
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def permissions
|
|
25
|
+
if full_access?
|
|
26
|
+
["full access"]
|
|
27
|
+
else
|
|
28
|
+
[].tap do |perms|
|
|
29
|
+
perms << "data entry" if data_entry?
|
|
30
|
+
perms << "manages users" if manages_users?
|
|
31
|
+
perms << "support" if support?
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def states
|
|
37
|
+
[].tap do |states|
|
|
38
|
+
states << "active" if active?
|
|
39
|
+
states << "approved" if approved?
|
|
40
|
+
states << "confirmed" if confirmed?
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# We may use these later, but for now default them to `true`.
|
|
45
|
+
def active? = true
|
|
46
|
+
def approved? = true
|
|
47
|
+
def confirmed? = true
|
|
48
|
+
|
|
49
|
+
def to_s
|
|
50
|
+
name.presence || email_address
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module Munificent
|
|
2
|
+
module Admin
|
|
3
|
+
class UserSession < Authlogic::Session::Base
|
|
4
|
+
login_field :email_address
|
|
5
|
+
record_selection_method :find_by_email_address
|
|
6
|
+
|
|
7
|
+
# This is a fairly restrictive configuration
|
|
8
|
+
# because this is an admin application.
|
|
9
|
+
consecutive_failed_logins_limit 5
|
|
10
|
+
encrypt_cookie true
|
|
11
|
+
generalize_credentials_error_messages true
|
|
12
|
+
httponly true
|
|
13
|
+
logout_on_timeout true
|
|
14
|
+
remember_me_for 20.hours
|
|
15
|
+
same_site "Strict"
|
|
16
|
+
single_access_allowed_request_types []
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
module Munificent
|
|
2
|
+
module Admin
|
|
3
|
+
class ApplicationPresenter < ActionView::Base
|
|
4
|
+
def self.delegate(array, to: :record)
|
|
5
|
+
super(*array, to:)
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def self.present(record)
|
|
9
|
+
presenter_class_for(record).new(record)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def self.presenter_class_for(record)
|
|
13
|
+
"#{record.class.name}Presenter".constantize
|
|
14
|
+
rescue NameError
|
|
15
|
+
raise ArgumentError, "No presenter available for record type `#{record.class.name}`"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
attr_reader :record
|
|
19
|
+
|
|
20
|
+
def initialize(record) # rubocop:disable Lint/MissingSuper
|
|
21
|
+
@record = record
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def name
|
|
25
|
+
record
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
module Munificent
|
|
2
|
+
module Admin
|
|
3
|
+
class UserPresenter < ApplicationPresenter
|
|
4
|
+
delegate(
|
|
5
|
+
%i[
|
|
6
|
+
id
|
|
7
|
+
email_address
|
|
8
|
+
current_login_at
|
|
9
|
+
login_count
|
|
10
|
+
permissions
|
|
11
|
+
states
|
|
12
|
+
],
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
def permissions
|
|
16
|
+
record.permissions.to_sentence.capitalize
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def states
|
|
20
|
+
record.states.to_sentence.capitalize
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
module Munificent
|
|
2
|
+
class BundlePresenter < Admin::ApplicationPresenter
|
|
3
|
+
delegate(
|
|
4
|
+
%i[
|
|
5
|
+
id
|
|
6
|
+
fundraiser
|
|
7
|
+
],
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
delegate(
|
|
11
|
+
%i[
|
|
12
|
+
ends_at
|
|
13
|
+
starts_at
|
|
14
|
+
], to: :"record.highest_tier",
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
def price
|
|
18
|
+
record.highest_tier&.human_price(symbol: true)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def state
|
|
22
|
+
tag.span(class: "badge text-bg-#{record.live? ? 'success' : 'secondary'}") do
|
|
23
|
+
record.state.humanize
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module Munificent
|
|
2
|
+
class BundleTierPresenter < Admin::ApplicationPresenter
|
|
3
|
+
delegate(
|
|
4
|
+
%i[
|
|
5
|
+
id
|
|
6
|
+
ends_at
|
|
7
|
+
fundraiser
|
|
8
|
+
starts_at
|
|
9
|
+
],
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
def price
|
|
13
|
+
record.human_price(symbol: true)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def games
|
|
17
|
+
record.bundle_tier_games.map(&:game).map(&:name).join(", ")
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
module Munificent
|
|
2
|
+
class DonationPresenter < Admin::ApplicationPresenter
|
|
3
|
+
delegate(
|
|
4
|
+
%i[
|
|
5
|
+
id
|
|
6
|
+
amount
|
|
7
|
+
created_at
|
|
8
|
+
curated_streamer
|
|
9
|
+
donated_by
|
|
10
|
+
donator_name
|
|
11
|
+
fundraiser
|
|
12
|
+
message
|
|
13
|
+
],
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
def state
|
|
17
|
+
tag.span(class: "badge text-bg-#{badge_type}") do
|
|
18
|
+
record.state.humanize
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def badge_type
|
|
25
|
+
case record.state
|
|
26
|
+
when "pending"
|
|
27
|
+
"secondary"
|
|
28
|
+
when "paid"
|
|
29
|
+
"info"
|
|
30
|
+
when "cancelled"
|
|
31
|
+
"danger"
|
|
32
|
+
when "fulfilled"
|
|
33
|
+
"success"
|
|
34
|
+
else
|
|
35
|
+
"light"
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|