plutonium 0.14.0 → 0.15.0.pre.rc1
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 +4 -4
- data/README copy.md +1 -1
- data/README.md +1 -1
- data/app/assets/plutonium.css +1 -1
- data/app/views/{application → plutonium}/_resource_header.html copy.erb +1 -1
- data/app/views/{application → plutonium}/_resource_header.html.erb +1 -1
- data/app/views/{application → plutonium}/_resource_sidebar.html.erb +2 -0
- data/app/views/resource/_resource_details.html.erb +1 -36
- data/app/views/resource/_resource_form.html.erb +1 -5
- data/app/views/resource/_resource_table.html.erb +315 -85
- data/app/views/resource/edit.html.erb +1 -5
- data/app/views/resource/index.html.erb +1 -5
- data/app/views/resource/new.html.erb +1 -5
- data/app/views/resource/show.html.erb +1 -5
- data/config/initializers/pagy.rb +1 -0
- data/config/initializers/rabl.rb +27 -20
- data/gemfiles/rails_7.gemfile.lock +5 -1
- data/lib/generators/pu/core/assets/assets_generator.rb +2 -2
- data/lib/generators/pu/core/install/install_generator.rb +0 -3
- data/lib/generators/pu/core/install/templates/app/controllers/plutonium_controller.rb.tt +2 -0
- data/lib/generators/pu/core/install/templates/app/controllers/resource_controller.rb.tt +21 -1
- data/lib/generators/pu/core/install/templates/app/definitions/resource_definition.rb.tt +2 -0
- data/lib/generators/pu/core/install/templates/app/models/resource_record.rb.tt +0 -2
- data/lib/generators/pu/core/install/templates/config/initializers/plutonium.rb +5 -2
- data/lib/generators/pu/eject/shell/shell_generator.rb +2 -2
- data/lib/generators/pu/lib/plutonium_generators/concerns/actions.rb +19 -0
- data/lib/generators/pu/lib/plutonium_generators/concerns/logger.rb +1 -1
- data/lib/generators/pu/lib/plutonium_generators/generator.rb +5 -3
- data/lib/generators/pu/lib/plutonium_generators/model_generator_base.rb +26 -2
- data/lib/generators/pu/pkg/{feature/feature_generator.rb → package/package_generator.rb} +4 -4
- data/lib/generators/pu/pkg/{feature → package}/templates/app/controllers/resource_controller.rb.tt +0 -2
- data/lib/generators/pu/pkg/package/templates/app/definitions/resource_definition.rb.tt +4 -0
- data/lib/generators/pu/pkg/package/templates/app/query_objects/resource_query_object.rb.tt +4 -0
- data/lib/generators/pu/pkg/{app/app_generator.rb → portal/portal_generator.rb} +10 -8
- data/lib/generators/pu/pkg/{app → portal}/templates/app/controllers/concerns/controller.rb.tt +3 -7
- data/lib/generators/pu/pkg/{app → portal}/templates/app/controllers/dashboard_controller.rb.tt +1 -1
- data/lib/generators/pu/pkg/portal/templates/app/controllers/plutonium_controller.rb.tt +5 -0
- data/lib/generators/pu/pkg/{app/templates/app/controllers/controller.rb.tt → portal/templates/app/controllers/resource_controller.rb.tt} +1 -1
- data/lib/generators/pu/pkg/portal/templates/app/definitions/resource_definition.rb.tt +4 -0
- data/lib/generators/pu/pkg/{app → portal}/templates/app/views/package/dashboard/index.html.erb +2 -1
- data/lib/generators/pu/res/conn/conn_generator.rb +78 -3
- data/lib/generators/pu/res/conn/templates/app/controllers/resource_controller.rb.tt +1 -1
- data/lib/generators/pu/res/conn/templates/app/definitions/resource_definition.rb.tt +3 -0
- data/lib/generators/pu/res/conn/templates/app/policies/resource_policy.rb.tt +29 -1
- data/lib/generators/pu/res/conn/templates/app/presenters/resource_presenter.rb.tt +1 -1
- data/lib/generators/pu/res/conn/templates/app/query_objects/resource_query_object.rb.tt +1 -1
- data/lib/generators/pu/res/model/model_generator.rb +0 -7
- data/lib/generators/pu/res/model/templates/model.rb.tt +4 -1
- data/lib/generators/pu/res/scaffold/scaffold_generator.rb +22 -4
- data/lib/generators/pu/res/scaffold/templates/controller.rb.tt +0 -1
- data/lib/generators/pu/res/scaffold/templates/definition.rb.tt +4 -0
- data/lib/generators/pu/res/scaffold/templates/policy.rb.tt +2 -2
- data/lib/generators/pu/rodauth/templates/app/controllers/rodauth_controller.rb.tt +1 -1
- data/lib/generators/pu/rodauth/templates/app/rodauth/account_rodauth_plugin.rb.tt +270 -0
- data/lib/plutonium/action/README.md +0 -0
- data/lib/plutonium/action/base.rb +103 -0
- data/lib/plutonium/action/interactive.rb +117 -0
- data/lib/plutonium/action/route_options.rb +65 -0
- data/lib/plutonium/action/simple.rb +8 -0
- data/lib/plutonium/auth.rb +1 -1
- data/lib/plutonium/configuration.rb +130 -0
- data/lib/plutonium/core/actions/collection.rb +1 -1
- data/lib/plutonium/core/associations/renderers/factory.rb +3 -1
- data/lib/plutonium/core/autodiscovery/association_renderer_discoverer.rb +1 -1
- data/lib/plutonium/core/autodiscovery/input_discoverer.rb +1 -1
- data/lib/plutonium/core/autodiscovery/renderer_discoverer.rb +1 -1
- data/lib/plutonium/core/controller.rb +110 -0
- data/lib/plutonium/core/controllers/authorizable.rb +12 -35
- data/lib/plutonium/core/controllers/bootable.rb +38 -7
- data/lib/plutonium/core/controllers/entity_scoping.rb +6 -2
- data/lib/plutonium/core/fields/renderers/association_renderer.rb +1 -1
- data/lib/plutonium/core/ui/collection.rb +1 -1
- data/lib/plutonium/core/ui/detail.rb +1 -1
- data/lib/plutonium/core/ui/form.rb +1 -1
- data/lib/plutonium/definition/actions.rb +50 -0
- data/lib/plutonium/definition/base.rb +92 -0
- data/lib/plutonium/definition/config_attr.rb +30 -0
- data/lib/plutonium/definition/defineable_props.rb +96 -0
- data/lib/plutonium/definition/search.rb +21 -0
- data/lib/plutonium/engine/validator.rb +30 -0
- data/lib/plutonium/engine.rb +25 -0
- data/lib/plutonium/helpers/assets_helper.rb +73 -20
- data/lib/plutonium/helpers/form_helper.rb +1 -3
- data/lib/plutonium/interaction/README.md +369 -0
- data/lib/plutonium/interaction/base.rb +75 -0
- data/lib/plutonium/interaction/concerns/presentable.rb +61 -0
- data/lib/plutonium/interaction/concerns/workflow_dsl.rb +82 -0
- data/lib/plutonium/interaction/outcome.rb +129 -0
- data/lib/plutonium/interaction/response/base.rb +63 -0
- data/lib/plutonium/interaction/response/null.rb +33 -0
- data/lib/plutonium/interaction/response/redirect.rb +30 -0
- data/lib/plutonium/interaction/response/render.rb +28 -0
- data/lib/plutonium/lib/bit_flags.rb +70 -9
- data/lib/plutonium/lib/overlayed_hash.rb +86 -0
- data/lib/plutonium/lib/smart_cache.rb +171 -0
- data/lib/plutonium/models/has_cents.rb +170 -0
- data/lib/plutonium/{pkg/base.rb → package/engine.rb} +10 -2
- data/lib/plutonium/{application → portal}/controller.rb +3 -11
- data/lib/plutonium/{application → portal}/dynamic_controllers.rb +4 -4
- data/lib/plutonium/portal/engine.rb +15 -0
- data/lib/plutonium/railtie.rb +35 -15
- data/lib/plutonium/reloader.rb +71 -29
- data/lib/plutonium/resource/controller.rb +51 -34
- data/lib/plutonium/resource/controllers/authorizable.rb +128 -0
- data/lib/plutonium/{core → resource}/controllers/crud_actions.rb +23 -22
- data/lib/plutonium/resource/controllers/defineable.rb +26 -0
- data/lib/plutonium/{core → resource}/controllers/interactive_actions.rb +12 -12
- data/lib/plutonium/resource/controllers/presentable.rb +41 -0
- data/lib/plutonium/resource/controllers/queryable.rb +44 -0
- data/lib/plutonium/resource/definition.rb +6 -0
- data/lib/plutonium/resource/policy.rb +25 -13
- data/lib/plutonium/resource/query_object.rb +50 -51
- data/lib/plutonium/resource/record.rb +6 -89
- data/lib/plutonium/resource/register.rb +82 -0
- data/lib/plutonium/routing/mapper_extensions.rb +1 -1
- data/lib/plutonium/routing/resource_registration.rb +1 -1
- data/lib/plutonium/routing/route_set_extensions.rb +6 -18
- data/lib/plutonium/ui/action_button.rb +125 -0
- data/lib/plutonium/ui/breadcrumbs.rb +163 -0
- data/lib/plutonium/ui/component/base.rb +13 -0
- data/lib/plutonium/ui/component/behaviour.rb +38 -0
- data/lib/plutonium/ui/component/kit.rb +31 -0
- data/lib/plutonium/ui/component/methods.rb +54 -0
- data/lib/plutonium/ui/display/base.rb +25 -0
- data/lib/plutonium/ui/display/component/association.rb +26 -0
- data/lib/plutonium/ui/display/resource.rb +77 -0
- data/lib/plutonium/ui/display/theme.rb +27 -0
- data/lib/plutonium/ui/dyna_frame/content.rb +20 -0
- data/lib/plutonium/ui/empty_card.rb +20 -0
- data/lib/plutonium/ui/form/base.rb +37 -0
- data/lib/plutonium/ui/form/resource.rb +75 -0
- data/lib/plutonium/ui/form/theme.rb +42 -0
- data/lib/plutonium/ui/page/base.rb +112 -0
- data/lib/plutonium/ui/page/edit.rb +23 -0
- data/lib/plutonium/ui/page/index.rb +27 -0
- data/lib/plutonium/ui/page/new.rb +23 -0
- data/lib/plutonium/ui/page/show.rb +27 -0
- data/lib/plutonium/ui/page_header.rb +49 -0
- data/lib/plutonium/ui/table/base.rb +13 -0
- data/lib/plutonium/ui/table/components/pagy_info.rb +70 -0
- data/lib/plutonium/ui/table/components/pagy_page_info.rb +70 -0
- data/lib/plutonium/ui/table/components/pagy_pagination.rb +105 -0
- data/lib/plutonium/ui/table/components/scopes_bar.rb +136 -0
- data/lib/plutonium/ui/table/components/search_bar.rb +158 -0
- data/lib/plutonium/ui/table/display_theme.rb +21 -0
- data/lib/plutonium/ui/table/resource.rb +98 -0
- data/lib/plutonium/ui/table/theme.rb +35 -0
- data/lib/plutonium/ui.rb +9 -0
- data/lib/plutonium/version.rb +5 -1
- data/lib/plutonium.rb +53 -26
- data/package-lock.json +19 -22
- data/package.json +4 -4
- data/sig/.keep +0 -0
- data/src/css/plutonium.css +15 -0
- data/tailwind.options.js +11 -3
- metadata +220 -81
- data/lib/generators/pu/core/install/templates/app/presenters/resource_presenter.rb.tt +0 -2
- data/lib/generators/pu/core/install/templates/app/query_objects/resource_query_object.rb.tt +0 -2
- data/lib/generators/pu/pkg/feature/templates/app/query_objects/resource_query_object.rb.tt +0 -4
- data/lib/plutonium/concerns/resource_validatable.rb +0 -34
- data/lib/plutonium/config.rb +0 -9
- data/lib/plutonium/core/controllers/base.rb +0 -101
- data/lib/plutonium/core/controllers/presentable.rb +0 -65
- data/lib/plutonium/core/controllers/queryable.rb +0 -28
- data/lib/plutonium/pkg/app.rb +0 -35
- data/lib/plutonium/pkg/concerns/resource_validatable.rb +0 -36
- data/lib/plutonium/pkg/feature.rb +0 -18
- data/lib/plutonium/policy/initializer.rb +0 -22
- data/lib/plutonium/policy/scope.rb +0 -19
- data/lib/plutonium/pundit/context.rb +0 -18
- data/lib/plutonium/pundit/policy_finder.rb +0 -25
- data/lib/plutonium/resource/policy_context.rb +0 -5
- data/lib/plutonium/resource_register.rb +0 -83
- data/lib/plutonium/smart_cache.rb +0 -151
- data/sig/plutonium.rbs +0 -12
- /data/app/views/{application → plutonium}/_flash.html.erb +0 -0
- /data/app/views/{application → plutonium}/_flash_alerts.html.erb +0 -0
- /data/app/views/{application → plutonium}/_flash_toasts.html.erb +0 -0
- /data/lib/generators/pu/pkg/{app/templates/app/views/package → package/templates}/.keep +0 -0
- /data/lib/generators/pu/pkg/{feature → package}/templates/app/interactions/resource_interaction.rb.tt +0 -0
- /data/lib/generators/pu/pkg/{feature → package}/templates/app/models/resource_record.rb.tt +0 -0
- /data/lib/generators/pu/pkg/{feature → package}/templates/app/policies/resource_policy.rb.tt +0 -0
- /data/lib/generators/pu/pkg/{feature → package}/templates/app/presenters/resource_presenter.rb.tt +0 -0
- /data/lib/generators/pu/pkg/{feature → package}/templates/lib/engine.rb.tt +0 -0
- /data/lib/generators/pu/pkg/{app → portal}/templates/app/policies/resource_policy.rb.tt +0 -0
- /data/lib/generators/pu/pkg/{app → portal}/templates/app/presenters/resource_presenter.rb.tt +0 -0
- /data/lib/generators/pu/pkg/{app → portal}/templates/app/query_objects/resource_query_object.rb.tt +0 -0
- /data/lib/generators/pu/pkg/{feature/templates → portal/templates/app/views/package}/.keep +0 -0
- /data/lib/generators/pu/pkg/{app → portal}/templates/config/routes.rb.tt +0 -0
- /data/lib/generators/pu/pkg/{app → portal}/templates/lib/engine.rb.tt +0 -0
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
require "sequel/core"
|
|
2
|
+
|
|
3
|
+
class <%= account_path.classify %>RodauthPlugin < RodauthPlugin
|
|
4
|
+
configure do
|
|
5
|
+
# This block is running inside of
|
|
6
|
+
# plugin :rodauth do
|
|
7
|
+
# ...
|
|
8
|
+
# end
|
|
9
|
+
|
|
10
|
+
# ==> Features
|
|
11
|
+
# See the Rodauth documentation for the list of available config options:
|
|
12
|
+
# http://rodauth.jeremyevans.net/documentation.html
|
|
13
|
+
|
|
14
|
+
# List of authentication features that are loaded.
|
|
15
|
+
enable <%= selected_features.map(&:inspect).join ', ' %>
|
|
16
|
+
|
|
17
|
+
# ==> General
|
|
18
|
+
|
|
19
|
+
# Change prefix of table and foreign key column names from default "account"
|
|
20
|
+
# accounts_table: 'players'
|
|
21
|
+
|
|
22
|
+
# The secret key used for hashing public-facing tokens for various features.
|
|
23
|
+
# Defaults to Rails `secret_key_base`, but you can use your own secret key.
|
|
24
|
+
# hmac_secret "<SECRET_KEY>"
|
|
25
|
+
|
|
26
|
+
# Use path prefix for all routes.
|
|
27
|
+
<%= '# ' if primary? %>prefix "/<%= account_path.pluralize %>"
|
|
28
|
+
<% unless separate_passwords? -%>
|
|
29
|
+
|
|
30
|
+
# Store password hash in a column instead of a separate table.
|
|
31
|
+
account_password_hash_column :password_hash
|
|
32
|
+
<% end -%>
|
|
33
|
+
|
|
34
|
+
# Specify the controller used for view rendering, CSRF, and callbacks.
|
|
35
|
+
rails_controller { Rodauth::<%= account_path.classify %>Controller }
|
|
36
|
+
|
|
37
|
+
# Specify the model to be used.
|
|
38
|
+
rails_account_model { <%= account_path.classify %> }
|
|
39
|
+
<% if verify_account? -%>
|
|
40
|
+
|
|
41
|
+
# Set password password during create account.
|
|
42
|
+
# verify_account_set_password? false
|
|
43
|
+
<% end -%>
|
|
44
|
+
|
|
45
|
+
# Change some default param keys.
|
|
46
|
+
# login_param "email"
|
|
47
|
+
# password_confirm_param "confirm_password"
|
|
48
|
+
<% if login? -%>
|
|
49
|
+
|
|
50
|
+
# Redirect back to originally requested location after authentication.
|
|
51
|
+
login_return_to_requested_location? true
|
|
52
|
+
<% end -%>
|
|
53
|
+
# two_factor_auth_return_to_requested_location? true # if using MFA
|
|
54
|
+
|
|
55
|
+
# Autologin the user after they have reset their password.
|
|
56
|
+
# reset_password_autologin? true
|
|
57
|
+
|
|
58
|
+
# Delete the account record when the user has closed their account.
|
|
59
|
+
# delete_account_on_close? true
|
|
60
|
+
|
|
61
|
+
# Redirect to the app from login and registration pages if already logged in.
|
|
62
|
+
# already_logged_in { redirect login_redirect }
|
|
63
|
+
<% if jwt? -%>
|
|
64
|
+
|
|
65
|
+
# ==> JWT
|
|
66
|
+
|
|
67
|
+
# Set JWT secret, which is used to cryptographically protect the token.
|
|
68
|
+
jwt_secret Rails.application.credentials.secret_key_base
|
|
69
|
+
<% end -%>
|
|
70
|
+
<% if only_json? -%>
|
|
71
|
+
|
|
72
|
+
# ==> Api only
|
|
73
|
+
|
|
74
|
+
# Accept only JSON requests.
|
|
75
|
+
only_json? true
|
|
76
|
+
|
|
77
|
+
# Handle login and password confirmation fields on the client side.
|
|
78
|
+
require_password_confirmation? false
|
|
79
|
+
require_login_confirmation? false
|
|
80
|
+
<% else -%>
|
|
81
|
+
|
|
82
|
+
# Accept both api and form requests
|
|
83
|
+
# Requires the JSON feature
|
|
84
|
+
<%= '# ' unless json? %>only_json? false
|
|
85
|
+
<% end -%>
|
|
86
|
+
<% if mails? -%>
|
|
87
|
+
|
|
88
|
+
# ==> Emails
|
|
89
|
+
# Use a custom mailer for delivering authentication emails.
|
|
90
|
+
<% if reset_password? -%>
|
|
91
|
+
|
|
92
|
+
create_reset_password_email do
|
|
93
|
+
Rodauth::<%= account_path.classify %>Mailer.reset_password(self.class.configuration_name, account_id, reset_password_key_value)
|
|
94
|
+
end
|
|
95
|
+
<% end -%>
|
|
96
|
+
<% if verify_account? -%>
|
|
97
|
+
|
|
98
|
+
create_verify_account_email do
|
|
99
|
+
Rodauth::<%= account_path.classify %>Mailer.verify_account(self.class.configuration_name, account_id, verify_account_key_value)
|
|
100
|
+
end
|
|
101
|
+
<% end -%>
|
|
102
|
+
<% if verify_login_change? -%>
|
|
103
|
+
|
|
104
|
+
create_verify_login_change_email do |_login|
|
|
105
|
+
Rodauth::<%= account_path.classify %>Mailer.verify_login_change(self.class.configuration_name, account_id, verify_login_change_key_value)
|
|
106
|
+
end
|
|
107
|
+
<% end -%>
|
|
108
|
+
<% if change_password_notify? -%>
|
|
109
|
+
|
|
110
|
+
create_password_changed_email do
|
|
111
|
+
Rodauth::<%= account_path.classify %>Mailer.change_password_notify(self.class.configuration_name, account_id)
|
|
112
|
+
end
|
|
113
|
+
<% end -%>
|
|
114
|
+
<% if reset_password_notify? -%>
|
|
115
|
+
|
|
116
|
+
create_reset_password_notify_email do
|
|
117
|
+
Rodauth::<%= account_path.classify %>Mailer.reset_password_notify(self.class.configuration_name, account_id)
|
|
118
|
+
end
|
|
119
|
+
<% end -%>
|
|
120
|
+
<% if email_auth? -%>
|
|
121
|
+
|
|
122
|
+
create_email_auth_email do
|
|
123
|
+
Rodauth::<%= account_path.classify %>Mailer.email_auth(self.class.configuration_name, account_id, email_auth_key_value)
|
|
124
|
+
end
|
|
125
|
+
<% end -%>
|
|
126
|
+
<% if lockout? -%>
|
|
127
|
+
|
|
128
|
+
create_unlock_account_email do
|
|
129
|
+
Rodauth::<%= account_path.classify %>Mailer.unlock_account(self.class.configuration_name, account_id, unlock_account_key_value)
|
|
130
|
+
end
|
|
131
|
+
<% end -%>
|
|
132
|
+
|
|
133
|
+
send_email do |email|
|
|
134
|
+
# queue email delivery on the mailer after the transaction commits
|
|
135
|
+
db.after_commit { email.deliver_later }
|
|
136
|
+
end
|
|
137
|
+
<% end -%>
|
|
138
|
+
<% unless only_json? -%>
|
|
139
|
+
|
|
140
|
+
# ==> Flash
|
|
141
|
+
# Does not work with only_json?
|
|
142
|
+
|
|
143
|
+
# Match flash keys with ones already used in the Rails app.
|
|
144
|
+
# flash_notice_key :success # default is :notice
|
|
145
|
+
# flash_error_key :error # default is :alert
|
|
146
|
+
|
|
147
|
+
# Override default flash messages.
|
|
148
|
+
# create_account_notice_flash "Your account has been created. Please verify your account by visiting the confirmation link sent to your email address."
|
|
149
|
+
# require_login_error_flash "Login is required for accessing this page"
|
|
150
|
+
# login_notice_flash nil
|
|
151
|
+
<% end -%>
|
|
152
|
+
|
|
153
|
+
# ==> Validation
|
|
154
|
+
# Override default validation error messages.
|
|
155
|
+
# no_matching_login_message "user with this email address doesn't exist"
|
|
156
|
+
# already_an_account_with_this_login_message "user with this email address already exists"
|
|
157
|
+
# password_too_short_message { "needs to have at least #{password_minimum_length} characters" }
|
|
158
|
+
# login_does_not_meet_requirements_message { "invalid email#{", #{login_requirement_message}" if login_requirement_message}" }
|
|
159
|
+
|
|
160
|
+
# ==> Passwords
|
|
161
|
+
|
|
162
|
+
# Passwords shorter than 8 characters are considered weak according to OWASP.
|
|
163
|
+
<%= '# ' unless login? %>password_minimum_length 8
|
|
164
|
+
|
|
165
|
+
# Custom password complexity requirements (alternative to password_complexity feature).
|
|
166
|
+
# password_meets_requirements? do |password|
|
|
167
|
+
# super(password) && password_complex_enough?(password)
|
|
168
|
+
# end
|
|
169
|
+
# auth_class_eval do
|
|
170
|
+
# def password_complex_enough?(password)
|
|
171
|
+
# return true if password.match?(/\d/) && password.match?(/[^a-zA-Z\d]/)
|
|
172
|
+
# set_password_requirement_error_message(:password_simple, "requires one number and one special character")
|
|
173
|
+
# false
|
|
174
|
+
# end
|
|
175
|
+
# end
|
|
176
|
+
<% unless argon2? -%>
|
|
177
|
+
|
|
178
|
+
# = bcrypt
|
|
179
|
+
|
|
180
|
+
# bcrypt has a maximum input length of 72 bytes, truncating any extra bytes.
|
|
181
|
+
password_maximum_bytes 72 if respond_to?(:password_maximum_bytes)
|
|
182
|
+
<% else -%>
|
|
183
|
+
|
|
184
|
+
# = argon2
|
|
185
|
+
|
|
186
|
+
# Use a rotatable password pepper when hashing passwords with Argon2.
|
|
187
|
+
argon2_secret "TODO: <SECRET_KEY>"
|
|
188
|
+
|
|
189
|
+
# Since we're using argon2, prevent loading the bcrypt gem to save memory.
|
|
190
|
+
require_bcrypt? false
|
|
191
|
+
|
|
192
|
+
# Having a maximum password length set prevents long password DoS attacks.
|
|
193
|
+
password_maximum_length 64 if respond_to?(:password_maximum_length)
|
|
194
|
+
<% end -%>
|
|
195
|
+
<% if remember? -%>
|
|
196
|
+
|
|
197
|
+
# ==> Remember Feature
|
|
198
|
+
|
|
199
|
+
# Remember all logged in users.
|
|
200
|
+
after_login { remember_login }
|
|
201
|
+
|
|
202
|
+
# Or only remember users that have ticked a "Remember Me" checkbox on login.
|
|
203
|
+
# after_login { remember_login if param_or_nil("remember") }
|
|
204
|
+
|
|
205
|
+
# Extend user's remember period when remembered via a cookie
|
|
206
|
+
extend_remember_deadline? true
|
|
207
|
+
|
|
208
|
+
# Store the user's remember cookie under a namespace
|
|
209
|
+
remember_cookie_key "_<%= table_prefix %>_remember"
|
|
210
|
+
<% end -%>
|
|
211
|
+
|
|
212
|
+
# ==> Hooks
|
|
213
|
+
|
|
214
|
+
# Validate custom fields in the create account form.
|
|
215
|
+
# before_create_account do
|
|
216
|
+
# throw_error_status(422, "name", "must be present") if param("name").empty?
|
|
217
|
+
# end
|
|
218
|
+
|
|
219
|
+
# Perform additional actions after the account is created.
|
|
220
|
+
# after_create_account do
|
|
221
|
+
# Profile.create!(account_id: account_id, name: param("name"))
|
|
222
|
+
# end
|
|
223
|
+
|
|
224
|
+
# Do additional cleanup after the account is closed.
|
|
225
|
+
# after_close_account do
|
|
226
|
+
# Profile.find_by!(account_id: account_id).destroy
|
|
227
|
+
# end
|
|
228
|
+
<% unless only_json? -%>
|
|
229
|
+
|
|
230
|
+
# ==> Redirects
|
|
231
|
+
<% if create_account? -%>
|
|
232
|
+
|
|
233
|
+
# Redirect to home after login.
|
|
234
|
+
create_account_redirect "/"
|
|
235
|
+
<% end -%>
|
|
236
|
+
<% if login? -%>
|
|
237
|
+
|
|
238
|
+
# Redirect to home after login.
|
|
239
|
+
login_redirect "/"
|
|
240
|
+
<% end -%>
|
|
241
|
+
<% if logout? -%>
|
|
242
|
+
|
|
243
|
+
# Redirect to home page after logout.
|
|
244
|
+
logout_redirect "/"
|
|
245
|
+
<% end -%>
|
|
246
|
+
<% if verify_account? -%>
|
|
247
|
+
|
|
248
|
+
# Redirect to wherever login redirects to after account verification.
|
|
249
|
+
verify_account_redirect { login_redirect }
|
|
250
|
+
<% end -%>
|
|
251
|
+
<% if reset_password? -%>
|
|
252
|
+
|
|
253
|
+
# Redirect to login page after password reset.
|
|
254
|
+
reset_password_redirect { login_path }
|
|
255
|
+
<% end -%>
|
|
256
|
+
|
|
257
|
+
# Ensure requiring login follows login route changes.
|
|
258
|
+
require_login_redirect { login_path }
|
|
259
|
+
<% end -%>
|
|
260
|
+
|
|
261
|
+
# ==> Deadlines
|
|
262
|
+
# Change default deadlines for some actions.
|
|
263
|
+
# verify_account_grace_period 3.days.to_i
|
|
264
|
+
# reset_password_deadline_interval Hash[hours: 6]
|
|
265
|
+
# verify_login_change_deadline_interval Hash[days: 2]
|
|
266
|
+
<% unless only_json? -%>
|
|
267
|
+
# remember_deadline_interval Hash[days: 30]
|
|
268
|
+
<% end -%>
|
|
269
|
+
end
|
|
270
|
+
end
|
|
File without changes
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_support/string_inquirer"
|
|
4
|
+
|
|
5
|
+
module Plutonium
|
|
6
|
+
module Action
|
|
7
|
+
# Base class for all actions in the Plutonium framework.
|
|
8
|
+
#
|
|
9
|
+
# @attr_reader [Symbol] name The name of the action.
|
|
10
|
+
# @attr_reader [String] label The human-readable label for the action.
|
|
11
|
+
# @attr_reader [String, nil] icon The icon associated with the action.
|
|
12
|
+
# @attr_reader [RouteOptions] route_options The routing options for the action.
|
|
13
|
+
# @attr_reader [String, nil] confirmation The confirmation message for the action.
|
|
14
|
+
# @attr_reader [String, nil] turbo_frame The Turbo Frame ID for the action.
|
|
15
|
+
# @attr_reader [Symbol, nil] color The color associated with the action.
|
|
16
|
+
# @attr_reader [Symbol, nil] category The category of the action.
|
|
17
|
+
# @attr_reader [Integer] position The position of the action within its category.
|
|
18
|
+
class Base
|
|
19
|
+
attr_reader :name, :label, :icon, :route_options, :confirmation, :turbo_frame, :color, :category, :position
|
|
20
|
+
|
|
21
|
+
# Initialize a new action.
|
|
22
|
+
#
|
|
23
|
+
# @param [Symbol] name The name of the action.
|
|
24
|
+
# @param [Hash] options The options for the action.
|
|
25
|
+
# @option options [String] :label The human-readable label for the action.
|
|
26
|
+
# @option options [String] :icon The icon associated with the action (e.g., 'fa-edit' for Font Awesome).
|
|
27
|
+
# @option options [Symbol] :color The color associated with the action (e.g., :primary, :secondary, :success, :warning, :danger).
|
|
28
|
+
# @option options [String] :confirmation The confirmation message to display before executing the action.
|
|
29
|
+
# @option options [RouteOptions, Hash] :route_options The routing options for the action.
|
|
30
|
+
# @option options [String] :turbo_frame The Turbo Frame ID for the action (used in Hotwire/Turbo Drive applications).
|
|
31
|
+
# @option options [Boolean] :bulk_action (false) If true, applies to a bulk selection of records (e.g., "Mark Selected as Read").
|
|
32
|
+
# @option options [Boolean] :collection_record_action (false) If true, applies to records in a collection (e.g., "Edit Record" button in a table).
|
|
33
|
+
# @option options [Boolean] :record_action (false) If true, applies to an individual record (e.g., "Delete" button on a Show page).
|
|
34
|
+
# @option options [Boolean] :resource_action (false) If true, applies to the entire resource and can be used in any context (e.g., "Import from CSV").
|
|
35
|
+
# @option options [Symbol] :category The category of the action. Determines visibility and grouping.
|
|
36
|
+
# Valid values include:
|
|
37
|
+
# @option options [Symbol] :primary Always shown and given prominence in the UI.
|
|
38
|
+
# @option options [Symbol] :secondary Shown in secondary menus or less prominent areas.
|
|
39
|
+
# @option options [Symbol] :danger Actions that require caution, often destructive operations.
|
|
40
|
+
# @option options [Integer] :position (50) The position of the action in its group. Lower numbers appear first.
|
|
41
|
+
def initialize(name, **options)
|
|
42
|
+
@name = name.to_sym
|
|
43
|
+
@label = options[:label] || name.to_s.humanize
|
|
44
|
+
@icon = options[:icon]
|
|
45
|
+
@color = options[:color]
|
|
46
|
+
@confirmation = options[:confirmation]
|
|
47
|
+
@route_options = build_route_options(options[:route_options])
|
|
48
|
+
@turbo_frame = options[:turbo_frame]
|
|
49
|
+
@bulk_action = options[:bulk_action] || false
|
|
50
|
+
@collection_record_action = options[:collection_record_action] || false
|
|
51
|
+
@record_action = options[:record_action] || false
|
|
52
|
+
@resource_action = options[:resource_action] || false
|
|
53
|
+
@category = ActiveSupport::StringInquirer.new((options[:category] || :secondary).to_s)
|
|
54
|
+
@position = options[:position] || 50
|
|
55
|
+
|
|
56
|
+
freeze
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# @return [Boolean] Whether this is a bulk action.
|
|
60
|
+
def bulk_action?
|
|
61
|
+
@bulk_action
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# @return [Boolean] Whether this is a collection record action.
|
|
65
|
+
def collection_record_action?
|
|
66
|
+
@collection_record_action
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# @return [Boolean] Whether this is a record action.
|
|
70
|
+
def record_action?
|
|
71
|
+
@record_action
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# @return [Boolean] Whether this is a resource action.
|
|
75
|
+
def resource_action?
|
|
76
|
+
@resource_action
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def permitted_by?(policy)
|
|
80
|
+
policy.allowed_to?(:"#{name}?")
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
private
|
|
84
|
+
|
|
85
|
+
# Build RouteOptions from the provided options
|
|
86
|
+
#
|
|
87
|
+
# @param [RouteOptions, Hash, nil] options The routing options
|
|
88
|
+
# @return [RouteOptions] The built RouteOptions object
|
|
89
|
+
def build_route_options(options)
|
|
90
|
+
case options
|
|
91
|
+
when RouteOptions
|
|
92
|
+
options
|
|
93
|
+
when Hash
|
|
94
|
+
RouteOptions.new(**options)
|
|
95
|
+
when nil
|
|
96
|
+
RouteOptions.new
|
|
97
|
+
else
|
|
98
|
+
raise ArgumentError, "Invalid route_options. Expected RouteOptions, Hash, or nil."
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Plutonium
|
|
4
|
+
module Action
|
|
5
|
+
# InteractiveAction class for handling interactive actions in the Plutonium framework
|
|
6
|
+
#
|
|
7
|
+
# @attr_reader [Class] interaction The interaction class associated with this action
|
|
8
|
+
# @attr_reader [Boolean] immediate Whether the action is executed immediately
|
|
9
|
+
class Interactive < Base
|
|
10
|
+
attr_reader :interaction, :immediate
|
|
11
|
+
|
|
12
|
+
# Initialize a new InteractiveAction
|
|
13
|
+
#
|
|
14
|
+
# @param [Symbol] name The name of the action
|
|
15
|
+
# @param [Class] interaction The interaction class for this action
|
|
16
|
+
# @param [Boolean] immediate Whether the action is executed immediately
|
|
17
|
+
# @param [Hash] options Additional options for the action
|
|
18
|
+
def initialize(name, interaction:, immediate:, **)
|
|
19
|
+
@interaction = interaction
|
|
20
|
+
@immediate = immediate
|
|
21
|
+
|
|
22
|
+
super(name, **)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Get the confirmation message for the action
|
|
26
|
+
#
|
|
27
|
+
# @return [String, nil] The confirmation message or nil if not applicable
|
|
28
|
+
def confirmation
|
|
29
|
+
super || (@immediate ? "#{label}?" : nil)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Factory for creating Interactive actions
|
|
33
|
+
class Factory
|
|
34
|
+
# Create a new Interactive action based on the interaction type
|
|
35
|
+
#
|
|
36
|
+
# @param [Symbol] name The name of the action
|
|
37
|
+
# @param [Class] interaction The interaction class
|
|
38
|
+
# @param [Hash] options Additional options for the action
|
|
39
|
+
# @return [Interactive] A new Interactive action instance
|
|
40
|
+
def self.create(name, interaction:, **options)
|
|
41
|
+
attribute_names = symbolized_attribute_names(interaction)
|
|
42
|
+
action_type = determine_action_type(attribute_names)
|
|
43
|
+
input_fields = determine_input_fields(attribute_names)
|
|
44
|
+
action_options = determine_action_options(action_type)
|
|
45
|
+
immediate = options.fetch(:immediate) { input_fields.blank? }
|
|
46
|
+
route_options = build_route_options(name, action_type, immediate)
|
|
47
|
+
|
|
48
|
+
Interactive.new(
|
|
49
|
+
name,
|
|
50
|
+
interaction: interaction,
|
|
51
|
+
immediate: immediate,
|
|
52
|
+
route_options: route_options,
|
|
53
|
+
**action_options,
|
|
54
|
+
**options
|
|
55
|
+
)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Get symbolized attribute names for the interaction
|
|
59
|
+
#
|
|
60
|
+
# @param [Class] interaction The interaction class
|
|
61
|
+
# @return [Array<Symbol>] Symbolized attribute names
|
|
62
|
+
def self.symbolized_attribute_names(interaction)
|
|
63
|
+
interaction.attribute_names.map(&:to_sym)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Determine the action type based on the interaction's attributes
|
|
67
|
+
#
|
|
68
|
+
# @param [Array<Symbol>] attribute_names Symbolized attribute names
|
|
69
|
+
# @return [Symbol] The determined action type
|
|
70
|
+
def self.determine_action_type(attribute_names)
|
|
71
|
+
if attribute_names.include?(:resource)
|
|
72
|
+
:interactive_resource_record_action
|
|
73
|
+
elsif attribute_names.include?(:resources)
|
|
74
|
+
:interactive_resource_collection_action
|
|
75
|
+
else
|
|
76
|
+
:interactive_resource_recordless_action
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Determine the input fields for the action
|
|
81
|
+
#
|
|
82
|
+
# @param [Array<Symbol>] attribute_names Symbolized attribute names
|
|
83
|
+
# @return [Array<Symbol>] The input fields
|
|
84
|
+
def self.determine_input_fields(attribute_names)
|
|
85
|
+
attribute_names - [:resource, :resources]
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Determine the action options based on the action type
|
|
89
|
+
#
|
|
90
|
+
# @param [Symbol] action_type The type of the action
|
|
91
|
+
# @return [Hash] The action options
|
|
92
|
+
def self.determine_action_options(action_type)
|
|
93
|
+
{
|
|
94
|
+
bulk_action: action_type == :interactive_resource_collection_action,
|
|
95
|
+
record_action: action_type == :interactive_resource_record_action,
|
|
96
|
+
collection_record_action: action_type == :interactive_resource_record_action,
|
|
97
|
+
resource_action: action_type == :interactive_resource_recordless_action
|
|
98
|
+
}
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Build the route options for the action
|
|
102
|
+
#
|
|
103
|
+
# @param [Symbol] name The name of the action
|
|
104
|
+
# @param [Symbol] action_type The type of the action
|
|
105
|
+
# @param [Boolean] immediate Whether the action is executed immediately
|
|
106
|
+
# @return [RouteOptions] The route options for the action
|
|
107
|
+
def self.build_route_options(name, action_type, immediate)
|
|
108
|
+
RouteOptions.new(
|
|
109
|
+
method: immediate ? :post : :get,
|
|
110
|
+
action: action_type,
|
|
111
|
+
interactive_action: name
|
|
112
|
+
)
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Plutonium
|
|
4
|
+
module Action
|
|
5
|
+
# RouteOptions class for handling routing options in the Plutonium framework.
|
|
6
|
+
#
|
|
7
|
+
# @attr_reader [Symbol] method The HTTP method for the route.
|
|
8
|
+
# @attr_reader [Array] url_args The positional arguments for URL generation.
|
|
9
|
+
# @attr_reader [Hash] url_options URL options for the route.
|
|
10
|
+
# @attr_reader [Symbol] url_resolver The method to use for resolving URLs.
|
|
11
|
+
class RouteOptions
|
|
12
|
+
attr_reader :method, :url_args, :url_options, :url_resolver
|
|
13
|
+
|
|
14
|
+
# Initialize a new RouteOptions instance.
|
|
15
|
+
#
|
|
16
|
+
# @param [Array] url_args The positional arguments for URL generation.
|
|
17
|
+
# @param [Symbol] method The HTTP method for the route (default: :get).
|
|
18
|
+
# @param [Symbol] url_resolver The method to use for resolving URLs (default: :resource_url_for).
|
|
19
|
+
# @param [Hash] url_options URL options for the route.
|
|
20
|
+
def initialize(*url_args, method: :get, url_resolver: :resource_url_for, **url_options)
|
|
21
|
+
@method = method
|
|
22
|
+
@url_resolver = url_resolver
|
|
23
|
+
@url_args = url_args
|
|
24
|
+
@url_options = url_options.freeze
|
|
25
|
+
freeze
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Convert the RouteOptions to arguments suitable for URL helpers.
|
|
29
|
+
#
|
|
30
|
+
# @return [Array] The arguments for URL generation.
|
|
31
|
+
def to_url_args
|
|
32
|
+
@url_args + [@url_options]
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Merge this RouteOptions with another RouteOptions instance.
|
|
36
|
+
#
|
|
37
|
+
# @param [RouteOptions] other The other RouteOptions instance to merge with.
|
|
38
|
+
# @return [RouteOptions] A new RouteOptions instance with merged values.
|
|
39
|
+
def merge(other)
|
|
40
|
+
self.class.new(
|
|
41
|
+
*(@url_args | other.url_args),
|
|
42
|
+
method: other.method || @method,
|
|
43
|
+
url_resolver: other.url_resolver || @url_resolver,
|
|
44
|
+
**@url_options.merge(other.url_options)
|
|
45
|
+
)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def ==(other)
|
|
49
|
+
self.class == other.class &&
|
|
50
|
+
method == other.method &&
|
|
51
|
+
url_resolver == other.url_resolver &&
|
|
52
|
+
url_args == other.url_args &&
|
|
53
|
+
url_options == other.url_options
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def eql?(other)
|
|
57
|
+
self == other
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def hash
|
|
61
|
+
[self.class, method, url_resolver, url_args, url_options].hash
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|