cccux 0.1.0
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/CHANGELOG.md +67 -0
- data/MIT-LICENSE +20 -0
- data/README.md +382 -0
- data/Rakefile +8 -0
- data/app/assets/config/cccux_manifest.js +1 -0
- data/app/assets/stylesheets/cccux/application.css +102 -0
- data/app/controllers/cccux/ability_permissions_controller.rb +271 -0
- data/app/controllers/cccux/application_controller.rb +37 -0
- data/app/controllers/cccux/authorization_controller.rb +10 -0
- data/app/controllers/cccux/cccux_controller.rb +64 -0
- data/app/controllers/cccux/dashboard_controller.rb +172 -0
- data/app/controllers/cccux/home_controller.rb +19 -0
- data/app/controllers/cccux/roles_controller.rb +290 -0
- data/app/controllers/cccux/simple_controller.rb +7 -0
- data/app/controllers/cccux/users_controller.rb +112 -0
- data/app/controllers/concerns/cccux/application_controller_concern.rb +32 -0
- data/app/helpers/cccux/application_helper.rb +4 -0
- data/app/helpers/cccux/authorization_helper.rb +228 -0
- data/app/jobs/cccux/application_job.rb +4 -0
- data/app/mailers/cccux/application_mailer.rb +6 -0
- data/app/models/cccux/ability.rb +142 -0
- data/app/models/cccux/ability_permission.rb +61 -0
- data/app/models/cccux/application_record.rb +5 -0
- data/app/models/cccux/role.rb +90 -0
- data/app/models/cccux/role_ability.rb +49 -0
- data/app/models/cccux/user_role.rb +42 -0
- data/app/models/concerns/cccux/authorizable.rb +25 -0
- data/app/models/concerns/cccux/scoped_ownership.rb +183 -0
- data/app/models/concerns/cccux/user_concern.rb +87 -0
- data/app/views/cccux/ability_permissions/edit.html.erb +58 -0
- data/app/views/cccux/ability_permissions/index.html.erb +108 -0
- data/app/views/cccux/ability_permissions/new.html.erb +308 -0
- data/app/views/cccux/dashboard/index.html.erb +69 -0
- data/app/views/cccux/dashboard/model_discovery.html.erb +148 -0
- data/app/views/cccux/home/index.html.erb +42 -0
- data/app/views/cccux/roles/_flash.html.erb +10 -0
- data/app/views/cccux/roles/_form.html.erb +78 -0
- data/app/views/cccux/roles/_role.html.erb +67 -0
- data/app/views/cccux/roles/edit.html.erb +317 -0
- data/app/views/cccux/roles/index.html.erb +51 -0
- data/app/views/cccux/roles/new.html.erb +3 -0
- data/app/views/cccux/roles/show.html.erb +99 -0
- data/app/views/cccux/users/edit.html.erb +117 -0
- data/app/views/cccux/users/index.html.erb +99 -0
- data/app/views/cccux/users/new.html.erb +94 -0
- data/app/views/cccux/users/show.html.erb +138 -0
- data/app/views/layouts/cccux/admin.html.erb +168 -0
- data/app/views/layouts/cccux/application.html.erb +17 -0
- data/app/views/shared/_footer.html.erb +101 -0
- data/config/routes.rb +63 -0
- data/db/migrate/20250626194001_create_cccux_roles.rb +15 -0
- data/db/migrate/20250626194007_create_cccux_ability_permissions.rb +18 -0
- data/db/migrate/20250626194011_create_cccux_user_roles.rb +13 -0
- data/db/migrate/20250626194016_create_cccux_role_abilities.rb +10 -0
- data/db/migrate/20250627170611_add_owned_to_cccux_role_abilities.rb +9 -0
- data/db/migrate/20250705193709_add_context_to_cccux_role_abilities.rb +9 -0
- data/db/migrate/20250706214415_add_ownership_configuration_to_role_abilities.rb +21 -0
- data/db/seeds.rb +136 -0
- data/lib/cccux/engine.rb +50 -0
- data/lib/cccux/version.rb +3 -0
- data/lib/cccux.rb +7 -0
- data/lib/tasks/cccux.rake +703 -0
- data/lib/tasks/view_helpers.rake +274 -0
- metadata +188 -0
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
module Cccux
|
|
2
|
+
class AbilityPermissionsController < CccuxController
|
|
3
|
+
# Ensure only Role Managers can access permission management
|
|
4
|
+
before_action :ensure_role_manager
|
|
5
|
+
|
|
6
|
+
before_action :set_ability_permission, only: [:show, :edit, :update, :destroy]
|
|
7
|
+
|
|
8
|
+
def index
|
|
9
|
+
@ability_permissions = Cccux::AbilityPermission.all.order(:subject, :action)
|
|
10
|
+
@grouped_permissions = @ability_permissions.group_by(&:subject)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def show
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def new
|
|
17
|
+
@ability_permission = Cccux::AbilityPermission.new
|
|
18
|
+
@ability_permission.subject = params[:subject] if params[:subject].present?
|
|
19
|
+
@available_subjects = get_available_subjects
|
|
20
|
+
@available_actions = get_available_actions
|
|
21
|
+
@subject_actions_map = get_subject_actions_map
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def create
|
|
25
|
+
# Handle bulk creation if actions is an array
|
|
26
|
+
if params[:ability_permission][:actions].is_a?(Array)
|
|
27
|
+
create_bulk_permissions
|
|
28
|
+
else
|
|
29
|
+
create_single_permission
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def edit
|
|
34
|
+
@available_subjects = get_available_subjects
|
|
35
|
+
@available_actions = get_available_actions
|
|
36
|
+
@subject_actions_map = get_subject_actions_map
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def update
|
|
40
|
+
if @ability_permission.update(ability_permission_params)
|
|
41
|
+
redirect_to cccux.ability_permissions_path, notice: 'Permission was successfully updated.'
|
|
42
|
+
else
|
|
43
|
+
@available_subjects = get_available_subjects
|
|
44
|
+
@available_actions = get_available_actions
|
|
45
|
+
@subject_actions_map = get_subject_actions_map
|
|
46
|
+
render :edit
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def destroy
|
|
51
|
+
@ability_permission.destroy
|
|
52
|
+
redirect_to cccux.ability_permissions_path, notice: 'Permission was successfully deleted.'
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def actions_for_subject
|
|
56
|
+
subject = params[:subject]
|
|
57
|
+
actions = get_actions_for_subject(subject)
|
|
58
|
+
render json: { actions: actions }
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
private
|
|
62
|
+
|
|
63
|
+
def set_ability_permission
|
|
64
|
+
@ability_permission = Cccux::AbilityPermission.find(params[:id])
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def ability_permission_params
|
|
68
|
+
params.require(:ability_permission).permit(:subject, :action, :description, :active)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def bulk_permission_params
|
|
72
|
+
params.require(:ability_permission).permit(:subject, :description, :active, actions: [])
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def create_bulk_permissions
|
|
76
|
+
subject = params[:ability_permission][:subject]
|
|
77
|
+
actions = params[:ability_permission][:actions].reject(&:blank?)
|
|
78
|
+
description_template = params[:ability_permission][:description]
|
|
79
|
+
active = params[:ability_permission][:active]
|
|
80
|
+
|
|
81
|
+
created_permissions = []
|
|
82
|
+
failed_permissions = []
|
|
83
|
+
|
|
84
|
+
actions.each do |action|
|
|
85
|
+
permission = Cccux::AbilityPermission.new(
|
|
86
|
+
subject: subject,
|
|
87
|
+
action: action,
|
|
88
|
+
description: description_template.present? ? "#{action.capitalize} #{subject.pluralize.downcase}" : "",
|
|
89
|
+
active: active
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
if permission.save
|
|
93
|
+
created_permissions << permission
|
|
94
|
+
else
|
|
95
|
+
failed_permissions << { action: action, errors: permission.errors.full_messages }
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
if failed_permissions.empty?
|
|
100
|
+
redirect_to cccux.ability_permissions_path,
|
|
101
|
+
notice: "Successfully created #{created_permissions.count} permissions for #{subject}!"
|
|
102
|
+
else
|
|
103
|
+
@ability_permission = Cccux::AbilityPermission.new(subject: subject)
|
|
104
|
+
@available_subjects = get_available_subjects
|
|
105
|
+
@available_actions = get_available_actions
|
|
106
|
+
@subject_actions_map = get_subject_actions_map
|
|
107
|
+
flash[:alert] = "Some permissions could not be created: #{failed_permissions.map { |f| f[:action] }.join(', ')}"
|
|
108
|
+
render :new
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def create_single_permission
|
|
113
|
+
@ability_permission = Cccux::AbilityPermission.new(ability_permission_params)
|
|
114
|
+
|
|
115
|
+
if @ability_permission.save
|
|
116
|
+
redirect_to cccux.ability_permissions_path, notice: 'Permission was successfully created.'
|
|
117
|
+
else
|
|
118
|
+
@available_subjects = get_available_subjects
|
|
119
|
+
@available_actions = get_available_actions
|
|
120
|
+
@subject_actions_map = get_subject_actions_map
|
|
121
|
+
render :new
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def get_available_subjects
|
|
126
|
+
# Get existing subjects plus discovered models
|
|
127
|
+
existing_subjects = Cccux::AbilityPermission.distinct.pluck(:subject).compact
|
|
128
|
+
discovered_models = discover_application_models
|
|
129
|
+
(existing_subjects + discovered_models).uniq.sort
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def get_available_actions
|
|
133
|
+
# Get existing actions plus common CRUD actions
|
|
134
|
+
existing_actions = Cccux::AbilityPermission.distinct.pluck(:action).compact
|
|
135
|
+
common_actions = %w[read create update destroy manage index show new edit]
|
|
136
|
+
(existing_actions + common_actions).uniq.sort
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def get_subject_actions_map
|
|
140
|
+
subjects = get_available_subjects
|
|
141
|
+
map = {}
|
|
142
|
+
|
|
143
|
+
subjects.each do |subject|
|
|
144
|
+
map[subject] = get_actions_for_subject(subject)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
map
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def get_actions_for_subject(subject)
|
|
151
|
+
return [] if subject.blank?
|
|
152
|
+
|
|
153
|
+
# Start with common CRUD actions
|
|
154
|
+
actions = %w[read create update destroy]
|
|
155
|
+
|
|
156
|
+
# Add route-discovered actions
|
|
157
|
+
route_actions = discover_actions_for_model(subject)
|
|
158
|
+
actions += route_actions
|
|
159
|
+
|
|
160
|
+
# Add existing actions for this subject
|
|
161
|
+
existing_actions = Cccux::AbilityPermission.where(subject: subject).distinct.pluck(:action).compact
|
|
162
|
+
actions += existing_actions
|
|
163
|
+
|
|
164
|
+
actions.uniq.sort
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def discover_application_models
|
|
168
|
+
models = []
|
|
169
|
+
|
|
170
|
+
begin
|
|
171
|
+
Rails.application.eager_load!
|
|
172
|
+
|
|
173
|
+
# Get all ApplicationRecord descendants from the host app
|
|
174
|
+
ApplicationRecord.descendants.each do |model_class|
|
|
175
|
+
next if model_class.abstract_class?
|
|
176
|
+
next if model_class.name.nil?
|
|
177
|
+
next if skip_model?(model_class)
|
|
178
|
+
|
|
179
|
+
models << model_class.name
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# Also add CCCUX engine models (so they can be managed through permissions)
|
|
183
|
+
cccux_models = %w[Cccux::Role Cccux::AbilityPermission Cccux::UserRole Cccux::RoleAbility]
|
|
184
|
+
models += cccux_models
|
|
185
|
+
|
|
186
|
+
rescue => e
|
|
187
|
+
Rails.logger.warn "Error discovering models: #{e.message}"
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
models.uniq.sort
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def discover_actions_for_model(subject)
|
|
194
|
+
actions = []
|
|
195
|
+
|
|
196
|
+
begin
|
|
197
|
+
# Convert subject to potential route patterns
|
|
198
|
+
resource_name = subject.underscore.pluralize
|
|
199
|
+
|
|
200
|
+
# For CCCUX models, also check the engine routes
|
|
201
|
+
if subject.start_with?('Cccux::')
|
|
202
|
+
cccux_resource_name = subject.gsub('Cccux::', '').underscore.pluralize
|
|
203
|
+
|
|
204
|
+
# Look through CCCUX engine routes for this resource
|
|
205
|
+
Cccux::Engine.routes.routes.each do |route|
|
|
206
|
+
next unless route.path.spec.to_s.include?(cccux_resource_name)
|
|
207
|
+
|
|
208
|
+
# Extract action from route
|
|
209
|
+
if route.defaults[:action]
|
|
210
|
+
action = route.defaults[:action]
|
|
211
|
+
# Map HTTP verbs to standard actions and include custom actions
|
|
212
|
+
case action
|
|
213
|
+
when 'index' then actions << 'read'
|
|
214
|
+
when 'show' then actions << 'read'
|
|
215
|
+
when 'create' then actions << 'create'
|
|
216
|
+
when 'update' then actions << 'update'
|
|
217
|
+
when 'destroy' then actions << 'destroy'
|
|
218
|
+
when 'edit', 'new' then next # Skip these as they're UI actions
|
|
219
|
+
else
|
|
220
|
+
# Custom actions like 'reorder', 'toggle_active', etc.
|
|
221
|
+
actions << action
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
# Also look through main Rails routes for this resource
|
|
228
|
+
Rails.application.routes.routes.each do |route|
|
|
229
|
+
next unless route.path.spec.to_s.include?(resource_name)
|
|
230
|
+
|
|
231
|
+
# Extract action from route
|
|
232
|
+
if route.defaults[:action]
|
|
233
|
+
action = route.defaults[:action]
|
|
234
|
+
# Map HTTP verbs to standard actions and include custom actions
|
|
235
|
+
case action
|
|
236
|
+
when 'index' then actions << 'read'
|
|
237
|
+
when 'show' then actions << 'read'
|
|
238
|
+
when 'create' then actions << 'create'
|
|
239
|
+
when 'update' then actions << 'update'
|
|
240
|
+
when 'destroy' then actions << 'destroy'
|
|
241
|
+
when 'edit', 'new' then next # Skip these as they're UI actions
|
|
242
|
+
else
|
|
243
|
+
# Custom actions
|
|
244
|
+
actions << action
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
rescue => e
|
|
250
|
+
Rails.logger.warn "Error discovering actions for #{subject}: #{e.message}"
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
actions.uniq
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
def skip_model?(model_class)
|
|
257
|
+
excluded_patterns = [
|
|
258
|
+
/^ActiveRecord::/,
|
|
259
|
+
/^ActiveStorage::/,
|
|
260
|
+
/^ActionText::/,
|
|
261
|
+
/^ActionMailbox::/,
|
|
262
|
+
/^ApplicationRecord$/,
|
|
263
|
+
/Version$/,
|
|
264
|
+
/Schema/,
|
|
265
|
+
/Migration/
|
|
266
|
+
]
|
|
267
|
+
|
|
268
|
+
excluded_patterns.any? { |pattern| model_class.name.match?(pattern) }
|
|
269
|
+
end
|
|
270
|
+
end
|
|
271
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
module Cccux
|
|
2
|
+
class ApplicationController < ActionController::Base
|
|
3
|
+
# CanCanCan authorization - shared across all CCCUX controllers
|
|
4
|
+
include CanCan::ControllerAdditions
|
|
5
|
+
|
|
6
|
+
# Handle CanCan authorization errors gracefully
|
|
7
|
+
rescue_from CanCan::AccessDenied do |exception|
|
|
8
|
+
redirect_to main_app.root_path, alert: 'Access denied.'
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# Handle 404 errors gracefully
|
|
12
|
+
rescue_from ActiveRecord::RecordNotFound do |exception|
|
|
13
|
+
redirect_to main_app.root_path, alert: 'The requested resource was not found.'
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
before_action :configure_permitted_parameters, if: :devise_controller?
|
|
17
|
+
before_action :set_current_user
|
|
18
|
+
|
|
19
|
+
protected
|
|
20
|
+
|
|
21
|
+
# Override current_ability to use CCCUX Ability class
|
|
22
|
+
def current_ability
|
|
23
|
+
@current_ability ||= Cccux::Ability.new(current_user)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def set_current_user
|
|
27
|
+
@current_user = current_user if defined?(current_user)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def configure_permitted_parameters
|
|
33
|
+
devise_parameter_sanitizer.permit(:sign_up, keys: [:first_name, :last_name])
|
|
34
|
+
devise_parameter_sanitizer.permit(:account_update, keys: [:first_name, :last_name])
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
module Cccux
|
|
2
|
+
class AuthorizationController < ApplicationController
|
|
3
|
+
# Provide CCCUX authorization without forcing a specific layout
|
|
4
|
+
# This allows host app controllers to use their own layouts
|
|
5
|
+
layout 'application'
|
|
6
|
+
|
|
7
|
+
# Automatically load and authorize resources for all actions
|
|
8
|
+
load_and_authorize_resource
|
|
9
|
+
end
|
|
10
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
module Cccux
|
|
2
|
+
class CccuxController < ApplicationController
|
|
3
|
+
layout 'cccux/admin'
|
|
4
|
+
|
|
5
|
+
# Override the default error message for admin interface
|
|
6
|
+
rescue_from CanCan::AccessDenied do |exception|
|
|
7
|
+
redirect_to main_app.root_path, alert: 'Access denied. Only Role Managers can access the admin interface.'
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
rescue_from ActionController::RoutingError do |exception|
|
|
11
|
+
redirect_to main_app.root_path, alert: 'The requested page was not found.'
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Handle parameter errors (like invalid IDs)
|
|
15
|
+
rescue_from ActionController::ParameterMissing do |exception|
|
|
16
|
+
redirect_to main_app.root_path, alert: 'Invalid request parameters.'
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Automatically load and authorize resources for all actions
|
|
20
|
+
# This works because CCCUX provides default roles (Guest, Basic User)
|
|
21
|
+
# so every user has permissions to check against
|
|
22
|
+
load_and_authorize_resource
|
|
23
|
+
|
|
24
|
+
protected
|
|
25
|
+
|
|
26
|
+
# Override resource_class to handle namespaced models
|
|
27
|
+
def resource_class
|
|
28
|
+
# For CCCUX controllers, use the namespaced model
|
|
29
|
+
if self.class.name.start_with?('Cccux::')
|
|
30
|
+
# Extract the model name from controller name (e.g., RolesController -> Cccux::Role)
|
|
31
|
+
model_name = self.class.name.gsub('Cccux::', '').gsub('Controller', '').singularize
|
|
32
|
+
"Cccux::#{model_name}".constantize
|
|
33
|
+
else
|
|
34
|
+
# For host app controllers, use the default behavior
|
|
35
|
+
super
|
|
36
|
+
end
|
|
37
|
+
rescue NameError
|
|
38
|
+
# Fallback to default behavior if constantization fails
|
|
39
|
+
super
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
def ensure_role_manager
|
|
45
|
+
# Check if user is authenticated
|
|
46
|
+
unless defined?(current_user) && current_user&.persisted?
|
|
47
|
+
respond_to do |format|
|
|
48
|
+
format.html { redirect_to main_app.root_path, alert: 'You must be logged in to access the admin interface.' }
|
|
49
|
+
format.json { render json: { success: false, error: 'You must be logged in to access the admin interface.' }, status: :unauthorized }
|
|
50
|
+
end
|
|
51
|
+
return
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Check if user has Role Manager role
|
|
55
|
+
unless current_user.has_role?('Role Manager')
|
|
56
|
+
respond_to do |format|
|
|
57
|
+
format.html { redirect_to main_app.root_path, alert: 'Access denied. Only Role Managers can access the admin interface.' }
|
|
58
|
+
format.json { render json: { success: false, error: 'Access denied. Only Role Managers can access the admin interface.' }, status: :forbidden }
|
|
59
|
+
end
|
|
60
|
+
return
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
module Cccux
|
|
2
|
+
class DashboardController < CccuxController
|
|
3
|
+
# Skip CanCanCan resource loading for dashboard since it doesn't work with a specific model
|
|
4
|
+
skip_load_and_authorize_resource
|
|
5
|
+
|
|
6
|
+
# Ensure only Role Managers can access the dashboard
|
|
7
|
+
before_action :ensure_role_manager
|
|
8
|
+
|
|
9
|
+
def index
|
|
10
|
+
@user_count = User.count
|
|
11
|
+
@role_count = Cccux::Role.count
|
|
12
|
+
@permission_count = Cccux::AbilityPermission.count
|
|
13
|
+
@total_assignments = Cccux::UserRole.count + (Cccux::Role.joins(:ability_permissions).count)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def model_discovery
|
|
17
|
+
@detected_models = detect_application_models
|
|
18
|
+
@existing_models = get_models_with_permissions
|
|
19
|
+
@missing_models = @detected_models - @existing_models
|
|
20
|
+
@actions = %w[read create update destroy]
|
|
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
|
+
end
|
|
33
|
+
|
|
34
|
+
def sync_permissions
|
|
35
|
+
models_to_add = params[:models] || []
|
|
36
|
+
actions = %w[read create update destroy]
|
|
37
|
+
|
|
38
|
+
added_permissions = []
|
|
39
|
+
|
|
40
|
+
models_to_add.each do |model_name|
|
|
41
|
+
next if model_name.blank?
|
|
42
|
+
|
|
43
|
+
actions.each do |action|
|
|
44
|
+
permission = Cccux::AbilityPermission.find_or_create_by(
|
|
45
|
+
action: action,
|
|
46
|
+
subject: model_name
|
|
47
|
+
) do |p|
|
|
48
|
+
p.description = "#{action.capitalize} #{model_name.pluralize.downcase}"
|
|
49
|
+
p.active = true
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
if permission.persisted? && !permission.previously_persisted?
|
|
53
|
+
added_permissions << permission
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
if added_permissions.any?
|
|
59
|
+
redirect_to cccux.model_discovery_path,
|
|
60
|
+
notice: "Successfully added #{added_permissions.count} permissions for #{models_to_add.count} models!"
|
|
61
|
+
else
|
|
62
|
+
redirect_to cccux.model_discovery_path,
|
|
63
|
+
alert: "No new permissions were added. Models may already have permissions."
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Handle any unmatched routes in CCCUX - redirect to home
|
|
68
|
+
def not_found
|
|
69
|
+
redirect_to main_app.root_path, alert: 'The requested page was not found.'
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
private
|
|
73
|
+
|
|
74
|
+
def detect_application_models
|
|
75
|
+
models = []
|
|
76
|
+
|
|
77
|
+
begin
|
|
78
|
+
# Direct approach: Get models from database tables (bypasses all autoloading issues)
|
|
79
|
+
Rails.logger.info "Detecting models from database tables..."
|
|
80
|
+
|
|
81
|
+
application_tables = ActiveRecord::Base.connection.tables.reject do |table|
|
|
82
|
+
# Skip Rails internal tables and CCCUX tables
|
|
83
|
+
table.start_with?('schema_migrations', 'ar_internal_metadata', 'cccux_') ||
|
|
84
|
+
skip_table?(table)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
Rails.logger.info "Found application tables: #{application_tables}"
|
|
88
|
+
|
|
89
|
+
application_tables.each do |table|
|
|
90
|
+
# Convert table name to model name
|
|
91
|
+
model_name = table.singularize.camelize
|
|
92
|
+
|
|
93
|
+
# Verify the model exists and is valid
|
|
94
|
+
begin
|
|
95
|
+
if Object.const_defined?(model_name)
|
|
96
|
+
model_class = Object.const_get(model_name)
|
|
97
|
+
if model_class.respond_to?(:table_name) &&
|
|
98
|
+
model_class.table_name == table &&
|
|
99
|
+
!skip_model_by_name?(model_name)
|
|
100
|
+
models << model_name
|
|
101
|
+
Rails.logger.info "✅ Found model: #{model_name} (table: #{table})"
|
|
102
|
+
end
|
|
103
|
+
else
|
|
104
|
+
# Model constant doesn't exist yet, but table does - likely a valid model
|
|
105
|
+
unless skip_model_by_name?(model_name)
|
|
106
|
+
models << model_name
|
|
107
|
+
Rails.logger.info "✅ Found model from table: #{model_name} (table: #{table})"
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
rescue => e
|
|
111
|
+
Rails.logger.debug "Skipped table #{table}: #{e.message}"
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
rescue => e
|
|
116
|
+
Rails.logger.error "Error detecting models from database: #{e.message}"
|
|
117
|
+
Rails.logger.error e.backtrace.join("\n")
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Always include CCCUX engine models for management (but not User since host app owns it)
|
|
121
|
+
cccux_models = %w[Cccux::Role Cccux::AbilityPermission Cccux::UserRole Cccux::RoleAbility]
|
|
122
|
+
models += cccux_models
|
|
123
|
+
|
|
124
|
+
# Debug what we found
|
|
125
|
+
Rails.logger.info "Model detection summary:"
|
|
126
|
+
Rails.logger.info " Total models found: #{models.uniq.count}"
|
|
127
|
+
Rails.logger.info " Application models: #{models.reject { |m| m.start_with?('Cccux::') }.join(', ')}"
|
|
128
|
+
Rails.logger.info " CCCUX models: #{models.select { |m| m.start_with?('Cccux::') }.join(', ')}"
|
|
129
|
+
|
|
130
|
+
models.uniq.sort
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def get_models_with_permissions
|
|
134
|
+
Cccux::AbilityPermission.distinct.pluck(:subject).compact.sort
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def skip_model?(model_class)
|
|
138
|
+
skip_model_by_name?(model_class.name)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def skip_model_by_name?(model_name)
|
|
142
|
+
# Skip models that shouldn't have permissions
|
|
143
|
+
excluded_patterns = [
|
|
144
|
+
/^ActiveRecord::/,
|
|
145
|
+
/^ActiveStorage::/,
|
|
146
|
+
/^ActionText::/,
|
|
147
|
+
/^ActionMailbox::/,
|
|
148
|
+
/^ApplicationRecord$/,
|
|
149
|
+
/Version$/, # PaperTrail versions
|
|
150
|
+
/Schema/,
|
|
151
|
+
/Migration/
|
|
152
|
+
]
|
|
153
|
+
|
|
154
|
+
excluded_patterns.any? { |pattern| model_name.match?(pattern) }
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def skip_table?(table_name)
|
|
158
|
+
# Skip tables that are not meant to have corresponding models
|
|
159
|
+
excluded_tables = [
|
|
160
|
+
'active_storage_blobs',
|
|
161
|
+
'active_storage_attachments',
|
|
162
|
+
'active_storage_variant_records',
|
|
163
|
+
'action_text_rich_texts',
|
|
164
|
+
'action_mailbox_inbound_emails',
|
|
165
|
+
'versions' # PaperTrail
|
|
166
|
+
]
|
|
167
|
+
|
|
168
|
+
excluded_tables.include?(table_name) ||
|
|
169
|
+
table_name.end_with?('_versions') # PaperTrail version tables
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Cccux
|
|
4
|
+
class HomeController < ApplicationController
|
|
5
|
+
def index
|
|
6
|
+
# This controller can be used as a fallback root route
|
|
7
|
+
# It will redirect to the host app's root or show a simple welcome page
|
|
8
|
+
|
|
9
|
+
# Try to redirect to host app's root first
|
|
10
|
+
# if Rails.application.routes.recognize_path('/', method: :get) rescue false
|
|
11
|
+
# redirect_to '/'
|
|
12
|
+
# return
|
|
13
|
+
# end
|
|
14
|
+
|
|
15
|
+
# If no host app root, show a simple welcome page
|
|
16
|
+
render :index
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|