easy-admin-rails 0.1.15 → 0.2.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.
Files changed (92) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/builds/easy_admin.base.js +254 -18
  3. data/app/assets/builds/easy_admin.base.js.map +4 -4
  4. data/app/assets/builds/easy_admin.css +112 -18
  5. data/app/components/easy_admin/base_component.rb +1 -0
  6. data/app/components/easy_admin/form_tabs_component.rb +5 -2
  7. data/app/components/easy_admin/navbar_component.rb +5 -1
  8. data/app/components/easy_admin/permissions/user_role_assignment_component.rb +254 -0
  9. data/app/components/easy_admin/permissions/user_role_permissions_component.rb +186 -0
  10. data/app/components/easy_admin/resources/index_component.rb +1 -4
  11. data/app/components/easy_admin/sidebar_component.rb +67 -2
  12. data/app/controllers/easy_admin/application_controller.rb +131 -1
  13. data/app/controllers/easy_admin/batch_actions_controller.rb +27 -0
  14. data/app/controllers/easy_admin/concerns/belongs_to_editing.rb +201 -0
  15. data/app/controllers/easy_admin/concerns/inline_field_editing.rb +297 -0
  16. data/app/controllers/easy_admin/concerns/resource_authorization.rb +55 -0
  17. data/app/controllers/easy_admin/concerns/resource_filtering.rb +178 -0
  18. data/app/controllers/easy_admin/concerns/resource_loading.rb +149 -0
  19. data/app/controllers/easy_admin/concerns/resource_pagination.rb +135 -0
  20. data/app/controllers/easy_admin/dashboard_controller.rb +2 -1
  21. data/app/controllers/easy_admin/dashboards_controller.rb +6 -40
  22. data/app/controllers/easy_admin/resources_controller.rb +13 -762
  23. data/app/controllers/easy_admin/row_actions_controller.rb +25 -0
  24. data/app/helpers/easy_admin/fields_helper.rb +61 -9
  25. data/app/javascript/easy_admin/controllers/event_emitter_controller.js +2 -4
  26. data/app/javascript/easy_admin/controllers/infinite_scroll_controller.js +0 -10
  27. data/app/javascript/easy_admin/controllers/jsoneditor_controller.js +1 -4
  28. data/app/javascript/easy_admin/controllers/permission_toggle_controller.js +227 -0
  29. data/app/javascript/easy_admin/controllers/role_preview_controller.js +93 -0
  30. data/app/javascript/easy_admin/controllers/select_field_controller.js +1 -2
  31. data/app/javascript/easy_admin/controllers/settings_button_controller.js +1 -2
  32. data/app/javascript/easy_admin/controllers/settings_sidebar_controller.js +1 -4
  33. data/app/javascript/easy_admin/controllers/turbo_stream_redirect.js +0 -2
  34. data/app/javascript/easy_admin/controllers.js +5 -1
  35. data/app/models/easy_admin/admin_user.rb +6 -0
  36. data/app/policies/admin_user_policy.rb +36 -0
  37. data/app/policies/application_policy.rb +83 -0
  38. data/app/views/easy_admin/application/authorization_failure.turbo_stream.erb +8 -0
  39. data/app/views/easy_admin/dashboards/card.html.erb +5 -0
  40. data/app/views/easy_admin/dashboards/card.turbo_stream.erb +7 -0
  41. data/app/views/easy_admin/dashboards/card_error.html.erb +3 -0
  42. data/app/views/easy_admin/dashboards/card_error.turbo_stream.erb +5 -0
  43. data/app/views/easy_admin/dashboards/show.turbo_stream.erb +7 -0
  44. data/app/views/easy_admin/resources/belongs_to_edit_attached.html.erb +6 -0
  45. data/app/views/easy_admin/resources/belongs_to_edit_attached.turbo_stream.erb +8 -0
  46. data/app/views/easy_admin/resources/belongs_to_reattach.html.erb +5 -0
  47. data/app/views/easy_admin/resources/edit.html.erb +1 -1
  48. data/app/views/easy_admin/resources/edit_field.html.erb +5 -0
  49. data/app/views/easy_admin/resources/edit_field.turbo_stream.erb +7 -0
  50. data/app/views/easy_admin/resources/index.html.erb +1 -1
  51. data/app/views/easy_admin/resources/index_frame.html.erb +8 -142
  52. data/app/views/easy_admin/resources/update_belongs_to_attached.turbo_stream.erb +25 -0
  53. data/app/views/layouts/easy_admin/application.html.erb +15 -2
  54. data/config/initializers/easy_admin_permissions.rb +73 -0
  55. data/db/seeds/easy_admin_permissions.rb +121 -0
  56. data/lib/easy-admin-rails.rb +2 -0
  57. data/lib/easy_admin/permissions/component.rb +168 -0
  58. data/lib/easy_admin/permissions/configuration.rb +37 -0
  59. data/lib/easy_admin/permissions/controller.rb +164 -0
  60. data/lib/easy_admin/permissions/dsl.rb +180 -0
  61. data/lib/easy_admin/permissions/models.rb +44 -0
  62. data/lib/easy_admin/permissions/permission_denied_component.rb +121 -0
  63. data/lib/easy_admin/permissions/resource_permissions.rb +231 -0
  64. data/lib/easy_admin/permissions/role_definition.rb +45 -0
  65. data/lib/easy_admin/permissions/role_denied_component.rb +159 -0
  66. data/lib/easy_admin/permissions/role_dsl.rb +73 -0
  67. data/lib/easy_admin/permissions/user_extensions.rb +129 -0
  68. data/lib/easy_admin/permissions.rb +113 -0
  69. data/lib/easy_admin/resource/base.rb +119 -0
  70. data/lib/easy_admin/resource/configuration.rb +148 -0
  71. data/lib/easy_admin/resource/dsl.rb +117 -0
  72. data/lib/easy_admin/resource/field_registry.rb +189 -0
  73. data/lib/easy_admin/resource/form_builder.rb +123 -0
  74. data/lib/easy_admin/resource/layout_builder.rb +249 -0
  75. data/lib/easy_admin/resource/scope_manager.rb +252 -0
  76. data/lib/easy_admin/resource/show_builder.rb +359 -0
  77. data/lib/easy_admin/resource.rb +8 -835
  78. data/lib/easy_admin/resource_modules.rb +11 -0
  79. data/lib/easy_admin/version.rb +1 -1
  80. data/lib/generators/easy_admin/permissions/install_generator.rb +90 -0
  81. data/lib/generators/easy_admin/permissions/templates/initializers/permissions.rb +37 -0
  82. data/lib/generators/easy_admin/permissions/templates/migrations/create_permission_tables.rb +27 -0
  83. data/lib/generators/easy_admin/permissions/templates/migrations/update_users_for_permissions.rb +6 -0
  84. data/lib/generators/easy_admin/permissions/templates/models/permission.rb +9 -0
  85. data/lib/generators/easy_admin/permissions/templates/models/role.rb +9 -0
  86. data/lib/generators/easy_admin/permissions/templates/models/role_permission.rb +9 -0
  87. data/lib/generators/easy_admin/permissions/templates/models/user_role.rb +9 -0
  88. data/lib/generators/easy_admin/permissions/templates/policies/application_policy.rb +47 -0
  89. data/lib/generators/easy_admin/permissions/templates/policies/user_policy.rb +36 -0
  90. data/lib/generators/easy_admin/permissions/templates/seeds/permissions.rb +89 -0
  91. metadata +62 -5
  92. data/db/migrate/20250101000001_create_easy_admin_admin_users.rb +0 -45
@@ -0,0 +1,168 @@
1
+ module EasyAdmin
2
+ module Permissions
3
+ module Component
4
+ extend ActiveSupport::Concern
5
+
6
+ # Check if current user has permission
7
+ def current_user_can?(permission_name, context: nil)
8
+ current_user = helpers.current_user if helpers.respond_to?(:current_user)
9
+ EasyAdmin::Permissions.authorized?(current_user, permission_name, context: context)
10
+ end
11
+
12
+ # Check if current user has role
13
+ def current_user_has_role?(role_name, context: nil)
14
+ current_user = helpers.current_user if helpers.respond_to?(:current_user)
15
+ EasyAdmin::Permissions.has_role?(current_user, role_name, context: context)
16
+ end
17
+
18
+ # Render content only if user has permission
19
+ def if_can(permission_name, context: nil, &block)
20
+ if current_user_can?(permission_name, context: context)
21
+ block.call if block_given?
22
+ end
23
+ end
24
+
25
+ # Render content only if user has role
26
+ def if_has_role(role_name, context: nil, &block)
27
+ if current_user_has_role?(role_name, context: context)
28
+ block.call if block_given?
29
+ end
30
+ end
31
+
32
+ # Render content if user DOESN'T have permission
33
+ def unless_can(permission_name, context: nil, &block)
34
+ unless current_user_can?(permission_name, context: context)
35
+ block.call if block_given?
36
+ end
37
+ end
38
+
39
+ # Render content if user DOESN'T have role
40
+ def unless_has_role(role_name, context: nil, &block)
41
+ unless current_user_has_role?(role_name, context: context)
42
+ block.call if block_given?
43
+ end
44
+ end
45
+
46
+ # Conditional CSS classes based on permissions
47
+ def permission_classes(permission_name, enabled_classes: "", disabled_classes: "opacity-50 cursor-not-allowed", context: nil)
48
+ if current_user_can?(permission_name, context: context)
49
+ enabled_classes
50
+ else
51
+ disabled_classes
52
+ end
53
+ end
54
+
55
+ # Conditional attributes based on permissions
56
+ def permission_attrs(permission_name, enabled_attrs: {}, disabled_attrs: {}, context: nil)
57
+ if current_user_can?(permission_name, context: context)
58
+ enabled_attrs
59
+ else
60
+ disabled_attrs
61
+ end
62
+ end
63
+
64
+ # Generate link with permission check
65
+ def permission_link(text, href, permission_name, context: nil, **attrs, &block)
66
+ if current_user_can?(permission_name, context: context)
67
+ a(href: href, **attrs) do
68
+ if block_given?
69
+ block.call
70
+ else
71
+ text
72
+ end
73
+ end
74
+ else
75
+ span(class: "text-gray-400 cursor-not-allowed", **attrs.except(:href, :data)) do
76
+ if block_given?
77
+ block.call
78
+ else
79
+ text
80
+ end
81
+ end
82
+ end
83
+ end
84
+
85
+ # Generate button with permission check
86
+ def permission_button(text = nil, permission_name:, context: nil, disabled_class: "opacity-50 cursor-not-allowed", **attrs, &block)
87
+ can_access = current_user_can?(permission_name, context: context)
88
+
89
+ button_attrs = attrs.dup
90
+ button_attrs[:disabled] = true unless can_access
91
+ button_attrs[:class] = [button_attrs[:class], disabled_class].compact.join(" ") unless can_access
92
+
93
+ button(**button_attrs) do
94
+ if block_given?
95
+ block.call
96
+ elsif text
97
+ text
98
+ end
99
+ end
100
+ end
101
+
102
+ # Render form field only if user can edit
103
+ def permission_field(permission_name, context: nil, readonly_class: "bg-gray-100", &block)
104
+ can_edit = current_user_can?(permission_name, context: context)
105
+
106
+ if can_edit
107
+ block.call if block_given?
108
+ else
109
+ # Render read-only version
110
+ div(class: readonly_class) do
111
+ block.call if block_given?
112
+ end
113
+ end
114
+ end
115
+
116
+ # Show different content based on multiple permission checks
117
+ def permission_case(context: nil, &block)
118
+ permission_case_builder = PermissionCaseBuilder.new(self, context)
119
+ permission_case_builder.instance_eval(&block) if block_given?
120
+ permission_case_builder.render
121
+ end
122
+
123
+ private
124
+
125
+ # Helper class for building conditional permission rendering
126
+ class PermissionCaseBuilder
127
+ def initialize(component, context)
128
+ @component = component
129
+ @context = context
130
+ @cases = []
131
+ @else_block = nil
132
+ end
133
+
134
+ def when_can(permission_name, &block)
135
+ @cases << { type: :permission, name: permission_name, block: block }
136
+ self
137
+ end
138
+
139
+ def when_has_role(role_name, &block)
140
+ @cases << { type: :role, name: role_name, block: block }
141
+ self
142
+ end
143
+
144
+ def otherwise(&block)
145
+ @else_block = block
146
+ self
147
+ end
148
+
149
+ def render
150
+ @cases.each do |case_item|
151
+ case case_item[:type]
152
+ when :permission
153
+ if @component.current_user_can?(case_item[:name], context: @context)
154
+ return case_item[:block].call if case_item[:block]
155
+ end
156
+ when :role
157
+ if @component.current_user_has_role?(case_item[:name], context: @context)
158
+ return case_item[:block].call if case_item[:block]
159
+ end
160
+ end
161
+ end
162
+
163
+ @else_block&.call
164
+ end
165
+ end
166
+ end
167
+ end
168
+ end
@@ -0,0 +1,37 @@
1
+ module EasyAdmin
2
+ module Permissions
3
+ class Configuration
4
+ attr_accessor :enabled,
5
+ :cache_duration,
6
+ :admin_bypass,
7
+ :user_model,
8
+ :context_types
9
+
10
+ def initialize
11
+ @enabled = false
12
+ @cache_duration = 1.hour
13
+ @admin_bypass = true
14
+ @user_model = 'User'
15
+ @context_types = [] # e.g., ['Organization', 'Project']
16
+ end
17
+
18
+ # Define available permission contexts
19
+ def contexts(*types)
20
+ @context_types = types.map(&:to_s)
21
+ end
22
+
23
+ # Set the user model class
24
+ def user_class(klass = nil)
25
+ if klass
26
+ @user_model = klass.to_s
27
+ else
28
+ @user_model.constantize
29
+ end
30
+ end
31
+
32
+ def user_class_name
33
+ @user_model
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,164 @@
1
+ module EasyAdmin
2
+ module Permissions
3
+ module Controller
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ # Include Action Policy authorization
8
+ include ActionPolicy::Controller if defined?(ActionPolicy)
9
+
10
+ # Set up authorization context for Action Policy
11
+ if respond_to?(:authorize)
12
+ authorize :user, through: :current_user
13
+ end
14
+ end
15
+
16
+ # Check if current user has permission
17
+ def current_user_can?(permission_name, context: nil)
18
+ EasyAdmin::Permissions.authorized?(current_user, permission_name, context: context)
19
+ end
20
+
21
+ # Check if current user has role
22
+ def current_user_has_role?(role_name, context: nil)
23
+ EasyAdmin::Permissions.has_role?(current_user, role_name, context: context)
24
+ end
25
+
26
+ # Require permission or show 403 error
27
+ def require_permission!(permission_name, context: nil)
28
+ unless current_user_can?(permission_name, context: context)
29
+ handle_permission_denied(permission_name)
30
+ end
31
+ end
32
+
33
+ # Require role or show 403 error
34
+ def require_role!(role_name, context: nil)
35
+ unless current_user_has_role?(role_name, context: context)
36
+ handle_role_denied(role_name)
37
+ end
38
+ end
39
+
40
+ # Before action to check permissions for CRUD operations
41
+ def check_permissions_for_action
42
+ action = action_name.to_s
43
+ resource_name = controller_name
44
+
45
+ permission_map = {
46
+ 'index' => "#{resource_name}:read",
47
+ 'show' => "#{resource_name}:read",
48
+ 'new' => "#{resource_name}:create",
49
+ 'create' => "#{resource_name}:create",
50
+ 'edit' => "#{resource_name}:update",
51
+ 'update' => "#{resource_name}:update",
52
+ 'destroy' => "#{resource_name}:delete"
53
+ }
54
+
55
+ if permission_name = permission_map[action]
56
+ require_permission!(permission_name)
57
+ end
58
+ end
59
+
60
+ # Get current user's permissions for view helpers
61
+ def current_user_permissions(context: nil)
62
+ return [] unless current_user
63
+ EasyAdmin::Permissions.user_permissions(current_user, context: context)
64
+ end
65
+
66
+ # Check permission in views (helper method)
67
+ def can?(permission_name, context: nil)
68
+ current_user_can?(permission_name, context: context)
69
+ end
70
+
71
+ # Check role in views (helper method)
72
+ def has_role?(role_name, context: nil)
73
+ current_user_has_role?(role_name, context: context)
74
+ end
75
+
76
+ private
77
+
78
+ def handle_permission_denied(permission_name)
79
+ respond_to do |format|
80
+ format.html do
81
+ if request.xhr? || request.headers['Content-Type'] == 'text/html'
82
+ render plain: permission_denied_component(permission_name).call, status: :forbidden
83
+ else
84
+ redirect_to root_path, alert: "Permission denied: #{permission_name}"
85
+ end
86
+ end
87
+ format.json { render json: { error: "Permission denied: #{permission_name}" }, status: :forbidden }
88
+ format.turbo_stream { render turbo_stream: turbo_stream.replace("main", permission_denied_component(permission_name).call) }
89
+ end
90
+ end
91
+
92
+ def handle_role_denied(role_name)
93
+ respond_to do |format|
94
+ format.html do
95
+ if request.xhr? || request.headers['Content-Type'] == 'text/html'
96
+ render plain: role_denied_component(role_name).call, status: :forbidden
97
+ else
98
+ redirect_to root_path, alert: "Role required: #{role_name}"
99
+ end
100
+ end
101
+ format.json { render json: { error: "Role required: #{role_name}" }, status: :forbidden }
102
+ format.turbo_stream { render turbo_stream: turbo_stream.replace("main", role_denied_component(role_name).call) }
103
+ end
104
+ end
105
+
106
+ def permission_denied_component(permission_name)
107
+ if defined?(EasyAdmin::Permissions::PermissionDeniedComponent)
108
+ EasyAdmin::Permissions::PermissionDeniedComponent.new(permission: permission_name, user: current_user)
109
+ else
110
+ # Fallback component
111
+ BasicPermissionDeniedComponent.new(permission: permission_name)
112
+ end
113
+ end
114
+
115
+ def role_denied_component(role_name)
116
+ if defined?(EasyAdmin::Permissions::RoleDeniedComponent)
117
+ EasyAdmin::Permissions::RoleDeniedComponent.new(role: role_name, user: current_user)
118
+ else
119
+ # Fallback component
120
+ BasicRoleDeniedComponent.new(role: role_name)
121
+ end
122
+ end
123
+
124
+ # Basic fallback components for when full components aren't defined
125
+ class BasicPermissionDeniedComponent
126
+ def initialize(permission:)
127
+ @permission = permission
128
+ end
129
+
130
+ def call
131
+ <<~HTML
132
+ <div class="flex items-center justify-center min-h-96">
133
+ <div class="text-center">
134
+ <div class="text-6xl text-gray-400 mb-4">🔒</div>
135
+ <h2 class="text-2xl font-bold text-gray-900 mb-2">Permission Denied</h2>
136
+ <p class="text-gray-600">You need the '#{@permission}' permission to access this resource.</p>
137
+ <a href="javascript:history.back()" class="inline-block mt-4 px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600">Go Back</a>
138
+ </div>
139
+ </div>
140
+ HTML
141
+ end
142
+ end
143
+
144
+ class BasicRoleDeniedComponent
145
+ def initialize(role:)
146
+ @role = role
147
+ end
148
+
149
+ def call
150
+ <<~HTML
151
+ <div class="flex items-center justify-center min-h-96">
152
+ <div class="text-center">
153
+ <div class="text-6xl text-gray-400 mb-4">👑</div>
154
+ <h2 class="text-2xl font-bold text-gray-900 mb-2">Role Required</h2>
155
+ <p class="text-gray-600">You need the '#{@role}' role to access this resource.</p>
156
+ <a href="javascript:history.back()" class="inline-block mt-4 px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600">Go Back</a>
157
+ </div>
158
+ </div>
159
+ HTML
160
+ end
161
+ end
162
+ end
163
+ end
164
+ end
@@ -0,0 +1,180 @@
1
+ module EasyAdmin
2
+ module Permissions
3
+ class DSL
4
+ def initialize
5
+ @permissions = []
6
+ @roles = []
7
+ end
8
+
9
+ # Define a permission
10
+ def permission(resource_type, action, description: nil, conditions: {})
11
+ name = "#{resource_type}:#{action}"
12
+
13
+ @permissions << {
14
+ name: name,
15
+ resource_type: resource_type.to_s,
16
+ action: action.to_s,
17
+ description: description || "#{action.to_s.humanize} #{resource_type.to_s.humanize}",
18
+ conditions: conditions
19
+ }
20
+ end
21
+
22
+ # Define a role with permissions
23
+ def role(name, slug: nil, description: nil, permissions: [], active: true, metadata: {})
24
+ @roles << {
25
+ name: name.to_s.humanize,
26
+ slug: slug&.to_s || name.to_s.parameterize,
27
+ description: description,
28
+ permissions: permissions.map(&:to_s),
29
+ active: active,
30
+ metadata: metadata
31
+ }
32
+ end
33
+
34
+ # Shortcut methods for common permission patterns
35
+ def crud_permissions(resource_type, description_prefix: nil)
36
+ prefix = description_prefix || resource_type.to_s.humanize
37
+
38
+ permission resource_type, :read, description: "View #{prefix.downcase}"
39
+ permission resource_type, :create, description: "Create new #{prefix.downcase.singularize}"
40
+ permission resource_type, :update, description: "Edit existing #{prefix.downcase}"
41
+ permission resource_type, :delete, description: "Delete #{prefix.downcase}"
42
+ end
43
+
44
+ def admin_permissions(resource_type, description_prefix: nil)
45
+ crud_permissions(resource_type, description_prefix: description_prefix)
46
+ permission resource_type, :manage, description: "Full management of #{(description_prefix || resource_type.to_s.humanize).downcase}"
47
+ end
48
+
49
+ # Resource shortcuts
50
+ def resource(resource_type, actions: [:read, :create, :update, :delete], description_prefix: nil)
51
+ prefix = description_prefix || resource_type.to_s.humanize
52
+
53
+ actions.each do |action|
54
+ case action
55
+ when :read
56
+ permission resource_type, :read, description: "View #{prefix.downcase}"
57
+ when :create
58
+ permission resource_type, :create, description: "Create new #{prefix.downcase.singularize}"
59
+ when :update
60
+ permission resource_type, :update, description: "Edit existing #{prefix.downcase}"
61
+ when :delete
62
+ permission resource_type, :delete, description: "Delete #{prefix.downcase}"
63
+ else
64
+ permission resource_type, action, description: "#{action.to_s.humanize} #{prefix.downcase}"
65
+ end
66
+ end
67
+ end
68
+
69
+ # EasyAdmin resource shortcuts using actual resource discovery
70
+ def easy_admin_resource(resource_name, actions: nil)
71
+ available_actions = EasyAdmin::Permissions::ResourcePermissions.actions_for_resource(resource_name)
72
+ actions_to_create = actions || available_actions
73
+
74
+ actions_to_create.each do |action|
75
+ next unless available_actions.include?(action.to_s)
76
+
77
+ permission_data = EasyAdmin::Permissions::ResourcePermissions.discover_all_permissions
78
+ .find { |p| p[:resource_type] == resource_name.to_s && p[:action] == action.to_s }
79
+
80
+ if permission_data
81
+ permission resource_name, action, description: permission_data[:description]
82
+ else
83
+ permission resource_name, action
84
+ end
85
+ end
86
+ end
87
+
88
+ # Auto-discover and create permissions for all EasyAdmin resources
89
+ def auto_discover_resources(actions: nil)
90
+ EasyAdmin::Permissions::ResourcePermissions.available_resources.each do |resource_name|
91
+ easy_admin_resource(resource_name, actions: actions)
92
+ end
93
+ end
94
+
95
+ # Enhanced role definition with EasyAdmin resource support
96
+ def easy_admin_role(name, slug: nil, description: nil, resources: {}, permissions: [], active: true, metadata: {})
97
+ # Convert resources hash to permission names
98
+ resource_permissions = []
99
+
100
+ resources.each do |resource_name, resource_actions|
101
+ Array(resource_actions).each do |action|
102
+ resource_permissions << "#{resource_name}:#{action}"
103
+ end
104
+ end
105
+
106
+ # Combine with manual permissions
107
+ all_permissions = resource_permissions + permissions.map(&:to_s)
108
+
109
+ role(name, slug: slug, description: description, permissions: all_permissions, active: active, metadata: metadata)
110
+ end
111
+
112
+ # Execute the DSL block and return the collected data
113
+ def self.evaluate(&block)
114
+ dsl = new
115
+ dsl.instance_eval(&block) if block_given?
116
+ {
117
+ permissions: dsl.instance_variable_get(:@permissions),
118
+ roles: dsl.instance_variable_get(:@roles)
119
+ }
120
+ end
121
+
122
+ # Create permissions and roles in the database
123
+ def self.seed_database(data)
124
+ # Check if models are available (avoid running during migrations)
125
+ begin
126
+ EasyAdmin::Permissions::Permission
127
+ EasyAdmin::Permissions::Role
128
+ EasyAdmin::Permissions::RolePermission
129
+ rescue NameError => e
130
+ Rails.logger.info "EasyAdmin::Permissions models not yet loaded: #{e.message}"
131
+ return
132
+ end
133
+
134
+ ActiveRecord::Base.transaction do
135
+ # Create permissions
136
+ data[:permissions].each do |perm_data|
137
+ EasyAdmin::Permissions::Permission.find_or_create_by(name: perm_data[:name]) do |permission|
138
+ permission.resource_type = perm_data[:resource_type]
139
+ permission.action = perm_data[:action]
140
+ permission.description = perm_data[:description]
141
+ permission.conditions = perm_data[:conditions]
142
+ end
143
+ end
144
+
145
+ # Create roles and assign permissions
146
+ data[:roles].each do |role_data|
147
+ role = EasyAdmin::Permissions::Role.find_or_create_by(slug: role_data[:slug]) do |r|
148
+ r.name = role_data[:name]
149
+ r.description = role_data[:description]
150
+ r.active = role_data[:active]
151
+ r.metadata = role_data[:metadata]
152
+ end
153
+
154
+ # Clear existing permissions for this role
155
+ role.role_permissions.destroy_all
156
+
157
+ # Assign new permissions
158
+ role_data[:permissions].each do |permission_name|
159
+ permission = EasyAdmin::Permissions::Permission.find_by(name: permission_name)
160
+ if permission
161
+ EasyAdmin::Permissions::RolePermission.find_or_create_by(
162
+ role: role,
163
+ permission: permission
164
+ )
165
+ else
166
+ Rails.logger.warn "Permission '#{permission_name}' not found for role '#{role_data[:name]}'"
167
+ end
168
+ end
169
+ end
170
+ end
171
+ end
172
+ end
173
+
174
+ # Main method to define permissions and roles
175
+ def self.define(&block)
176
+ data = DSL.evaluate(&block)
177
+ DSL.seed_database(data)
178
+ end
179
+ end
180
+ end
@@ -0,0 +1,44 @@
1
+ module EasyAdmin
2
+ module Permissions
3
+ module Models
4
+ extend ActiveSupport::Concern
5
+
6
+ # Include this in your Role model
7
+ module Role
8
+ extend ActiveSupport::Concern
9
+
10
+ included do
11
+ has_many :user_roles, class_name: 'EasyAdmin::Permissions::UserRole', dependent: :destroy
12
+ has_many :users, through: :user_roles, class_name: EasyAdmin::Permissions.configuration.user_class_name
13
+
14
+ validates :name, presence: true, uniqueness: true
15
+ validates :slug, presence: true, uniqueness: true
16
+
17
+ before_validation :generate_slug, if: -> { slug.blank? }
18
+
19
+ scope :active, -> { where(active: true) }
20
+ end
21
+
22
+ private
23
+
24
+ def generate_slug
25
+ self.slug = name&.parameterize
26
+ end
27
+ end
28
+
29
+ # Include this in your UserRole model (join table)
30
+ module UserRole
31
+ extend ActiveSupport::Concern
32
+
33
+ included do
34
+ belongs_to :user, class_name: EasyAdmin::Permissions.configuration.user_class_name, foreign_key: 'user_id'
35
+ belongs_to :role, class_name: 'EasyAdmin::Permissions::Role'
36
+
37
+ validates :user_id, uniqueness: { scope: :role_id }
38
+
39
+ scope :active, -> { where(active: true) }
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end