lesli_admin 0.2.0 → 0.3.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.
@@ -29,4 +29,5 @@ Building a better future, one line of code at a time.
29
29
  // ·
30
30
  */
31
31
 
32
- body.lesli-admin-users { @import "users"; }
32
+ body.lesli-admin-users,
33
+ body.lesli-admin-profile { @import "users"; }
@@ -36,7 +36,7 @@ module LesliAdmin
36
36
  # GET /profiles/1
37
37
  def show
38
38
  respond_to do |format|
39
- format.html { }
39
+ format.html
40
40
  format.json { respond_with_successful(@profile.show) }
41
41
  end
42
42
  end
@@ -45,7 +45,7 @@ module LesliAdmin
45
45
 
46
46
  # Use callbacks to share common setup or constraints between actions.
47
47
  def set_profile
48
- @profile = Lesli::UserService.new(current_user, query).find(current_user.id)
48
+ @profile = UserService.new(current_user, query).find(current_user.id)
49
49
  end
50
50
 
51
51
  # Only allow a list of trusted parameters through.
@@ -31,23 +31,208 @@ Building a better future, one line of code at a time.
31
31
  =end
32
32
 
33
33
  module LesliAdmin
34
- class UsersController < ApplicationController
35
- #before_action :set_user, only: [:show]
34
+ class UsersController < Lesli::ApplicationLesliController
35
+ before_action :set_user, only: [
36
+ :show,
37
+ :update,
38
+ :destroy,
39
+ :requestpassword,
40
+ :passwordreset,
41
+ :revokeaccess,
42
+ :logout
43
+ ]
44
+
45
+ # GET /users/list
46
+ def list
47
+ respond_to do |format|
48
+ format.json {
49
+ respond_with_successful(UserService.new("current_user").list(query, params))
50
+ }
51
+ end
52
+ end
36
53
 
37
54
  # GET /users
38
55
  def index
56
+ respond_to do |format|
57
+ format.html
58
+ format.json {
59
+ return respond_with_pagination(UserService.new(current_user, query).index(params))
60
+ }
61
+ end
39
62
  end
40
63
 
41
64
  def show
65
+ respond_to do |format|
66
+ format.html
67
+ format.json {
68
+ return respond_with_not_found unless @user.found?
69
+ return respond_with_successful(@user.show)
70
+ }
71
+ end
42
72
  end
43
73
 
44
- def new
74
+ def create
75
+ user = UserService.new(current_user).create(user_params)
76
+ if user.successful?
77
+ respond_with_successful(user.result)
78
+ else
79
+ respond_with_error("Error creating user", user.errors)
80
+ end
45
81
  end
82
+
83
+ def update
46
84
 
47
- def edit
48
- end
85
+ # check if the user trully exists
86
+ return respond_with_not_found unless @user.found?
49
87
 
50
- private
88
+ # update the user information
89
+ @user.update(user_params)
51
90
 
91
+ # check saved
92
+ if @user.successful?
93
+ respond_with_successful(@user.result)
94
+ else
95
+ respond_with_error(@user.errors)
96
+ end
97
+ end
98
+
99
+ def destroy
100
+ return respond_with_not_found unless @user
101
+
102
+ # get the user found in the UserServices
103
+ user = @user.result
104
+
105
+ if user.delete
106
+ current_user.logs.create({ title: "deleted_user", description: "by_user_id: #{ current_user.email }" })
107
+ respond_with_successful()
108
+ else
109
+ respond_with_error(user.errors.full_messages.to_sentence)
110
+ end
111
+ end
112
+
113
+ # Force the user to update his password
114
+ def requestpassword
115
+ if @user.request_password
116
+ respond_with_successful
117
+ else
118
+ respond_with_error
119
+ end
120
+ end
121
+
122
+ # Reset password (generate random)
123
+ def passwordreset
124
+
125
+ pass = @user.password_reset
126
+
127
+ if pass
128
+ respond_with_successful(pass)
129
+ else
130
+ respond_with_error
131
+ end
132
+ end
133
+
134
+ # this method is going to close all the open user sessions
135
+ def logout
136
+ if @user.logout
137
+ respond_with_successful
138
+ else
139
+ respond_with_error
140
+ end
141
+ end
142
+
143
+ # Remove all user access
144
+ def revokeaccess
145
+ if @user.revoke_access
146
+ respond_with_successful
147
+ else
148
+ respond_with_error
149
+ end
150
+ end
151
+
152
+ def become
153
+
154
+ # always should be disabled
155
+ if Rails.configuration.lesli.dig(:security, :enable_becoming) != true
156
+ return respond_with_unauthorized
157
+ end
158
+
159
+ # Allow only sysadmin to become as user
160
+ return respond_with_unauthorized if current_user.email != Rails.application.config.lesli.dig(:account, :user, :email) # sysadmin user
161
+
162
+ # Search for desire user
163
+ becoming_user = User.find(params[:id])
164
+
165
+ # Return an error if user does not exist
166
+ return respond_with_error I18n.t("core.shared.messages_warning_user_not_found") if becoming_user.blank?
167
+
168
+ # Extrictly save a log when becoming
169
+ current_user.activities.create!({
170
+ users_id: becoming_user.id,
171
+ owner_id: current_user.id,
172
+ description: "#{current_user.full_name} -> #{becoming_user.full_name}",
173
+ category: "action_become"
174
+ })
175
+
176
+ # Sign in as the becoming user
177
+ sign_in(:user, becoming_user)
178
+
179
+ # Create a new session for the becoming user
180
+ becoming_session = becoming_user.sessions.create({
181
+ :user_agent => get_user_agent,
182
+ :user_remote => request.remote_ip,
183
+ :session_source => "become_session",
184
+ :last_used_at => LC::Date.now,
185
+ :expiration_at => 60.minutes.from_now
186
+ })
187
+
188
+ # assign the session of the becomer user to the becoming user
189
+ session[:user_session_id] = becoming_session[:id]
190
+
191
+ # Response successful
192
+ respond_with_successful([current_user, becoming_user])
193
+
194
+ end
195
+
196
+ def options
197
+ respond_with_successful({
198
+ #regions: current_user.account.locations.where(level: "region"),
199
+ salutations: User::Detail.salutations.map {|k, v| {value: k, text: v}},
200
+ locales: Rails.application.config.lesli.dig(:configuration, :locales_available),
201
+ mfa_methods: Rails.application.config.lesli.dig(:configuration, :mfa_methods)
202
+ })
203
+ end
204
+
205
+ private
206
+
207
+ # @return [void]
208
+ # @description Sets the requested user based on the current_users's account
209
+ # @example
210
+ # # Executing this method from a controller action:
211
+ # set_user
212
+ # puts @user
213
+ # # This will either display nil or an instance of Account::User
214
+ def set_user
215
+ @user = UserService.new(current_user).find(params[:id])
216
+ end
217
+
218
+ def user_params
219
+ params.require(:user).permit(
220
+ :active,
221
+ :email,
222
+ :alias,
223
+ :roles_id,
224
+ :first_name,
225
+ :last_name,
226
+ :telephone,
227
+ detail_attributes: [
228
+ :title,
229
+ :salutation,
230
+ :address,
231
+ :work_city,
232
+ :work_region,
233
+ :work_address
234
+ ]
235
+ )
236
+ end
52
237
  end
53
238
  end
@@ -31,36 +31,8 @@ Building a better future, one line of code at a time.
31
31
  =end
32
32
 
33
33
  module LesliAdmin
34
- class Account < Lesli::Account
35
-
36
- # accounts always belongs to a user
37
- belongs_to :user, optional: true
38
-
39
- # account resources
40
- has_many :users
41
-
42
- has_one :bell, class_name: "LesliBell::Account"
43
-
44
-
45
-
46
- # account statuses
47
- enum status: [
48
- :registered,
49
- :onboarding,
50
- :active,
51
- :suspended
52
- ]
53
-
54
-
55
- # company region (GDPR)
56
- enum region: {
57
- latin_america: "latin_america",
58
- united_states: "united_states",
59
- european_union: "european_union"
60
- }
61
-
62
-
63
- # required a name for the lesli account
64
- validates :company_name, :presence => true
34
+ class Account < ApplicationRecord
35
+ belongs_to :account, class_name: "Lesli::Account"
36
+ has_many :users, class_name: "Lesli::User"
65
37
  end
66
38
  end
@@ -0,0 +1,265 @@
1
+ =begin
2
+
3
+ Lesli
4
+
5
+ Copyright (c) 2023, Lesli Technologies, S. A.
6
+
7
+ This program is free software: you can redistribute it and/or modify
8
+ it under the terms of the GNU General Public License as published by
9
+ the Free Software Foundation, either version 3 of the License, or
10
+ (at your option) any later version.
11
+
12
+ This program is distributed in the hope that it will be useful,
13
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ GNU General Public License for more details.
16
+
17
+ You should have received a copy of the GNU General Public License
18
+ along with this program. If not, see http://www.gnu.org/licenses/.
19
+
20
+ Lesli · Ruby on Rails SaaS Development Framework.
21
+
22
+ Made with ♥ by https://www.lesli.tech
23
+ Building a better future, one line of code at a time.
24
+
25
+ @contact hello@lesli.tech
26
+ @website https://www.lesli.tech
27
+ @license GPLv3 http://www.gnu.org/licenses/gpl-3.0.en.html
28
+
29
+ // · ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~
30
+ // ·
31
+ =end
32
+
33
+ module LesliAdmin
34
+ class UserService < Lesli::ApplicationLesliService
35
+
36
+ # @return [Array] Paginated index of users.
37
+ # @description Return a paginated array of users, used mostly in frontend views
38
+ # TODO: Implement pg_search
39
+ def index params
40
+
41
+ # sql string to join to user_roles and get all the roles assigned to a user
42
+ sql_string_for_user_roles = "left join (
43
+ select
44
+ ur.user_id, string_agg(r.\"name\", ', ') rolenames
45
+ from user_roles ur
46
+ join roles r
47
+ on r.id = ur.role_id
48
+ where ur.deleted_at is null
49
+ group by ur.user_id
50
+ ) roles on roles.user_id = users.id"
51
+
52
+ # sql string to joing to user_sessions and get all the active sessions of a user
53
+ sql_string_for_user_sessions = "left join (
54
+ select
55
+ max(last_used_at) as last_action_performed_at,
56
+ user_id
57
+ from user_sessions us
58
+ where us.deleted_at is null
59
+ group by(us.user_id)
60
+ ) sessions on sessions.user_id = users.id"
61
+
62
+ #users = current_user.account.users
63
+ users = Lesli::Account.first.users
64
+ #.joins(sql_string_for_user_roles)
65
+ #.joins(sql_string_for_user_sessions)
66
+ users = users.page(query[:pagination][:page])
67
+ .per(query[:pagination][:perPage])
68
+ .order("#{query[:order][:by]} #{query[:order][:dir]} NULLS LAST")
69
+
70
+ users.select(
71
+ :id,
72
+ "CONCAT(COALESCE(first_name, ''), ' ', COALESCE(last_name, '')) as name",
73
+ :email,
74
+ :active,
75
+ #:rolenames,
76
+ #Date2.new.date_time.db_column("current_sign_in_at"),
77
+ #Date2.new.date_time.db_column("last_action_performed_at")
78
+ )
79
+
80
+ end
81
+
82
+
83
+ # Creates a query that selects all user information from several tables if CloudLock is present
84
+ def show
85
+
86
+ user = resource
87
+
88
+ return {
89
+ id: user[:id],
90
+ email: user[:email],
91
+ alias: user[:alias],
92
+ active: user[:active],
93
+ full_name: user.full_name,
94
+ salutation: user[:salutation],
95
+ first_name: user[:first_name],
96
+ last_name: user[:last_name],
97
+ telephone: user[:telephone],
98
+
99
+ #locale: user.settings.select(:value).find_by(:name => "locale"),
100
+
101
+ #roles: user.roles.map { |r| { id: r[:id], name: r[:name], permission_level: r[:object_level_permission]} },
102
+
103
+ #mfa_enabled: user.mfa_settings[:enabled],
104
+ #mfa_method: user.mfa_settings[:method],
105
+
106
+ created_at: user[:created_at],
107
+ updated_at: user[:updated_at],
108
+ detail_attributes: {
109
+ title: user.detail&[:title] || "",
110
+ # address: user.detail[:address],
111
+ # work_city: user.detail[:work_city],
112
+ # work_region: user.detail[:work_region],
113
+ # work_address: user.detail[:work_address]
114
+ }
115
+ }
116
+ end
117
+
118
+ def create user_params
119
+
120
+ # check if request has an email to create the user
121
+ if user_params[:email].blank?
122
+ self.error(I18n.t("core.users.messages_danger_not_valid_email_found"))
123
+ end
124
+
125
+
126
+ # register the new user
127
+ user = User.new({
128
+ :active => true,
129
+ :email => user_params[:email],
130
+ :alias => user_params[:alias] || "",
131
+ :first_name => user_params[:first_name] || "",
132
+ :last_name => user_params[:last_name] || "",
133
+ :telephone => user_params[:telephone] || "",
134
+ #:detail_attributes => user_params[:detail_attributes] || {}
135
+ })
136
+
137
+
138
+
139
+ # assign a random password
140
+ user.password = Devise.friendly_token
141
+
142
+ # enrol user to my own account
143
+ user.account = current_user.account
144
+
145
+ # users created through the administration area does not need to confirm their accounts
146
+ # instead we send a password reset link, so they can have access to the platform
147
+ #user.confirm
148
+
149
+ if user.save
150
+
151
+ # if a role is provided to assign to the new user
152
+ # unless user_params[:roles_id].blank?
153
+ # # check if current user can work with the sent role
154
+ # if current_user.can_work_with_role?(user_params[:roles_id])
155
+ # # Search the role assigned
156
+ # role = current_user.account.roles.find_by(id: user_params[:roles_id])
157
+ # # assign role to the new user
158
+ # user.user_roles.create({ role: role })
159
+ # end
160
+ # end
161
+
162
+ # role validation - if new user does not have any role assigned
163
+ # if user.roles.blank?
164
+
165
+ # default_role_id = current_user.account.settings.find_by(:name => "default_role_id")&.value
166
+ # owner_role_id = current_user.account.roles.find_by(:name => "owner").id
167
+ # if default_role_id.present? && default_role_id != owner_role_id
168
+ # # assign default role
169
+ # user.user_roles.create({ role: current_user.account.roles.find_by(:id => default_role_id)})
170
+
171
+ # else
172
+ # # assign limited role
173
+ # user.user_roles.create({ role: current_user.account.roles.find_by(:name => "limited") })
174
+ # end
175
+ # end
176
+
177
+ # saving logs with information about the creation of the user
178
+ # user.logs.create({ title: "user_created_at", description: Date2.new.date_time.to_s })
179
+ # user.logs.create({ title: "user_created_by", description: current_user.email })
180
+ # user.logs.create({ title: "user_created_with_role", description: user.user_roles.first.role.name + " " + user.user_roles.first.role.id.to_s})
181
+ # User.log_activity_create(current_user, user)
182
+
183
+ self.resource = user
184
+
185
+ begin
186
+ # users created through the administration area does not need to confirm their accounts
187
+ # instead we send a password reset link, so they can have access to the platform
188
+ #UserMailer.with(user: user).invitation_instructions.deliver_now
189
+ rescue => exception
190
+ #Honeybadger.notify(exception)
191
+ #user.logs.create({ title: "user_creation_email_failed ", description: exception.message })
192
+ end
193
+
194
+ else
195
+ self.error(user.errors.full_messages.to_sentence)
196
+ end
197
+
198
+ self
199
+
200
+ end
201
+
202
+ def update params
203
+
204
+ # old_attributes = resource.detail.attributes.merge({
205
+ # active: resource.active
206
+ # })
207
+
208
+ if resource.update(params)
209
+ # new_attributes = resource.detail.attributes.merge({
210
+ # active: resource.active
211
+ # })
212
+ #resource.log_activity_update(current_user, resource, old_attributes, new_attributes)
213
+ else
214
+ self.error(resource.errors.full_messages.to_sentence)
215
+ end
216
+
217
+ self
218
+ end
219
+
220
+
221
+ # force the user to change the password (at next login)
222
+ def request_password
223
+
224
+ # expire password
225
+ resource.set_password_as_expired
226
+
227
+ resource.logs.create({ title: "request_password", description: "by_user: " + current_user.email })
228
+ end
229
+
230
+
231
+ # generate a random password for the user
232
+ def password_reset
233
+
234
+ # generate random password
235
+ pass = resource.password_reset
236
+
237
+ resource.logs.create({ title: "password_reset", description: "by_user: " + current_user.email })
238
+
239
+ pass
240
+ end
241
+
242
+ def logout
243
+ # delete user active sessions
244
+ resource.sessions.destroy_all
245
+
246
+ resource.logs.create({ title: "close_sessions", description: "by_user: " + current_user.email })
247
+ end
248
+
249
+ def revoke_access
250
+
251
+ # delete user active sessions
252
+ self.logout
253
+
254
+ # add delete date to the last active session
255
+ resource.revoke_access
256
+
257
+ resource.logs.create({ title: "revoke_access", description: "by_user: " + current_user.email })
258
+ end
259
+
260
+ def find id
261
+ #super(current_user.account.users.joins(:detail).find_by(id: id))
262
+ super(current_user.account.users.find_by(id: id))
263
+ end
264
+ end
265
+ end
@@ -1,6 +1,5 @@
1
1
  ---
2
2
  :en:
3
- users:
4
- title_users: Users
5
- dashboards:
6
- button_my: My button in my dashboard
3
+ lesli_admin:
4
+ shared:
5
+ button_save: Save
@@ -1,6 +1,5 @@
1
1
  ---
2
2
  :es:
3
- users:
4
- title_users: Usuarios
5
- dashboards:
6
- button_my: Mi boton en mi dashboard
3
+ lesli_admin:
4
+ shared:
5
+ button_save: Guardar
data/config/routes.rb CHANGED
@@ -3,7 +3,7 @@ LesliAdmin::Engine.routes.draw do
3
3
  root to: "dashboards#show"
4
4
  resource :dashboard, only: [:show]
5
5
 
6
- resource :profile
6
+ resource :profile, only: [:show]
7
7
 
8
8
  resource :account, only: [:show]
9
9
  resources :users, only: [:index, :show, :new]
@@ -0,0 +1,42 @@
1
+ =begin
2
+
3
+ Lesli
4
+
5
+ Copyright (c) 2023, Lesli Technologies, S. A.
6
+
7
+ This program is free software: you can redistribute it and/or modify
8
+ it under the terms of the GNU General Public License as published by
9
+ the Free Software Foundation, either version 3 of the License, or
10
+ (at your option) any later version.
11
+
12
+ This program is distributed in the hope that it will be useful,
13
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ GNU General Public License for more details.
16
+
17
+ You should have received a copy of the GNU General Public License
18
+ along with this program. If not, see http://www.gnu.org/licenses/.
19
+
20
+ Lesli · Ruby on Rails SaaS Development Framework.
21
+
22
+ Made with ♥ by https://www.lesli.tech
23
+ Building a better future, one line of code at a time.
24
+
25
+ @contact hello@lesli.tech
26
+ @website https://www.lesli.tech
27
+ @license GPLv3 http://www.gnu.org/licenses/gpl-3.0.en.html
28
+
29
+ // · ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~
30
+ // ·
31
+ =end
32
+
33
+ class CreateLesliAdminAccounts < ActiveRecord::Migration[6.0]
34
+ def change
35
+ create_table :lesli_admin_accounts do |t|
36
+ t.integer :status
37
+ t.datetime :deleted_at, index: true
38
+ t.timestamps
39
+ end
40
+ add_reference(:lesli_admin_accounts, :account, foreign_key: { to_table: :lesli_accounts })
41
+ end
42
+ end
@@ -1,4 +1,4 @@
1
1
  module LesliAdmin
2
- VERSION = "0.2.0"
3
- BUILD = "1696737766"
2
+ VERSION = "0.3.0"
3
+ BUILD = "1697000148"
4
4
  end
@@ -36,7 +36,7 @@ import { ref, reactive, onMounted, watch, computed } from "vue"
36
36
 
37
37
 
38
38
  // · import lesli stores
39
- import { useAccount } from "LesliAdmin/stores/account"
39
+ import { useAccount } from "Lesli/stores/account"
40
40
 
41
41
 
42
42
  // · implement stores
@@ -36,7 +36,7 @@ import { computed, onMounted } from "vue"
36
36
 
37
37
 
38
38
  // · import lesli stores
39
- import { useAccount } from "LesliAdmin/stores/account"
39
+ import { useAccount } from "Lesli/stores/account"
40
40
 
41
41
 
42
42
  // · import account components
@@ -6,7 +6,6 @@ import { lesliChartLine } from "lesli-vue/components"
6
6
  <template>
7
7
  <lesli-application-container>
8
8
  <lesli-header title="Dashboard"></lesli-header>
9
-
10
9
  <div class="columns">
11
10
  <div class="column">
12
11
  <lesli-application-component>