lesli 5.0.21 → 5.0.23

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 (156) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +4 -0
  3. data/app/assets/config/lesli_manifest.js +2 -2
  4. data/app/assets/images/lesli/brand/register-background.jpg +0 -0
  5. data/app/assets/stylesheets/lesli/application.css +192 -1
  6. data/app/controllers/lesli/abouts_controller.rb +12 -13
  7. data/app/controllers/lesli/application_controller.rb +1 -42
  8. data/app/controllers/lesli/application_lesli_controller.rb +8 -1
  9. data/app/controllers/lesli/apps_controller.rb +25 -0
  10. data/app/controllers/lesli/items/actions_controller.rb +122 -0
  11. data/app/{models/lesli/item/activity.rb → controllers/lesli/items/activities_controller.rb} +2 -2
  12. data/app/controllers/lesli/items/discussions_controller.rb +93 -0
  13. data/app/controllers/lesli/{system_controllers_controller.rb → resources_controller.rb} +3 -3
  14. data/app/controllers/lesli/shared/dashboards_controller.rb +11 -279
  15. data/app/helpers/lesli/customization_helper.rb +3 -3
  16. data/app/helpers/lesli/html_helper.rb +114 -2
  17. data/app/helpers/lesli/navigation_helper.rb +8 -25
  18. data/app/helpers/lesli/system_helper.rb +21 -4
  19. data/app/helpers/lesli/turbo_helper.rb +19 -2
  20. data/app/interfaces/lesli/requester_interface.rb +2 -29
  21. data/app/interfaces/lesli/responder_interface.rb +121 -61
  22. data/app/models/concerns/lesli/account_initializer.rb +13 -13
  23. data/{lib/rspec/factories/lesli_account.rb → app/models/concerns/lesli/account_logs.rb} +23 -9
  24. data/app/models/concerns/lesli/user_extensions.rb +33 -51
  25. data/app/models/lesli/account.rb +6 -5
  26. data/app/models/lesli/application_lesli_record.rb +14 -0
  27. data/app/models/lesli/items/action.rb +15 -0
  28. data/app/models/lesli/items/activity.rb +50 -0
  29. data/app/models/lesli/items/discussion.rb +15 -0
  30. data/app/models/lesli/{user/journal.rb → resource.rb} +9 -4
  31. data/app/models/lesli/role.rb +2 -17
  32. data/app/models/lesli/shared/dashboard.rb +1 -0
  33. data/app/models/lesli/user.rb +23 -77
  34. data/app/services/lesli/application_lesli_service.rb +21 -4
  35. data/app/{operators/lesli/controller_operator.rb → services/lesli/resource_service.rb} +92 -23
  36. data/app/services/lesli/role_service.rb +16 -10
  37. data/app/services/lesli/user_service.rb +28 -27
  38. data/app/views/lesli/abouts/welcome.html.erb +11 -11
  39. data/app/views/lesli/apps/show.html.erb +1 -1
  40. data/app/views/lesli/errors/not_found.html.erb +1 -1
  41. data/app/views/lesli/errors/unauthorized.html.erb +9 -11
  42. data/app/views/lesli/layouts/application-devise.html.erb +6 -9
  43. data/app/views/lesli/layouts/application-lesli.html.erb +9 -9
  44. data/app/views/lesli/layouts/application-public.html.erb +4 -18
  45. data/app/views/lesli/layouts/mailer.html.erb +1 -1
  46. data/app/views/lesli/partials/{_application-analytics.html.erb → _application-lesli-analytics.html.erb} +18 -9
  47. data/app/views/lesli/partials/{_application-lesli-scss.html.erb → _application-lesli-assets.html.erb} +9 -11
  48. data/{db/migrate/v1/0000110310_create_lesli_account_settings.rb → app/views/lesli/partials/_application-lesli-data.html.erb} +11 -7
  49. data/app/views/lesli/partials/{_application-head.html.erb → _application-lesli-head.html.erb} +2 -2
  50. data/app/views/lesli/partials/_application-lesli-header.html.erb +6 -6
  51. data/app/views/lesli/partials/_application-lesli-navigation.html.erb +3 -6
  52. data/app/views/lesli/partials/_application-lesli-notifications.html.erb +0 -1
  53. data/app/views/lesli/partials/turbo/_redirection.html.erb +5 -0
  54. data/app/views/lesli/{partials/_application-data.html.erb → shared/dashboards/edit.html.erb} +2 -6
  55. data/app/views/lesli/shared/dashboards/{_show.html.erb → show.html.erb} +3 -3
  56. data/config/brakeman.yml +55 -0
  57. data/config/importmap.rb +0 -3
  58. data/config/initializers/devise.rb +1 -2
  59. data/config/initializers/lesli.rb +27 -14
  60. data/config/initializers/lesli_migration_helpers.rb +0 -5
  61. data/config/locales/translations.en.yml +6 -2
  62. data/config/locales/translations.es.yml +5 -2
  63. data/config/locales/translations.fr.yml +1 -4
  64. data/config/locales/translations.it.yml +5 -2
  65. data/config/locales/translations.pt.yml +1 -4
  66. data/config/routes.rb +4 -6
  67. data/db/migrate/v1/{0000000310_create_lesli_users.rb → 0000000210_create_lesli_users.rb} +4 -0
  68. data/db/migrate/v1/{0000000210_create_lesli_roles.rb → 0000000310_create_lesli_roles.rb} +6 -0
  69. data/db/migrate/v1/{0000100110_create_lesli_system_controllers.rb → 0000000410_create_lesli_resources.rb} +8 -5
  70. data/db/seed/users.rb +1 -1
  71. data/db/seed/xyz.rb +3 -1
  72. data/lib/generators/lesli/spec/spec_generator.rb +0 -3
  73. data/lib/generators/lesli/view/view_generator.rb +0 -3
  74. data/lib/lesli/configuration.rb +1 -1
  75. data/{app/lib → lib}/lesli/courier.rb +40 -7
  76. data/lib/lesli/engine.rb +4 -5
  77. data/lib/lesli/{routing.rb → router.rb} +3 -2
  78. data/lib/lesli/version.rb +2 -2
  79. data/lib/lesli.rb +3 -2
  80. data/lib/migrate/common.rb +2 -0
  81. data/lib/migrate/items/action_structure.rb +2 -1
  82. data/lib/migrate/items/activity_structure.rb +12 -2
  83. data/lib/migrate/items/discussion_structure.rb +1 -1
  84. data/lib/scss/_apps.scss +1 -0
  85. data/lib/tasks/lesli/db.rake +5 -5
  86. data/lib/tasks/lesli/{controllers.rake → resources.rake} +4 -4
  87. data/lib/tasks/lesli_tasks.rake +13 -3
  88. data/lib/test/config.rb +111 -0
  89. data/{db/migrate/v1/0000130210_create_lesli_user_roles.rb → lib/test/helpers/response_integration_helper.rb} +12 -9
  90. data/{db/migrate/v1/0000100210_create_lesli_system_controller_actions.rb → lib/test/lesli.rb} +28 -9
  91. data/readme.md +61 -68
  92. metadata +49 -121
  93. data/app/helpers/lesli/assets_helper.rb +0 -110
  94. data/app/helpers/lesli/general_helper.rb +0 -96
  95. data/app/mailers/lesli/devise_mailer.rb +0 -67
  96. data/app/models/concerns/lesli/has_activities.rb +0 -29
  97. data/app/models/concerns/lesli/user_activities.rb +0 -163
  98. data/app/models/concerns/lesli/user_security.rb +0 -220
  99. data/app/models/lesli/account/detail.rb +0 -37
  100. data/app/models/lesli/account/log.rb +0 -57
  101. data/app/models/lesli/account/request.rb +0 -37
  102. data/app/models/lesli/descriptor/privilege.rb +0 -39
  103. data/app/models/lesli/descriptor.rb +0 -57
  104. data/app/models/lesli/role/action.rb +0 -73
  105. data/app/models/lesli/role/privilege.rb +0 -38
  106. data/app/models/lesli/system_controller/action.rb +0 -37
  107. data/app/models/lesli/system_controller.rb +0 -104
  108. data/app/models/lesli/user/activity.rb +0 -5
  109. data/app/models/lesli/user/detail.rb +0 -55
  110. data/app/models/lesli/user/request.rb +0 -38
  111. data/app/models/lesli/user/role.rb +0 -39
  112. data/app/models/lesli/user/session.rb +0 -79
  113. data/app/models/lesli/user/setting.rb +0 -46
  114. data/app/operators/lesli/role_operator.rb +0 -153
  115. data/app/services/lesli/role/action_service.rb +0 -75
  116. data/app/services/lesli/user/session_service.rb +0 -78
  117. data/app/validators/lesli/application_lesli_validator.rb +0 -67
  118. data/app/validators/lesli/users_validator.rb +0 -217
  119. data/app/views/lesli/emails/user_mailer/invitation.html.erb +0 -23
  120. data/app/views/lesli/partials/_application-lesli-annoouncements.html.erb +0 -49
  121. data/app/views/lesli/partials/_application-lesli-chatbox.html.erb +0 -35
  122. data/app/views/lesli/partials/_application-lesli-javascript.html.erb +0 -48
  123. data/app/views/lesli/partials/_application-lesli-panels.html.erb +0 -58
  124. data/app/views/lesli/partials/_application-public-footer.html.erb +0 -51
  125. data/app/views/lesli/partials/_application-public-javascript.html.erb +0 -49
  126. data/app/views/lesli/partials/_application-public-scss.html.erb +0 -35
  127. data/app/views/lesli/shared/dashboards/_edit.html.erb +0 -33
  128. data/app/views/lesli/wrappers/_application-devise-simple.erb +0 -59
  129. data/app/views/lesli/wrappers/_application-devise.html.erb +0 -76
  130. data/config/initializers/devise_rails_8_patch.rb +0 -8
  131. data/db/migrate/v1/0000110210_create_lesli_account_details.rb +0 -70
  132. data/db/migrate/v1/0000110410_create_lesli_account_activities.rb +0 -37
  133. data/db/migrate/v1/0000120210_create_lesli_role_actions.rb +0 -45
  134. data/db/migrate/v1/0000120310_create_lesli_role_privileges.rb +0 -45
  135. data/db/migrate/v1/0000130310_create_lesli_user_sessions.rb +0 -56
  136. data/db/migrate/v1/0000130410_create_lesli_user_activities.rb +0 -37
  137. data/db/migrate2/0000100510_create_lesli_account_locations.rb +0 -64
  138. data/db/migrate2/0000100610_create_lesli_account_currencies.rb +0 -48
  139. data/lib/assets/javascripts/lesli/i18n_js +0 -1095
  140. data/lib/migrate/shared/dashboard_structure.rb +0 -54
  141. data/lib/migrate/shared/setting_structure.rb +0 -20
  142. data/lib/rspec/config/spec_coverage.rb +0 -95
  143. data/lib/rspec/factories/lesli_role.rb +0 -62
  144. data/lib/rspec/factories/lesli_user.rb +0 -64
  145. data/lib/rspec/fixtures/files/lesli-icon.png +0 -0
  146. data/lib/rspec/fixtures/files/test_file_1.docx +0 -0
  147. data/lib/rspec/helpers/lesli_helper.rb +0 -54
  148. data/lib/rspec/helpers/rails_helper.rb +0 -134
  149. data/lib/rspec/helpers/response_request_helper.rb +0 -214
  150. data/lib/rspec/helpers/response_service_helper.rb +0 -66
  151. data/lib/rspec/helpers/spec_helper.rb +0 -99
  152. data/lib/rspec/lesli_api_tester.rb +0 -133
  153. data/lib/rspec/lesli_service_tester.rb +0 -46
  154. data/lib/rspec/testers/controller.rb +0 -63
  155. data/lib/rspec/testers/model.rb +0 -36
  156. data/lib/rspec/testers/request.rb +0 -93
@@ -2,7 +2,7 @@
2
2
 
3
3
  Lesli
4
4
 
5
- Copyright (c) 2025, Lesli Technologies, S. A.
5
+ Copyright (c) 2026, Lesli Technologies, S. A.
6
6
 
7
7
  This program is free software: you can redistribute it and/or modify
8
8
  it under the terms of the GNU General Public License as published by
@@ -32,10 +32,10 @@ Building a better future, one line of code at a time.
32
32
 
33
33
  module Lesli
34
34
  class User < ApplicationLesliRecord
35
- include Lesli::UserSecurity
36
35
  include Lesli::UserExtensions
37
- #include UserActivities
36
+ include LesliShield::UserSecurity if defined?(LesliShield)
38
37
 
38
+ # user required configuration
39
39
  validates(:email,
40
40
  format: { with: URI::MailTo::EMAIL_REGEXP },
41
41
  presence: true,
@@ -52,8 +52,6 @@ module Lesli
52
52
  :confirmable,
53
53
  :trackable
54
54
  );
55
- #:omniauthable, omniauth_providers: [:google_oauth2, :facebook]
56
-
57
55
 
58
56
  # users belongs to an account only...
59
57
  belongs_to :account, optional: true
@@ -67,24 +65,16 @@ module Lesli
67
65
  # users data extensions
68
66
  has_many :tokens
69
67
  has_many :settings
70
- has_many :sessions
71
- has_many :activities #, class_name: "Lesli::Item::Activity"
72
-
73
-
74
- has_many :shortcuts, class_name: "LesliShield::User::Shortcuts"
75
-
68
+ has_many :sessions, class_name: 'LesliShield::User::Session'
69
+ has_many :requests, class_name: 'LesliAudit::UserRequest'
70
+ has_many :journals, class_name: 'LesliAudit::UserJournal'
71
+ has_many :logs, class_name: 'LesliAudit::UserLog'
76
72
 
77
- # users can have many roles and too many privileges through the roles
78
- # lesliroles is a shortcut to Lesli::Roles
79
- has_many :roles
80
- has_many :lesliroles, through: :roles, source: :role, class_name: "Lesli::Role"
81
- has_many :privileges, through: :lesliroles, class_name: "Lesli::Role::Privilege"
82
-
83
-
84
- # callbacks
85
- before_create :before_create_user
86
- after_create :after_create_user
87
- #after_update :update_associated_services
73
+
74
+ # Users can have many roles and too many privileges through the roles
75
+ has_many :user_roles, class_name: "LesliShield::User::Role"
76
+ has_many :roles, through: :user_roles, source: :role
77
+ has_many :privileges, through: :roles
88
78
 
89
79
 
90
80
  # allow save duplicated users to execute callbacks
@@ -93,65 +83,21 @@ module Lesli
93
83
  rescue ActiveRecord::RecordNotUnique => error
94
84
  end
95
85
 
86
+ before_create :before_create_user
96
87
 
97
- # @return [void]
98
- # @description Before creating a user we make sure there is no capitalized email
99
- def before_create_user
100
- self.email = self.email.downcase
101
- end
102
-
103
-
104
- def after_create_user
105
- self.activities.create(title: "create_user", description:"User created")
106
- after_confirmation
107
- after_account_assignation
108
- end
109
-
110
-
111
- def after_account_assignation
112
- return unless self.account
113
-
114
- #Courier::One::Firebase::User.sync_user(self)
115
- # Lesli::Courier.new(:lesli_calendar).from(:calendar_service, self).create({
116
- # name: "Personal Calendar",
117
- # default: true
118
- # })
119
- end
120
-
121
-
122
- # Initialize user settings and dependencies needed
123
- def after_confirmation
124
- return unless self.confirmed?
125
-
126
- self.activities.create(title: "create_user", description:"User confirmed")
127
-
128
- # create an alias based on user name defined in user extensions
129
- self.set_alias
88
+ private
130
89
 
131
- # Minimum security settings required
132
- #self.settings.create_with(:value => false).find_or_create_by(:name => "mfa_enabled")
133
- #self.settings.create_with(:value => :email).find_or_create_by(:name => "mfa_method")
90
+ def before_create_user
91
+ self.uid ||= loop do
92
+ candidate = generate_resource_uid(prefix:'LID')
93
+ break candidate unless self.account&.users&.exists?(uid: candidate)
94
+ end
134
95
  end
135
96
 
136
-
137
- def update_associated_services
138
- if saved_change_to_first_name? || saved_change_to_last_name? || saved_change_to_telephone?
139
-
140
- # defined in user extensions
141
- self.set_alias
142
-
143
- return
144
- if defined? CloudOne
145
-
146
- data = {
147
- full_name: self.user.full_name,
148
- telephone: self.telephone,
149
- }
150
-
151
- CloudOne::Firebase::User.update_data(self.user, data)
152
-
153
- end
154
- end
97
+ def generate_resource_uid(prefix:'Lesli')
98
+ charset = "23456789ABCDEFGHJKLMNPQRSTUVWXYZ"
99
+ letters = Array.new(4) { charset.chars.sample }.join
100
+ return "#{prefix}-#{letters}"
155
101
  end
156
102
  end
157
103
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  Lesli
4
4
 
5
- Copyright (c) 2023, Lesli Technologies, S. A.
5
+ Copyright (c) 2026, Lesli Technologies, S. A.
6
6
 
7
7
  This program is free software: you can redistribute it and/or modify
8
8
  it under the terms of the GNU General Public License as published by
@@ -17,9 +17,9 @@ GNU General Public License for more details.
17
17
  You should have received a copy of the GNU General Public License
18
18
  along with this program. If not, see http://www.gnu.org/licenses/.
19
19
 
20
- Lesli · Ruby on Rails SaaS development platform.
20
+ Lesli · Ruby on Rails SaaS Development Framework.
21
21
 
22
- Made with ♥ by https://www.lesli.tech
22
+ Made with ♥ by LesliTech
23
23
  Building a better future, one line of code at a time.
24
24
 
25
25
  @contact hello@lesli.tech
@@ -28,7 +28,6 @@ Building a better future, one line of code at a time.
28
28
 
29
29
  // · ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~
30
30
  // ·
31
-
32
31
  =end
33
32
 
34
33
  # ·
@@ -149,6 +148,14 @@ module Lesli
149
148
  self.resource
150
149
  end
151
150
 
151
+ def cache_key_for_account(method=nil)
152
+ cache_key_builder('account',current_user.account.id,method)
153
+ end
154
+
155
+ def cache_key_for_user(method=nil)
156
+ cache_key_builder('user',current_user.id,method)
157
+ end
158
+
152
159
  private
153
160
 
154
161
  def response resource = nil
@@ -156,6 +163,16 @@ module Lesli
156
163
  self
157
164
  end
158
165
 
166
+ def cache_key_builder(namespace,namespace_id,method)
167
+ [
168
+ namespace,
169
+ namespace_id,
170
+ self.class.name.underscore,
171
+ method,
172
+ query[:cacheKey]
173
+ ].compact.join(":").tr("/_", "-")
174
+ end
175
+
159
176
  attr_reader :current_user
160
177
 
161
178
  attr_reader :resource
@@ -2,7 +2,7 @@
2
2
 
3
3
  Lesli
4
4
 
5
- Copyright (c) 2023, Lesli Technologies, S. A.
5
+ Copyright (c) 2026, Lesli Technologies, S. A.
6
6
 
7
7
  This program is free software: you can redistribute it and/or modify
8
8
  it under the terms of the GNU General Public License as published by
@@ -31,7 +31,7 @@ Building a better future, one line of code at a time.
31
31
  =end
32
32
 
33
33
  module Lesli
34
- class ControllerOperator < Lesli::ApplicationLesliService
34
+ class ResourceService < Lesli::ApplicationLesliService
35
35
 
36
36
  DEVISE_CONTROLLERS = [
37
37
  "users/registrations",
@@ -43,7 +43,72 @@ module Lesli
43
43
  def initialize
44
44
  end
45
45
 
46
- "Scan new routes added and create role privileges"
46
+ def index matrix:false
47
+
48
+ # get a matrix of controllers and actions
49
+ c = Resource.actions.joins(:parent).select(
50
+ "parents_lesli_resources.engine as engine",
51
+ "parents_lesli_resources.route as route",
52
+ "parents_lesli_resources.identifier as controller",
53
+ "parents_lesli_resources.label as controller_name",
54
+ "parents_lesli_resources.id as controller_id",
55
+ "lesli_resources.action as action",
56
+ "lesli_resources.id as action_id",
57
+ "case lesli_resources.action
58
+ when 'index' then 1
59
+ when 'show' then 2
60
+ when 'new' then 3
61
+ when 'edit' then 4
62
+ when 'create' then 5
63
+ when 'update' then 6
64
+ when 'destroy' then 7
65
+ when 'options' then 8
66
+ else 9
67
+ end as importance
68
+ "
69
+ )
70
+ .where("parents_lesli_resources.deleted_at is NULL")
71
+ .order("importance DESC")
72
+
73
+ return c unless matrix
74
+
75
+ cc = {}
76
+
77
+ # convert the matrix to a hash of engines with controllers and available actions as values
78
+ # example:
79
+ # my_engine: { my_controller: [ my list of actions ]}
80
+ c.each do |c|
81
+
82
+ engine = c[:engine]
83
+ controller = c[:controller]
84
+
85
+ # create a uniq container for every action that belongs to a specific controller
86
+ if cc[engine].blank?
87
+ cc[engine] = {}
88
+ end
89
+
90
+ # create a uniq container for every action that belongs to a specific controller
91
+ if cc[engine][controller].blank?
92
+ cc[engine][controller] = {
93
+ id: c[:controller_id],
94
+ name: c[:controller_name],
95
+ route: c[:route],
96
+ actions: []
97
+ }
98
+ end
99
+
100
+ # push every action to his specic controller
101
+ cc[engine][controller][:actions].push({
102
+ id: c[:action_id],
103
+ action: c[:action]
104
+ })
105
+ end
106
+
107
+ return cc
108
+
109
+ end
110
+
111
+ # Scan new routes added and create role privileges
47
112
  def build
48
113
 
49
114
  # get all the engines, controllers and actions
@@ -60,24 +125,26 @@ module Lesli
60
125
  # cloud_bell/notifications converts to CloudBell::Notifications
61
126
  # sometimes we need a second split to deal with third level deep of controllers
62
127
  # Example: "Account::Currency::ExchangeRatesController" from "account/currency/exchange_rates"
63
- reference = controller_route
64
- .split('/') # split the controller path by namespace
65
- .collect(&:capitalize) # uppercase the first letter to match the class name convention of Rails
66
- .join("::") # join by ruby class separator for namespaces
67
- .split('_') # work with compound words like "exchange_rates"
68
- .collect { |x| x[0] = x[0].upcase; x } # convert ['exchange', 'rates'] to ['Exchange', 'Rates']
69
- .join('') # joins everything in a single string
70
-
71
- name = reference.sub('::',' ')
72
-
73
- controller = Lesli::SystemController.create_with({
74
- name: name,
75
- :engine => engine,
76
- :reference => reference
77
- }).find_or_create_by!(route: controller_route)
78
-
128
+ identifier = controller_route
129
+ .camelize
130
+ .gsub('/', '::')
131
+
132
+ name = identifier.demodulize.humanize
133
+
134
+ controller = Lesli::Resource.find_or_initialize_by(route: controller_route)
135
+ controller.assign_attributes(
136
+ label: name,
137
+ engine: engine,
138
+ identifier: identifier
139
+ )
140
+ controller.save!
141
+
79
142
  controller_actions.each do |action_name|
80
- controller.actions.find_or_create_by!(name: action_name)
143
+ controller.children.find_or_create_by!(
144
+ action: action_name,
145
+ identifier: action_name,
146
+ label: action_name.humanize
147
+ )
81
148
  end
82
149
  end
83
150
  end
@@ -89,15 +156,15 @@ module Lesli
89
156
 
90
157
  # Global container
91
158
  controller_list = {
92
- "app" => {},
159
+ "rails_app" => {},
93
160
  "lesli" => {}
94
161
  }
95
162
 
96
163
  # Get the list of controllers and actions of the main rails app
97
164
  Rails.application.routes.routes.each do |route|
98
165
 
99
- list = "app"
100
- route = route.defaults
166
+ list = "rails_app"
167
+ route = route.defaults
101
168
 
102
169
  # filter the non-used main app routes
103
170
  next if route[:controller].blank?
@@ -106,6 +173,7 @@ module Lesli
106
173
  next if route[:controller].include? "active_storage"
107
174
  next if route[:controller].include? "view_components"
108
175
  next if route[:controller].include? "turbo/native/navigation"
176
+ next if route[:controller].include? "lesli/abouts" #default welcome page
109
177
 
110
178
  if DEVISE_CONTROLLERS.include?(route[:controller])
111
179
  list = "lesli"
@@ -132,6 +200,7 @@ module Lesli
132
200
  # validate if route has information, some special routes like redirects
133
201
  # can generate an empty entry in the route hash
134
202
  next if route.empty?
203
+ next if route[:controller].include? "rails/health"
135
204
 
136
205
  # get the engine code
137
206
  engine_code = engine_info[:code]
@@ -42,7 +42,7 @@ module Lesli
42
42
  # according to object level permission
43
43
  def list(params)
44
44
  current_user.account.roles
45
- .where("permission_level <= ?", current_user.max_object_level_permission)
45
+ .where("permission_level <= ?", current_user.max_level_permission)
46
46
  .order(permission_level: :desc, name: :asc)
47
47
  .select(:id, :name, :permission_level)
48
48
  end
@@ -52,29 +52,35 @@ module Lesli
52
52
  # @description Return a paginated array of users, used mostly in frontend views
53
53
  def index
54
54
 
55
- current_user.account.roles.where.not(
56
- :name => ["owner"]
57
- ).joins("
55
+ users_subquery = <<-SQL
58
56
  left join (
59
57
  select
60
58
  count(1) users,
61
59
  role_id
62
- from lesli_user_roles
60
+ from lesli_shield_user_roles
63
61
  inner join lesli_users as u
64
- on u.id = lesli_user_roles.user_id
62
+ on u.id = lesli_shield_user_roles.user_id
65
63
  and u.deleted_at is null
66
- where lesli_user_roles.deleted_at is null
64
+ where lesli_shield_user_roles.deleted_at is null
67
65
  group by (role_id)
68
66
  ) users on users.role_id = lesli_roles.id
69
- ").joins(%(
67
+ SQL
68
+
69
+ actions_subquery = <<-SQL
70
70
  left join (
71
71
  select
72
72
  count(1) actions,
73
73
  role_id
74
- from lesli_role_actions
74
+ from lesli_shield_role_actions
75
75
  group by role_id
76
76
  ) actions on actions.role_id = lesli_roles.id
77
- )).where("lesli_roles.permission_level <= ?", current_user.max_level_permission)
77
+ SQL
78
+
79
+ current_user.account.roles
80
+ .where.not(:name => ['owner'])
81
+ .where("lesli_roles.permission_level <= ?", current_user.max_level_permission)
82
+ .joins(users_subquery)
83
+ .joins(actions_subquery)
78
84
  .select(
79
85
  :id,
80
86
  :name,
@@ -34,7 +34,6 @@ module Lesli
34
34
  class UserService < Lesli::ApplicationLesliService
35
35
 
36
36
  def find id
37
- #super(current_user.account.users.joins(:detail).find_by(id: id))
38
37
  super(current_user.account.users.find_by(id: id))
39
38
  end
40
39
 
@@ -89,40 +88,36 @@ module Lesli
89
88
  # TODO: Implement pg_search
90
89
  def index params
91
90
 
92
- # sql string to join to user_roles and get all the roles assigned to a user
93
- sql_string_for_user_roles = "left join (
94
- select
95
- ur.user_id, string_agg(r.\"name\", ', ') rolenames
96
- from lesli_user_roles ur
97
- join lesli_roles r
98
- on r.id = ur.role_id
99
- where ur.deleted_at is null
100
- group by ur.user_id
101
- ) roles on roles.user_id = lesli_users.id"
102
-
103
- # sql string to joing to user_sessions and get all the active sessions of a user
104
- sql_string_for_user_sessions = "left join (
105
- select
106
- max(last_used_at) as last_action_performed_at,
107
- user_id
108
- from lesli_user_sessions us
109
- where us.deleted_at is null
110
- group by(us.user_id)
111
- ) sessions on sessions.user_id = lesli_users.id"
91
+ # Build user full name
92
+ fullname_sql = "TRIM(CONCAT(lesli_users.first_name, ' ', lesli_users.last_name))"
93
+
94
+ # Build user status
95
+ status_sql = "case when lesli_users.active is true then 'active' else 'inactive' end"
96
+
97
+ # Get all the roles assigned to the user
98
+ roles_subquery = <<-SQL
99
+ LEFT JOIN (
100
+ SELECT ur.user_id, string_agg(r.name, ', ' ORDER BY r.name) as rolenames
101
+ FROM lesli_shield_user_roles ur
102
+ JOIN lesli_roles r ON r.id = ur.role_id
103
+ WHERE ur.deleted_at IS NULL
104
+ GROUP BY ur.user_id
105
+ ) roles ON roles.user_id = lesli_users.id
106
+ SQL
112
107
 
113
108
  current_user.account.users
114
- .joins(sql_string_for_user_roles)
115
- .joins(sql_string_for_user_sessions)
116
- .page(query[:pagination][:page])
117
- .per(query[:pagination][:perPage])
109
+ .joins(roles_subquery)
118
110
  .select(
119
111
  :id,
120
- "CONCAT(COALESCE(lesli_users.first_name, ''), ' ', COALESCE(lesli_users.last_name, '')) as fullname",
121
112
  :email,
122
113
  :active,
123
- :rolenames,
114
+ "#{status_sql} AS status",
115
+ "#{fullname_sql} AS fullname",
116
+ "COALESCE(roles.rolenames, '') AS rolenames",
124
117
  Date2.new.date_time.db_column("current_sign_in_at")
125
118
  )
119
+ .page(query.dig(:pagination, :page))
120
+ .per(query.dig(:pagination, :perPage))
126
121
  end
127
122
 
128
123
  # Creates a query that selects all user information from several tables if CloudLock is present
@@ -137,6 +132,12 @@ module Lesli
137
132
  # })
138
133
 
139
134
  if resource.update(params)
135
+
136
+ # Set the user alias based on the full_name.
137
+ if resource.alias.empty?
138
+ resource.update(alias: resource.full_name_initials())
139
+ end
140
+
140
141
  # new_attributes = resource.detail.attributes.merge({
141
142
  # active: resource.active
142
143
  # })
@@ -1,13 +1,13 @@
1
1
  <style>
2
+ html, body, * {
3
+ box-sizing: border-box;
4
+ }
5
+
2
6
  html, body {
3
7
  margin: 0;
4
8
  padding: 0;
5
9
  }
6
10
 
7
- html, body, * {
8
- box-sizing: border-box;
9
- }
10
-
11
11
  .page-welcome {
12
12
  display: flex;
13
13
  flex-direction: column;
@@ -59,11 +59,11 @@ html, body, * {
59
59
  align-items: center;
60
60
  }
61
61
 
62
- .buttons {
62
+ .page-welcome .buttons {
63
63
  margin-bottom: 2rem;
64
64
  }
65
65
 
66
- .buttons a {
66
+ .page-welcome .buttons a {
67
67
  border: none;
68
68
  display: inline-block;
69
69
  text-decoration: none;
@@ -79,25 +79,25 @@ html, body, * {
79
79
  line-height: 28px;
80
80
  }
81
81
 
82
- .buttons a.doc {
82
+ .page-welcome .buttons a.doc {
83
83
  background: #184fee;
84
84
  }
85
85
 
86
- .buttons a.start {
86
+ .page-welcome .buttons a.start {
87
87
  background: #444444;
88
88
  }
89
89
 
90
90
  @media only screen and (max-width: 600px) {
91
- .page-welcome svg {
91
+ .page-welcome .page-welcome svg {
92
92
  width: 250px;
93
93
  }
94
94
 
95
- .page-welcome h1 {
95
+ .page-welcome .page-welcome h1 {
96
96
  padding: 0 1rem;
97
97
  font-size: 2.7rem;
98
98
  }
99
99
 
100
- .buttons a {
100
+ .page-welcome .buttons a {
101
101
  padding: 14px 20px;
102
102
  }
103
103
  }
@@ -15,6 +15,7 @@ document.addEventListener('turbo:load', () => {
15
15
  :admin,
16
16
  :mailer,
17
17
  :bell,
18
+ :contacts,
18
19
  :calendar,
19
20
  :papers,
20
21
  :support,
@@ -27,7 +28,6 @@ document.addEventListener('turbo:load', () => {
27
28
 
28
29
  <%= render(LesliView::Layout::Container.new("lesli-apps", dashboard: false)) do %>
29
30
  <%= render("lesli_dashboard/dashboards/shared/header") if defined?(LesliDashboard) %>
30
-
31
31
  <section class="columns is-multiline is-mobile is-centered engines">
32
32
  <% @navigation_engines.each do |engine| %>
33
33
  <%= public_send("navigation_engine_#{engine}") %>
@@ -21,7 +21,7 @@
21
21
  <%= render(LesliView::Elements::Empty.new(text:nil)) %>
22
22
  </div>
23
23
 
24
- <h2 class="mb-4">404 Not found</h2>
24
+ <h2 class="mb-4">404 <%= I18n.t("lesli.shared.message_error_not_found") %></h2>
25
25
 
26
26
  <p>
27
27
  <%= @message %>
@@ -1,20 +1,20 @@
1
1
  <style>
2
- .error-401 .material-symbols {
2
+ .error-403 .material-symbols {
3
3
  font-size: 8rem;
4
- color: rgb(190,190,190);
4
+ color: #7a0000;
5
5
  }
6
- .error-401 h2 {
6
+ .error-403 h2 {
7
7
  font-size: 2rem;
8
8
  color: #7a0000;
9
9
  }
10
10
 
11
- .error-401 p {
11
+ .error-403 p {
12
12
  font-size: 1.4rem;
13
13
  font-weight: 300;
14
14
  }
15
15
  </style>
16
16
  <%= render(LesliView::Layout::Container.new("accounts")) do %>
17
- <section class="hero is-medium error-401">
17
+ <section class="hero is-medium error-403">
18
18
  <div class="hero-body has-text-centered">
19
19
 
20
20
  <div class="mb-6">
@@ -25,19 +25,17 @@
25
25
  </span>
26
26
  </div>
27
27
 
28
- <h2 class="mb-4">401 Unauthorized</h2>
28
+ <h2 class="mb-4">403 <%= I18n.t("lesli.shared.message_error_unauthorized") %></h2>
29
29
 
30
30
  <p>
31
- We could not validate your privileges.
32
- Please ask the administrator to send you an invite to see this content.
31
+ <%= I18n.t("lesli.shared.message_error_unauthorized_credentials") %>
33
32
  </p>
34
33
 
35
34
  <div class="box mt-6">
36
35
  <ul>
37
- <!--li><%= @error_object[:error_message] %></li-->
38
36
  <li>
39
- <%= @error_object.dig(:error_detail, :controller) %>
40
- (<%= @error_object.dig(:error_detail, :action) %>)
37
+ <%= @error_object.dig(:error_details, :controller) %>
38
+ (<%= @error_object.dig(:error_details, :action) %>)
41
39
  </li>
42
40
  <li><%= @error_object[:error_role] %></li>
43
41
  </ul>