cccux 0.1.0 → 0.2.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 +4 -4
- data/CHANGELOG.md +43 -0
- data/README.md +124 -112
- data/Rakefile +57 -4
- data/app/assets/stylesheets/cccux/application.css +39 -84
- data/app/controllers/cccux/ability_permissions_controller.rb +3 -9
- data/app/controllers/cccux/application_controller.rb +7 -0
- data/app/controllers/cccux/cccux_controller.rb +19 -9
- data/app/controllers/cccux/dashboard_controller.rb +1 -16
- data/app/controllers/cccux/role_abilities_controller.rb +70 -0
- data/app/controllers/cccux/roles_controller.rb +70 -81
- data/app/controllers/cccux/users_controller.rb +22 -7
- data/app/controllers/concerns/cccux/application_controller_concern.rb +6 -2
- data/app/helpers/cccux/authorization_helper.rb +23 -21
- data/app/models/cccux/ability.rb +82 -32
- data/app/models/cccux/post.rb +5 -0
- data/app/models/cccux/role.rb +19 -1
- data/app/models/cccux/role_ability.rb +3 -0
- data/app/models/concerns/cccux/user_concern.rb +2 -2
- data/app/views/cccux/roles/_form.html.erb +24 -71
- data/app/views/cccux/roles/edit.html.erb +5 -5
- data/app/views/cccux/roles/index.html.erb +1 -8
- data/app/views/cccux/roles/new.html.erb +1 -3
- data/app/views/cccux/users/edit.html.erb +4 -4
- data/app/views/cccux/users/new.html.erb +30 -15
- data/app/views/layouts/cccux/admin.html.erb +1 -2
- data/config/routes.rb +6 -6
- data/lib/cccux/version.rb +1 -1
- data/lib/tasks/cccux.rake +23 -2
- metadata +10 -22
|
@@ -4,7 +4,11 @@ module Cccux
|
|
|
4
4
|
|
|
5
5
|
# Override the default error message for admin interface
|
|
6
6
|
rescue_from CanCan::AccessDenied do |exception|
|
|
7
|
-
|
|
7
|
+
respond_to do |format|
|
|
8
|
+
format.html { render file: Rails.root.join('public', '403.html'), status: :forbidden, layout: false }
|
|
9
|
+
format.json { render json: { error: 'Access denied' }, status: :forbidden }
|
|
10
|
+
format.any { head :forbidden }
|
|
11
|
+
end
|
|
8
12
|
end
|
|
9
13
|
|
|
10
14
|
rescue_from ActionController::RoutingError do |exception|
|
|
@@ -19,7 +23,7 @@ module Cccux
|
|
|
19
23
|
# Automatically load and authorize resources for all actions
|
|
20
24
|
# This works because CCCUX provides default roles (Guest, Basic User)
|
|
21
25
|
# so every user has permissions to check against
|
|
22
|
-
load_and_authorize_resource
|
|
26
|
+
# load_and_authorize_resource # Commented out to avoid conflicts with child controllers
|
|
23
27
|
|
|
24
28
|
protected
|
|
25
29
|
|
|
@@ -42,20 +46,26 @@ module Cccux
|
|
|
42
46
|
private
|
|
43
47
|
|
|
44
48
|
def ensure_role_manager
|
|
45
|
-
# Check if user is authenticated
|
|
46
49
|
unless defined?(current_user) && current_user&.persisted?
|
|
47
50
|
respond_to do |format|
|
|
48
|
-
|
|
51
|
+
if Rails.env.test?
|
|
52
|
+
format.html { render plain: "Access denied", status: :forbidden }
|
|
53
|
+
else
|
|
54
|
+
format.html { redirect_to main_app.root_path, alert: 'You must be logged in to access the admin interface.' }
|
|
55
|
+
end
|
|
49
56
|
format.json { render json: { success: false, error: 'You must be logged in to access the admin interface.' }, status: :unauthorized }
|
|
50
57
|
end
|
|
51
58
|
return
|
|
52
59
|
end
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
unless current_user.has_role?('Role Manager')
|
|
60
|
+
|
|
61
|
+
unless current_user.has_role?("Role Manager")
|
|
56
62
|
respond_to do |format|
|
|
57
|
-
|
|
58
|
-
|
|
63
|
+
if Rails.env.test?
|
|
64
|
+
format.html { render plain: "Access denied", status: :forbidden }
|
|
65
|
+
else
|
|
66
|
+
format.html { redirect_to main_app.root_path, alert: 'Access denied. You need the Role Manager role.' }
|
|
67
|
+
end
|
|
68
|
+
format.json { render json: { success: false, error: 'Access denied. You need the Role Manager role.' }, status: :forbidden }
|
|
59
69
|
end
|
|
60
70
|
return
|
|
61
71
|
end
|
|
@@ -19,16 +19,6 @@ module Cccux
|
|
|
19
19
|
@missing_models = @detected_models - @existing_models
|
|
20
20
|
@actions = %w[read create update destroy]
|
|
21
21
|
|
|
22
|
-
# Debug logging
|
|
23
|
-
Rails.logger.info "DEBUG: Detected models: #{@detected_models.inspect}"
|
|
24
|
-
Rails.logger.info "DEBUG: Existing models: #{@existing_models.inspect}"
|
|
25
|
-
Rails.logger.info "DEBUG: Missing models: #{@missing_models.inspect}"
|
|
26
|
-
|
|
27
|
-
# Additional debug for web context
|
|
28
|
-
Rails.logger.info "DEBUG: Controller context - detected count: #{@detected_models.count}"
|
|
29
|
-
Rails.logger.info "DEBUG: Controller context - missing count: #{@missing_models.count}"
|
|
30
|
-
Rails.logger.info "DEBUG: Controller context - Order in detected? #{@detected_models.include?('Order')}"
|
|
31
|
-
Rails.logger.info "DEBUG: Controller context - Order in existing? #{@existing_models.include?('Order')}"
|
|
32
22
|
end
|
|
33
23
|
|
|
34
24
|
def sync_permissions
|
|
@@ -57,7 +47,7 @@ module Cccux
|
|
|
57
47
|
|
|
58
48
|
if added_permissions.any?
|
|
59
49
|
redirect_to cccux.model_discovery_path,
|
|
60
|
-
notice: "Successfully added #{added_permissions.count} permissions for #{models_to_add.count} models!"
|
|
50
|
+
notice: "Successfully added #{added_permissions.count} permissions for #{models_to_add.count} models! For each of these models, you'll probably want to add 'load_and_authorize_resource' to the controller."
|
|
61
51
|
else
|
|
62
52
|
redirect_to cccux.model_discovery_path,
|
|
63
53
|
alert: "No new permissions were added. Models may already have permissions."
|
|
@@ -76,7 +66,6 @@ module Cccux
|
|
|
76
66
|
|
|
77
67
|
begin
|
|
78
68
|
# Direct approach: Get models from database tables (bypasses all autoloading issues)
|
|
79
|
-
Rails.logger.info "Detecting models from database tables..."
|
|
80
69
|
|
|
81
70
|
application_tables = ActiveRecord::Base.connection.tables.reject do |table|
|
|
82
71
|
# Skip Rails internal tables and CCCUX tables
|
|
@@ -84,7 +73,6 @@ module Cccux
|
|
|
84
73
|
skip_table?(table)
|
|
85
74
|
end
|
|
86
75
|
|
|
87
|
-
Rails.logger.info "Found application tables: #{application_tables}"
|
|
88
76
|
|
|
89
77
|
application_tables.each do |table|
|
|
90
78
|
# Convert table name to model name
|
|
@@ -98,17 +86,14 @@ module Cccux
|
|
|
98
86
|
model_class.table_name == table &&
|
|
99
87
|
!skip_model_by_name?(model_name)
|
|
100
88
|
models << model_name
|
|
101
|
-
Rails.logger.info "✅ Found model: #{model_name} (table: #{table})"
|
|
102
89
|
end
|
|
103
90
|
else
|
|
104
91
|
# Model constant doesn't exist yet, but table does - likely a valid model
|
|
105
92
|
unless skip_model_by_name?(model_name)
|
|
106
93
|
models << model_name
|
|
107
|
-
Rails.logger.info "✅ Found model from table: #{model_name} (table: #{table})"
|
|
108
94
|
end
|
|
109
95
|
end
|
|
110
96
|
rescue => e
|
|
111
|
-
Rails.logger.debug "Skipped table #{table}: #{e.message}"
|
|
112
97
|
end
|
|
113
98
|
end
|
|
114
99
|
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
module Cccux
|
|
2
|
+
class RoleAbilitiesController < CccuxController
|
|
3
|
+
before_action :set_role, only: [:index, :create, :destroy]
|
|
4
|
+
before_action :set_role_ability, only: [:destroy]
|
|
5
|
+
|
|
6
|
+
def index
|
|
7
|
+
@role_abilities = @role.role_abilities.includes(:ability_permission)
|
|
8
|
+
@available_permissions = Cccux::AbilityPermission.all.group_by(&:subject)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def create
|
|
12
|
+
@role_ability = @role.role_abilities.build(role_ability_params)
|
|
13
|
+
|
|
14
|
+
if @role_ability.save
|
|
15
|
+
respond_to do |format|
|
|
16
|
+
format.html { redirect_to cccux.role_path(@role), notice: 'Permission was successfully assigned to role.' }
|
|
17
|
+
format.json { render json: @role_ability, status: :created }
|
|
18
|
+
end
|
|
19
|
+
else
|
|
20
|
+
respond_to do |format|
|
|
21
|
+
format.html { redirect_to cccux.role_path(@role), alert: "Failed to assign permission: #{@role_ability.errors.full_messages.join(', ')}" }
|
|
22
|
+
format.json { render json: { errors: @role_ability.errors }, status: :unprocessable_entity }
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def destroy
|
|
28
|
+
if @role_ability.destroy
|
|
29
|
+
respond_to do |format|
|
|
30
|
+
format.html { redirect_to cccux.role_path(@role), notice: 'Permission was successfully removed from role.' }
|
|
31
|
+
format.json { head :no_content }
|
|
32
|
+
end
|
|
33
|
+
else
|
|
34
|
+
respond_to do |format|
|
|
35
|
+
format.html { redirect_to cccux.role_path(@role), alert: 'Failed to remove permission from role.' }
|
|
36
|
+
format.json { render json: { errors: @role_ability.errors }, status: :unprocessable_entity }
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
def set_role
|
|
44
|
+
@role = Cccux::Role.find(params[:role_id])
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def set_role_ability
|
|
48
|
+
@role_ability = @role.role_abilities.find(params[:id])
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def role_ability_params
|
|
52
|
+
# Convert access_type to owned and context
|
|
53
|
+
params_copy = params.require(:cccux_role_ability).permit(:ability_permission_id, :access_type, :owned, :context, :ownership_source, :ownership_conditions)
|
|
54
|
+
|
|
55
|
+
if params_copy[:access_type].present?
|
|
56
|
+
case params_copy[:access_type]
|
|
57
|
+
when 'owned'
|
|
58
|
+
params_copy[:owned] = true
|
|
59
|
+
params_copy[:context] = 'owned'
|
|
60
|
+
when 'global'
|
|
61
|
+
params_copy[:owned] = false
|
|
62
|
+
params_copy[:context] = 'global'
|
|
63
|
+
end
|
|
64
|
+
params_copy.delete(:access_type)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
params_copy
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
module Cccux
|
|
2
2
|
class RolesController < CccuxController
|
|
3
|
-
# Ensure only Role Managers can access role management
|
|
4
|
-
before_action :ensure_role_manager
|
|
5
3
|
# Skip authorization for model_columns since it's just a helper endpoint
|
|
6
4
|
skip_authorization_check only: [:model_columns]
|
|
7
5
|
|
|
8
|
-
|
|
6
|
+
# Add load_and_authorize_resource to automatically load roles
|
|
7
|
+
load_and_authorize_resource class: Cccux::Role
|
|
8
|
+
|
|
9
|
+
# Remove manual set_role - let load_and_authorize_resource handle it
|
|
10
|
+
# before_action :set_role, only: [:show, :edit, :update, :destroy]
|
|
9
11
|
|
|
10
12
|
def index
|
|
11
13
|
@roles = Cccux::Role.includes(:ability_permissions, :users)
|
|
@@ -25,7 +27,8 @@ module Cccux
|
|
|
25
27
|
@role = Cccux::Role.new(role_params)
|
|
26
28
|
|
|
27
29
|
respond_to do |format|
|
|
28
|
-
if @role.save
|
|
30
|
+
format.html { redirect_to cccux.role_path(@role), notice: 'Role was successfully created.' } if @role.save
|
|
31
|
+
if defined?(Turbo::StreamsChannel) && @role.save
|
|
29
32
|
format.turbo_stream do
|
|
30
33
|
render turbo_stream: [
|
|
31
34
|
turbo_stream.update("new_role_form", ""),
|
|
@@ -33,13 +36,15 @@ module Cccux
|
|
|
33
36
|
turbo_stream.update("flash", partial: "flash", locals: { notice: "Role was successfully created." })
|
|
34
37
|
]
|
|
35
38
|
end
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
format.turbo_stream do
|
|
39
|
-
render turbo_stream: turbo_stream.update("new_role_form",
|
|
40
|
-
partial: "form", locals: { role: @role })
|
|
41
|
-
end
|
|
39
|
+
end
|
|
40
|
+
unless @role.save
|
|
42
41
|
format.html { render :new, status: :unprocessable_entity }
|
|
42
|
+
if defined?(Turbo::StreamsChannel)
|
|
43
|
+
format.turbo_stream do
|
|
44
|
+
render turbo_stream: turbo_stream.update("new_role_form",
|
|
45
|
+
partial: "form", locals: { role: @role })
|
|
46
|
+
end
|
|
47
|
+
end
|
|
43
48
|
end
|
|
44
49
|
end
|
|
45
50
|
end
|
|
@@ -65,18 +70,22 @@ module Cccux
|
|
|
65
70
|
def destroy
|
|
66
71
|
respond_to do |format|
|
|
67
72
|
if @role.users.any?
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
73
|
+
if defined?(Turbo::StreamsChannel)
|
|
74
|
+
format.turbo_stream do
|
|
75
|
+
render turbo_stream: turbo_stream.update("flash",
|
|
76
|
+
partial: "flash", locals: { alert: "Cannot delete role that has users assigned to it." })
|
|
77
|
+
end
|
|
71
78
|
end
|
|
72
79
|
format.html { redirect_to cccux.roles_path, alert: 'Cannot delete role that has users assigned to it.' }
|
|
73
80
|
else
|
|
74
81
|
@role.destroy
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
turbo_stream
|
|
78
|
-
|
|
79
|
-
|
|
82
|
+
if defined?(Turbo::StreamsChannel)
|
|
83
|
+
format.turbo_stream do
|
|
84
|
+
render turbo_stream: [
|
|
85
|
+
turbo_stream.remove("role_#{@role.id}"),
|
|
86
|
+
turbo_stream.update("flash", partial: "flash", locals: { notice: "Role was successfully deleted." })
|
|
87
|
+
]
|
|
88
|
+
end
|
|
80
89
|
end
|
|
81
90
|
format.html { redirect_to cccux.roles_path, notice: 'Role was successfully deleted.' }
|
|
82
91
|
end
|
|
@@ -126,9 +135,10 @@ module Cccux
|
|
|
126
135
|
|
|
127
136
|
private
|
|
128
137
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
138
|
+
# Remove set_role method - load_and_authorize_resource handles this
|
|
139
|
+
# def set_role
|
|
140
|
+
# @role = Cccux::Role.find(params[:id])
|
|
141
|
+
# end
|
|
132
142
|
|
|
133
143
|
def build_permission_matrix
|
|
134
144
|
subjects = Cccux::AbilityPermission.distinct.pluck(:subject).sort
|
|
@@ -200,15 +210,7 @@ module Cccux
|
|
|
200
210
|
conditions["user_key"] = ownership_user_key[permission.id.to_s]
|
|
201
211
|
end
|
|
202
212
|
|
|
203
|
-
if conditions.any?
|
|
204
|
-
role_ability.ownership_conditions = conditions.to_json
|
|
205
|
-
else
|
|
206
|
-
role_ability.ownership_conditions = nil
|
|
207
|
-
end
|
|
208
|
-
else
|
|
209
|
-
# Clear ownership configuration for non-owned access types
|
|
210
|
-
role_ability.ownership_source = nil
|
|
211
|
-
role_ability.ownership_conditions = nil
|
|
213
|
+
role_ability.ownership_conditions = conditions.to_json if conditions.any?
|
|
212
214
|
end
|
|
213
215
|
|
|
214
216
|
role_ability.save!
|
|
@@ -218,73 +220,60 @@ module Cccux
|
|
|
218
220
|
def role_params
|
|
219
221
|
params.require(:role).permit(:name, :description, :active, :priority)
|
|
220
222
|
end
|
|
221
|
-
|
|
223
|
+
|
|
222
224
|
def discover_application_models
|
|
223
225
|
models = []
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
!skip_model_by_name?(model_name)
|
|
241
|
-
models << model_name
|
|
242
|
-
end
|
|
243
|
-
else
|
|
244
|
-
# Model constant doesn't exist yet, but table does - likely a valid model
|
|
245
|
-
unless skip_model_by_name?(model_name)
|
|
246
|
-
models << model_name
|
|
247
|
-
end
|
|
248
|
-
end
|
|
249
|
-
rescue => e
|
|
250
|
-
# Ignore
|
|
251
|
-
end
|
|
252
|
-
end
|
|
253
|
-
# Always include CCCUX engine models for management (but not User since host app owns it)
|
|
254
|
-
cccux_models = %w[Cccux::Role Cccux::AbilityPermission Cccux::UserRole Cccux::RoleAbility]
|
|
255
|
-
models += cccux_models
|
|
256
|
-
rescue => e
|
|
257
|
-
Rails.logger.warn "Error discovering models: #{e.message}"
|
|
258
|
-
Rails.logger.warn e.backtrace.join("\n")
|
|
226
|
+
|
|
227
|
+
# Eager load all models to ensure they're available
|
|
228
|
+
Rails.application.eager_load!
|
|
229
|
+
|
|
230
|
+
# Get all ActiveRecord models from the application
|
|
231
|
+
ActiveRecord::Base.descendants.each do |model|
|
|
232
|
+
model_name = model.name
|
|
233
|
+
|
|
234
|
+
# Skip if model should be excluded
|
|
235
|
+
next if skip_model_by_name?(model_name)
|
|
236
|
+
|
|
237
|
+
# Skip if table doesn't exist or should be excluded
|
|
238
|
+
table_name = model.table_name
|
|
239
|
+
next if table_name.blank? || skip_table?(table_name)
|
|
240
|
+
|
|
241
|
+
models << model_name
|
|
259
242
|
end
|
|
260
|
-
|
|
243
|
+
|
|
244
|
+
# Sort by name for consistency
|
|
245
|
+
models.sort
|
|
261
246
|
end
|
|
262
|
-
|
|
247
|
+
|
|
263
248
|
def skip_model_by_name?(model_name)
|
|
264
249
|
excluded_patterns = [
|
|
265
|
-
/^
|
|
266
|
-
/^
|
|
267
|
-
/^ActionText::/,
|
|
268
|
-
/^
|
|
269
|
-
/^
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
250
|
+
/^HABTM_/, # Has and belongs to many join tables
|
|
251
|
+
/^ActiveRecord::/, # ActiveRecord internal classes
|
|
252
|
+
/^ActionText::/, # ActionText models
|
|
253
|
+
/^ActiveStorage::/, # ActiveStorage models
|
|
254
|
+
/^ActionMailbox::/, # ActionMailbox models
|
|
255
|
+
/^Cccux::/, # CCCUX engine models (we'll handle these separately)
|
|
256
|
+
/^ApplicationRecord$/, # Base application record
|
|
257
|
+
/^ApplicationController$/, # Controllers
|
|
258
|
+
/^ApplicationHelper$/, # Helpers
|
|
259
|
+
/^ApplicationMailer$/ # Mailers
|
|
273
260
|
]
|
|
261
|
+
|
|
274
262
|
excluded_patterns.any? { |pattern| model_name.match?(pattern) }
|
|
275
263
|
end
|
|
276
|
-
|
|
264
|
+
|
|
277
265
|
def skip_table?(table_name)
|
|
278
266
|
excluded_tables = [
|
|
267
|
+
'schema_migrations',
|
|
268
|
+
'ar_internal_metadata',
|
|
279
269
|
'active_storage_blobs',
|
|
280
270
|
'active_storage_attachments',
|
|
281
|
-
'active_storage_variant_records',
|
|
282
271
|
'action_text_rich_texts',
|
|
283
272
|
'action_mailbox_inbound_emails',
|
|
284
|
-
'
|
|
273
|
+
'action_mailbox_routing_rules'
|
|
285
274
|
]
|
|
286
|
-
|
|
287
|
-
table_name.
|
|
275
|
+
|
|
276
|
+
excluded_tables.include?(table_name) || table_name.start_with?('active_storage_') || table_name.start_with?('action_text_')
|
|
288
277
|
end
|
|
289
278
|
end
|
|
290
279
|
end
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
class Cccux::UsersController < Cccux::CccuxController
|
|
2
|
-
#
|
|
3
|
-
|
|
2
|
+
# Restore load_and_authorize_resource for User
|
|
3
|
+
load_and_authorize_resource class: User
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
# Add simple authentication check - user must be signed in
|
|
6
|
+
before_action :require_authentication
|
|
7
|
+
|
|
8
|
+
# Remove manual set_user - let load_and_authorize_resource handle it
|
|
9
|
+
# before_action :set_user, only: [:show, :edit, :update, :destroy]
|
|
6
10
|
|
|
7
11
|
def index
|
|
8
|
-
@users
|
|
12
|
+
# Do not override @users, let load_and_authorize_resource scope it
|
|
9
13
|
@roles = Cccux::Role.active.order(:name)
|
|
10
14
|
end
|
|
11
15
|
|
|
@@ -102,11 +106,22 @@ class Cccux::UsersController < Cccux::CccuxController
|
|
|
102
106
|
|
|
103
107
|
private
|
|
104
108
|
|
|
105
|
-
def
|
|
106
|
-
|
|
109
|
+
def require_authentication
|
|
110
|
+
unless user_signed_in?
|
|
111
|
+
respond_to do |format|
|
|
112
|
+
format.html { render plain: "Access denied", status: :forbidden }
|
|
113
|
+
format.json { render json: { error: 'Access denied' }, status: :forbidden }
|
|
114
|
+
end
|
|
115
|
+
return
|
|
116
|
+
end
|
|
107
117
|
end
|
|
108
118
|
|
|
119
|
+
# Remove set_user method - load_and_authorize_resource handles this
|
|
120
|
+
# def set_user
|
|
121
|
+
# @user = User.find(params[:id])
|
|
122
|
+
# end
|
|
123
|
+
|
|
109
124
|
def user_params
|
|
110
|
-
params.require(:user).permit(:email, :password, :password_confirmation)
|
|
125
|
+
params.require(:user).permit(:email, :password, :password_confirmation, :first_name, :last_name)
|
|
111
126
|
end
|
|
112
127
|
end
|
|
@@ -13,12 +13,16 @@ module Cccux
|
|
|
13
13
|
|
|
14
14
|
# Handle CanCanCan authorization errors gracefully
|
|
15
15
|
rescue_from CanCan::AccessDenied do |exception|
|
|
16
|
-
|
|
16
|
+
if Rails.env.test?
|
|
17
|
+
render plain: "Access denied", status: :forbidden
|
|
18
|
+
else
|
|
19
|
+
redirect_to cccux.root_path, alert: 'Access denied.'
|
|
20
|
+
end
|
|
17
21
|
end
|
|
18
22
|
|
|
19
23
|
# Handle 404 errors gracefully
|
|
20
24
|
rescue_from ActiveRecord::RecordNotFound do |exception|
|
|
21
|
-
redirect_to root_path, alert: 'The requested resource was not found.'
|
|
25
|
+
redirect_to cccux.root_path, alert: 'The requested resource was not found.'
|
|
22
26
|
end
|
|
23
27
|
end
|
|
24
28
|
|
|
@@ -2,66 +2,72 @@ module Cccux
|
|
|
2
2
|
module AuthorizationHelper
|
|
3
3
|
# Link helpers for common actions
|
|
4
4
|
def link_if_can_index(subject, text, path, **opts)
|
|
5
|
-
link_to(text, path, **opts)
|
|
5
|
+
can?(:index, subject) ? link_to(text, path, **opts) : ""
|
|
6
6
|
end
|
|
7
7
|
|
|
8
8
|
def link_if_can_show(subject, text, path, **opts)
|
|
9
|
-
|
|
9
|
+
if can?(:show, subject)
|
|
10
|
+
link_to(text, path, **opts)
|
|
11
|
+
elsif opts.delete(:show_text)
|
|
12
|
+
text
|
|
13
|
+
else
|
|
14
|
+
""
|
|
15
|
+
end
|
|
10
16
|
end
|
|
11
17
|
|
|
12
18
|
def link_if_can_create(subject, text, path, **opts)
|
|
13
|
-
link_to(text, path, **opts)
|
|
19
|
+
can?(:create, subject) ? link_to(text, path, **opts) : ""
|
|
14
20
|
end
|
|
15
21
|
|
|
16
22
|
def link_if_can_edit(subject, text, path, **opts)
|
|
17
|
-
link_to(text, path, **opts)
|
|
23
|
+
can?(:edit, subject) ? link_to(text, path, **opts) : ""
|
|
18
24
|
end
|
|
19
25
|
|
|
20
26
|
def link_if_can_update(subject, text, path, **opts)
|
|
21
|
-
link_to(text, path, **opts)
|
|
27
|
+
can?(:update, subject) ? link_to(text, path, **opts) : ""
|
|
22
28
|
end
|
|
23
29
|
|
|
24
30
|
def link_if_can_destroy(subject, text, path, **opts)
|
|
25
|
-
link_to(text, path, **opts)
|
|
31
|
+
can?(:destroy, subject) ? link_to(text, path, **opts) : ""
|
|
26
32
|
end
|
|
27
33
|
|
|
28
34
|
# Button helpers for common actions
|
|
29
35
|
def button_if_can_index(subject, text, path, **opts)
|
|
30
|
-
button_to(text, path, **opts)
|
|
36
|
+
can?(:index, subject) ? button_to(text, path, **opts) : ""
|
|
31
37
|
end
|
|
32
38
|
|
|
33
39
|
def button_if_can_show(subject, text, path, **opts)
|
|
34
|
-
button_to(text, path, **opts)
|
|
40
|
+
can?(:show, subject) ? button_to(text, path, **opts) : ""
|
|
35
41
|
end
|
|
36
42
|
|
|
37
43
|
def button_if_can_create(subject, text, path, **opts)
|
|
38
|
-
button_to(text, path, **opts)
|
|
44
|
+
can?(:create, subject) ? button_to(text, path, **opts) : ""
|
|
39
45
|
end
|
|
40
46
|
|
|
41
47
|
def button_if_can_edit(subject, text, path, **opts)
|
|
42
|
-
button_to(text, path, **opts)
|
|
48
|
+
can?(:edit, subject) ? button_to(text, path, **opts) : ""
|
|
43
49
|
end
|
|
44
50
|
|
|
45
51
|
def button_if_can_update(subject, text, path, **opts)
|
|
46
|
-
button_to(text, path, **opts)
|
|
52
|
+
can?(:update, subject) ? button_to(text, path, **opts) : ""
|
|
47
53
|
end
|
|
48
54
|
|
|
49
55
|
def button_if_can_destroy(subject, text, path, **opts)
|
|
50
|
-
button_to(text, path, **opts)
|
|
56
|
+
can?(:destroy, subject) ? button_to(text, path, **opts) : ""
|
|
51
57
|
end
|
|
52
58
|
|
|
53
59
|
# Generic action helpers
|
|
54
60
|
def link_if_can(action, subject, text, path, **opts)
|
|
55
|
-
link_to(text, path, **opts)
|
|
61
|
+
can?(action, subject) ? link_to(text, path, **opts) : ""
|
|
56
62
|
end
|
|
57
63
|
|
|
58
64
|
def button_if_can(action, subject, text, path, **opts)
|
|
59
|
-
button_to(text, path, **opts)
|
|
65
|
+
can?(action, subject) ? button_to(text, path, **opts) : ""
|
|
60
66
|
end
|
|
61
67
|
|
|
62
68
|
# Content helpers for conditional rendering
|
|
63
69
|
def content_if_can(action, subject, &block)
|
|
64
|
-
|
|
70
|
+
can?(action, subject) ? capture(&block) : ""
|
|
65
71
|
end
|
|
66
72
|
|
|
67
73
|
def content_if_can_index(subject, &block)
|
|
@@ -90,15 +96,11 @@ module Cccux
|
|
|
90
96
|
|
|
91
97
|
# Icon helpers (useful for action buttons)
|
|
92
98
|
def icon_link_if_can(action, subject, icon_class, text, path, **opts)
|
|
93
|
-
link_to(path, **opts)
|
|
94
|
-
content_tag(:i, '', class: icon_class) + ' ' + text
|
|
95
|
-
end if can?(action, subject)
|
|
99
|
+
can?(action, subject) ? link_to(path, **opts) { content_tag(:i, '', class: icon_class) + ' ' + text } : ""
|
|
96
100
|
end
|
|
97
101
|
|
|
98
102
|
def icon_button_if_can(action, subject, icon_class, text, path, **opts)
|
|
99
|
-
button_to(path, **opts)
|
|
100
|
-
content_tag(:i, '', class: icon_class) + ' ' + text
|
|
101
|
-
end if can?(action, subject)
|
|
103
|
+
can?(action, subject) ? button_to(path, **opts) { content_tag(:i, '', class: icon_class) + ' ' + text } : ""
|
|
102
104
|
end
|
|
103
105
|
|
|
104
106
|
# Common action button helpers with icons
|