lesli 5.1.12 → 5.1.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Rakefile +3 -0
- data/app/assets/stylesheets/lesli/application.css +43 -3
- data/app/controllers/lesli/application_devise_controller.rb +9 -5
- data/app/controllers/lesli/application_lesli_controller.rb +4 -0
- data/app/controllers/lesli/items/attachments_controller.rb +200 -0
- data/app/controllers/lesli/items/discussions_controller.rb +94 -67
- data/app/controllers/lesli/items/tasks_controller.rb +132 -62
- data/app/helpers/lesli/turbo_helper.rb +0 -18
- data/app/interfaces/lesli/responder_interface.rb +58 -38
- data/app/mailers/lesli/application_lesli_mailer.rb +34 -8
- data/app/models/concerns/lesli/account_initializer.rb +11 -3
- data/app/models/concerns/lesli/account_logs.rb +5 -16
- data/app/models/concerns/lesli/items/activities.rb +29 -33
- data/app/models/concerns/lesli/items/attachments.rb +88 -0
- data/app/models/concerns/lesli/items/discussions.rb +25 -11
- data/app/models/concerns/lesli/items/ownership.rb +27 -0
- data/app/models/concerns/lesli/items/tasks.rb +26 -6
- data/app/models/concerns/lesli/user_extensions.rb +5 -9
- data/app/models/lesli/account.rb +10 -14
- data/app/models/lesli/items/activity.rb +25 -2
- data/app/models/lesli/items/attachment.rb +117 -0
- data/app/models/lesli/items/discussion.rb +6 -2
- data/app/models/lesli/items/task.rb +3 -1
- data/app/models/lesli/user.rb +12 -21
- data/app/views/lesli/abouts/welcome.html.erb +2 -5
- data/app/views/lesli/errors/not_found.html.erb +7 -21
- data/app/views/lesli/errors/unauthorized.html.erb +11 -27
- data/app/views/lesli/layouts/application-devise.html.erb +10 -9
- data/app/views/lesli/layouts/application-lesli.html.erb +1 -1
- data/app/views/lesli/partials/_application-lesli-assets.html.erb +3 -3
- data/app/views/lesli/partials/_application-lesli-header.html.erb +5 -9
- data/config/initializers/lesli.rb +4 -1
- data/config/locales/translations.en.yml +34 -0
- data/config/locales/translations.es.yml +34 -0
- data/db/seed/accounts.rb +5 -5
- data/db/seed/users.rb +8 -16
- data/db/seed/xyz.rb +38 -17
- data/lib/lesli/engine.rb +0 -4
- data/lib/lesli/router.rb +5 -2
- data/lib/lesli/version.rb +2 -2
- data/lib/migrate/items/activity_structure.rb +8 -8
- data/lib/migrate/items/attachment_structure.rb +9 -14
- data/lib/migrate/items/discussion_structure.rb +5 -5
- data/lib/migrate/items/task_structure.rb +2 -3
- data/lib/migrate/shared/account_structure.rb +1 -1
- data/lib/tasks/lesli_tasks.rake +2 -3
- data/readme.md +3 -3
- data/test/fixtures/lesli_accounts.yml +18 -0
- data/test/fixtures/lesli_users.yml +23 -0
- metadata +17 -25
- data/app/assets/stylesheets/lesli/application.tailwind.css +0 -33
- data/config/brakeman.yml +0 -44
- data/config/initializers/devise.rb +0 -27
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
=begin
|
|
2
|
+
|
|
3
|
+
Lesli
|
|
4
|
+
|
|
5
|
+
Copyright (c) 2026, 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 LesliTech
|
|
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 Lesli
|
|
34
|
+
module Items
|
|
35
|
+
module Attachments
|
|
36
|
+
extend ActiveSupport::Concern
|
|
37
|
+
include Lesli::Items::Ownership
|
|
38
|
+
|
|
39
|
+
included do
|
|
40
|
+
class_attribute :lesli_attachments_class, instance_accessor: false
|
|
41
|
+
lesli_attachments_setup
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
class_methods do
|
|
45
|
+
def is_lesli_attachable?
|
|
46
|
+
true
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def lesli_attachments_setup(
|
|
50
|
+
use: nil,
|
|
51
|
+
as: :attachable,
|
|
52
|
+
association_name: :attachments
|
|
53
|
+
)
|
|
54
|
+
klass = if use
|
|
55
|
+
use.to_s.constantize
|
|
56
|
+
else
|
|
57
|
+
"#{name.deconstantize}::Items::Attachment".constantize
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
self.lesli_attachments_class = klass
|
|
61
|
+
|
|
62
|
+
expected_table = lesli_expected_attachments_table_name_for(klass)
|
|
63
|
+
klass.table_name = expected_table if klass.table_name != expected_table
|
|
64
|
+
|
|
65
|
+
has_many(
|
|
66
|
+
association_name,
|
|
67
|
+
-> { order(created_at: :desc) },
|
|
68
|
+
as: as,
|
|
69
|
+
class_name: klass.name,
|
|
70
|
+
dependent: :destroy
|
|
71
|
+
)
|
|
72
|
+
rescue NameError
|
|
73
|
+
raise NameError,
|
|
74
|
+
"Attachment class not found for #{name}. " \
|
|
75
|
+
"Expected #{name.deconstantize}::Items::Attachment or pass `use:`"
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
private
|
|
79
|
+
|
|
80
|
+
def lesli_expected_attachments_table_name_for(klass)
|
|
81
|
+
parts = klass.name.split("::").map(&:underscore)
|
|
82
|
+
parts.join("_").pluralize
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -34,9 +34,13 @@ module Lesli
|
|
|
34
34
|
module Items
|
|
35
35
|
module Discussions
|
|
36
36
|
extend ActiveSupport::Concern
|
|
37
|
+
include Lesli::Items::Ownership
|
|
37
38
|
|
|
38
39
|
included do
|
|
39
40
|
class_attribute :lesli_discussions_class, instance_accessor: false
|
|
41
|
+
class_attribute :lesli_discussions_association_name,
|
|
42
|
+
instance_accessor: false,
|
|
43
|
+
default: :discussions
|
|
40
44
|
lesli_discussions_setup
|
|
41
45
|
end
|
|
42
46
|
|
|
@@ -47,8 +51,7 @@ module Lesli
|
|
|
47
51
|
true
|
|
48
52
|
end
|
|
49
53
|
|
|
50
|
-
#
|
|
51
|
-
# engine model that is implementing the task items
|
|
54
|
+
# Resolve the concrete discussion model for the engine.
|
|
52
55
|
def lesli_discussions_setup(use: nil, as: :discussable, association_name: :discussions)
|
|
53
56
|
klass = if use
|
|
54
57
|
use.to_s.constantize
|
|
@@ -57,6 +60,7 @@ module Lesli
|
|
|
57
60
|
end
|
|
58
61
|
|
|
59
62
|
self.lesli_discussions_class = klass
|
|
63
|
+
self.lesli_discussions_association_name = association_name.to_sym
|
|
60
64
|
|
|
61
65
|
# ✅ Auto-fix table name (works even when engine defines table_name_prefix)
|
|
62
66
|
expected_table = lesli_expected_discussions_table_name_for(klass)
|
|
@@ -65,13 +69,8 @@ module Lesli
|
|
|
65
69
|
klass.table_name = expected_table
|
|
66
70
|
end
|
|
67
71
|
|
|
68
|
-
# Set associations
|
|
69
|
-
# Example:
|
|
70
|
-
# class LesliSupport::Ticket
|
|
71
|
-
# has_many :lesli_support_tasks
|
|
72
|
-
# end
|
|
73
72
|
has_many(association_name,
|
|
74
|
-
-> {
|
|
73
|
+
-> { order(created_at: :desc, id: :desc) },
|
|
75
74
|
as: as,
|
|
76
75
|
class_name: klass.name,
|
|
77
76
|
dependent: :destroy)
|
|
@@ -79,14 +78,29 @@ module Lesli
|
|
|
79
78
|
|
|
80
79
|
private
|
|
81
80
|
|
|
82
|
-
# Build: "
|
|
81
|
+
# Build: "lesli_support_items_discussions" from
|
|
82
|
+
# "LesliSupport::Items::Discussion"
|
|
83
83
|
#
|
|
84
84
|
# We DO NOT rely on ActiveRecord's table_name_prefix, because that’s what breaks your case.
|
|
85
85
|
def lesli_expected_discussions_table_name_for(klass)
|
|
86
|
-
parts = klass.name.split("::").map(&:underscore)
|
|
87
|
-
"#{parts.join('_').pluralize}"
|
|
86
|
+
parts = klass.name.split("::").map(&:underscore)
|
|
87
|
+
"#{parts.join('_').pluralize}"
|
|
88
88
|
end
|
|
89
89
|
end
|
|
90
|
+
|
|
91
|
+
def add_comment!(author:, message:)
|
|
92
|
+
discussion_association.create!(
|
|
93
|
+
author: author,
|
|
94
|
+
account: lesli_item_account,
|
|
95
|
+
message: message
|
|
96
|
+
)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
private
|
|
100
|
+
|
|
101
|
+
def discussion_association
|
|
102
|
+
public_send(self.class.lesli_discussions_association_name)
|
|
103
|
+
end
|
|
90
104
|
end
|
|
91
105
|
end
|
|
92
106
|
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
module Lesli
|
|
2
|
+
module Items
|
|
3
|
+
module Ownership
|
|
4
|
+
extend ActiveSupport::Concern
|
|
5
|
+
|
|
6
|
+
# Item records use the same account as their parent resource. For
|
|
7
|
+
# engine resources this is an Engine::Account; Core resources may
|
|
8
|
+
# return Lesli::Account directly.
|
|
9
|
+
def lesli_item_account
|
|
10
|
+
account
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def lesli_item_accessible_by?(user)
|
|
14
|
+
return false if user.blank?
|
|
15
|
+
|
|
16
|
+
item_account = lesli_item_account
|
|
17
|
+
core_account = if item_account.respond_to?(:account)
|
|
18
|
+
item_account.account
|
|
19
|
+
else
|
|
20
|
+
item_account
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
core_account == user.account
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -34,9 +34,13 @@ module Lesli
|
|
|
34
34
|
module Items
|
|
35
35
|
module Tasks
|
|
36
36
|
extend ActiveSupport::Concern
|
|
37
|
+
include Lesli::Items::Ownership
|
|
37
38
|
|
|
38
39
|
included do
|
|
39
40
|
class_attribute :lesli_tasks_class, instance_accessor: false
|
|
41
|
+
class_attribute :lesli_tasks_association_name,
|
|
42
|
+
instance_accessor: false,
|
|
43
|
+
default: :tasks
|
|
40
44
|
lesli_tasks_setup
|
|
41
45
|
end
|
|
42
46
|
|
|
@@ -57,6 +61,7 @@ module Lesli
|
|
|
57
61
|
end
|
|
58
62
|
|
|
59
63
|
self.lesli_tasks_class = klass
|
|
64
|
+
self.lesli_tasks_association_name = association_name.to_sym
|
|
60
65
|
|
|
61
66
|
# ✅ Auto-fix table name (works even when engine defines table_name_prefix)
|
|
62
67
|
expected_table = lesli_expected_task_table_name_for(klass)
|
|
@@ -71,25 +76,40 @@ module Lesli
|
|
|
71
76
|
# has_many :lesli_support_tasks
|
|
72
77
|
# end
|
|
73
78
|
has_many(association_name,
|
|
74
|
-
-> {
|
|
79
|
+
-> { order(created_at: :desc) },
|
|
75
80
|
as: as,
|
|
76
81
|
class_name: klass.name,
|
|
77
82
|
dependent: :destroy)
|
|
78
83
|
rescue NameError
|
|
79
84
|
raise NameError,
|
|
80
|
-
"Task class not found for #{name}. Expected #{name.deconstantize}::Task or pass `use:`"
|
|
85
|
+
"Task class not found for #{name}. Expected #{name.deconstantize}::Items::Task or pass `use:`"
|
|
81
86
|
end
|
|
82
87
|
|
|
83
88
|
private
|
|
84
89
|
|
|
85
|
-
# Build
|
|
90
|
+
# Build "lesli_support_items_tasks" from
|
|
91
|
+
# "LesliSupport::Items::Task".
|
|
86
92
|
#
|
|
87
|
-
#
|
|
93
|
+
# Do not rely on Active Record's table_name_prefix here.
|
|
88
94
|
def lesli_expected_task_table_name_for(klass)
|
|
89
|
-
parts = klass.name.split("::").map(&:underscore)
|
|
90
|
-
|
|
95
|
+
parts = klass.name.split("::").map(&:underscore)
|
|
96
|
+
parts.join("_").pluralize
|
|
91
97
|
end
|
|
92
98
|
end
|
|
99
|
+
|
|
100
|
+
def add_task!(creator:, title:)
|
|
101
|
+
task_association.create!(
|
|
102
|
+
user: creator,
|
|
103
|
+
account: lesli_item_account,
|
|
104
|
+
title: title
|
|
105
|
+
)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
private
|
|
109
|
+
|
|
110
|
+
def task_association
|
|
111
|
+
public_send(self.class.lesli_tasks_association_name)
|
|
112
|
+
end
|
|
93
113
|
end
|
|
94
114
|
end
|
|
95
115
|
end
|
|
@@ -94,22 +94,18 @@ module Lesli
|
|
|
94
94
|
subject:nil # resource related to the log
|
|
95
95
|
)
|
|
96
96
|
|
|
97
|
-
return unless defined?(LesliAudit)
|
|
97
|
+
return unless defined?(LesliAudit::ActivityAudit)
|
|
98
98
|
|
|
99
|
-
|
|
99
|
+
LesliAudit::ActivityAudit.record_user!(
|
|
100
|
+
user: self,
|
|
100
101
|
engine: engine,
|
|
101
102
|
source: source,
|
|
102
103
|
action: action,
|
|
103
|
-
|
|
104
104
|
operation: operation,
|
|
105
105
|
description: description,
|
|
106
106
|
session_id: session_id,
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
subject_id: subject&.id,
|
|
110
|
-
|
|
111
|
-
account: self&.account&.audit
|
|
112
|
-
})
|
|
107
|
+
subject: subject
|
|
108
|
+
)
|
|
113
109
|
end
|
|
114
110
|
end
|
|
115
111
|
end
|
data/app/models/lesli/account.rb
CHANGED
|
@@ -45,26 +45,21 @@ module Lesli
|
|
|
45
45
|
has_many :settings
|
|
46
46
|
has_many :locations
|
|
47
47
|
has_many :currencies
|
|
48
|
-
has_many :requests, class_name: "LesliAudit::AccountRequest"
|
|
49
|
-
has_many :logs, class_name: "LesliAudit::AccountLog"
|
|
50
48
|
|
|
51
49
|
|
|
52
50
|
# third-party engines associations
|
|
53
|
-
has_one :bell, class_name: "LesliBell::Account"
|
|
54
|
-
has_one :
|
|
55
|
-
has_one :audit, class_name: "LesliAudit::Account"
|
|
56
|
-
has_one :
|
|
57
|
-
has_one :guard, class_name: "LesliGuard::Account"
|
|
58
|
-
has_one :shield, class_name: "LesliShield::Account"
|
|
51
|
+
has_one :bell, class_name: "LesliBell::Account", foreign_key: :account_id, inverse_of: :account
|
|
52
|
+
has_one :admin, class_name: "LesliAdmin::Account", foreign_key: :account_id, inverse_of: :account
|
|
53
|
+
has_one :audit, class_name: "LesliAudit::Account", foreign_key: :account_id, inverse_of: :account
|
|
54
|
+
has_one :shield, class_name: "LesliShield::Account", foreign_key: :account_id, inverse_of: :account
|
|
59
55
|
has_one :letter, class_name: "LesliLetter::Account"
|
|
60
56
|
has_one :papers, class_name: "LesliPapers::Account"
|
|
61
|
-
has_one :
|
|
62
|
-
has_one :
|
|
63
|
-
has_one :
|
|
64
|
-
has_one :
|
|
65
|
-
|
|
57
|
+
has_one :mailer, class_name: "LesliMailer::Account", foreign_key: :account_id, inverse_of: :account
|
|
58
|
+
has_one :support, class_name: "LesliSupport::Account", foreign_key: :account_id, inverse_of: :account
|
|
59
|
+
has_one :contacts, class_name: "LesliContacts::Account", foreign_key: :account_id, inverse_of: :account
|
|
60
|
+
has_one :calendar, class_name: "LesliCalendar::Account", foreign_key: :account_id, inverse_of: :account
|
|
61
|
+
has_one :dashboard, class_name: "LesliDashboard::Account", foreign_key: :account_id, inverse_of: :account
|
|
66
62
|
|
|
67
|
-
validates :status, presence: true
|
|
68
63
|
|
|
69
64
|
# account statuses
|
|
70
65
|
enum :status, {
|
|
@@ -83,6 +78,7 @@ module Lesli
|
|
|
83
78
|
|
|
84
79
|
# required a name for the lesli account
|
|
85
80
|
validates :name, :presence => true
|
|
81
|
+
validates :status, presence: true
|
|
86
82
|
|
|
87
83
|
|
|
88
84
|
# initializers for new accounts
|
|
@@ -34,9 +34,32 @@ module Lesli
|
|
|
34
34
|
module Items
|
|
35
35
|
class Activity < ApplicationRecord
|
|
36
36
|
self.abstract_class = true
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
|
|
38
|
+
# Activity ownership contract:
|
|
39
|
+
# - subject is the resource affected by the event.
|
|
40
|
+
# - actor is the Lesli user who performed the event. The existing
|
|
41
|
+
# user_id column is kept as the foreign key.
|
|
42
|
+
# - account is declared by the concrete engine model.
|
|
43
|
+
# - activity_code is a semantic event name, not a column name.
|
|
44
|
+
belongs_to :actor, class_name: "Lesli::User", foreign_key: :user_id
|
|
39
45
|
belongs_to :subject, polymorphic: true
|
|
46
|
+
|
|
47
|
+
validate :metadata_is_an_object
|
|
48
|
+
validates :activity_type, presence: true
|
|
49
|
+
validates :activity_code,
|
|
50
|
+
presence: true,
|
|
51
|
+
format: {
|
|
52
|
+
with: /\A[a-z][a-z0-9_]*\z/,
|
|
53
|
+
message: "must be a semantic snake_case event name"
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
private
|
|
57
|
+
|
|
58
|
+
def metadata_is_an_object
|
|
59
|
+
return if metadata.is_a?(Hash)
|
|
60
|
+
|
|
61
|
+
errors.add(:metadata, "must be a JSON object")
|
|
62
|
+
end
|
|
40
63
|
end
|
|
41
64
|
end
|
|
42
65
|
end
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
=begin
|
|
2
|
+
|
|
3
|
+
Lesli
|
|
4
|
+
|
|
5
|
+
Copyright (c) 2026, 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 LesliTech
|
|
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 Lesli
|
|
34
|
+
module Items
|
|
35
|
+
class Attachment < ApplicationLesliRecord
|
|
36
|
+
DEFAULT_MAX_FILE_SIZE = 25.megabytes
|
|
37
|
+
DEFAULT_CONTENT_TYPES = %w[
|
|
38
|
+
application/pdf
|
|
39
|
+
application/msword
|
|
40
|
+
application/vnd.ms-excel
|
|
41
|
+
application/vnd.ms-powerpoint
|
|
42
|
+
application/vnd.openxmlformats-officedocument.presentationml.presentation
|
|
43
|
+
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
|
|
44
|
+
application/vnd.openxmlformats-officedocument.wordprocessingml.document
|
|
45
|
+
application/zip
|
|
46
|
+
image/gif
|
|
47
|
+
image/jpeg
|
|
48
|
+
image/png
|
|
49
|
+
image/webp
|
|
50
|
+
text/csv
|
|
51
|
+
text/plain
|
|
52
|
+
].freeze
|
|
53
|
+
|
|
54
|
+
self.abstract_class = true
|
|
55
|
+
|
|
56
|
+
class_attribute :lesli_attachment_max_file_size,
|
|
57
|
+
instance_accessor: false,
|
|
58
|
+
default: DEFAULT_MAX_FILE_SIZE
|
|
59
|
+
class_attribute :lesli_attachment_content_types,
|
|
60
|
+
instance_accessor: false,
|
|
61
|
+
default: DEFAULT_CONTENT_TYPES
|
|
62
|
+
|
|
63
|
+
belongs_to :user, class_name: "Lesli::User"
|
|
64
|
+
belongs_to :attachable, polymorphic: true
|
|
65
|
+
|
|
66
|
+
has_one_attached :file
|
|
67
|
+
|
|
68
|
+
validates :label, length: { maximum: 255 }, allow_blank: true
|
|
69
|
+
validates :category, length: { maximum: 100 }, allow_blank: true
|
|
70
|
+
validate :file_is_attached
|
|
71
|
+
validate :file_size_is_allowed
|
|
72
|
+
validate :file_type_is_allowed
|
|
73
|
+
|
|
74
|
+
def display_name
|
|
75
|
+
label.presence || file.filename.to_s
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
private
|
|
79
|
+
|
|
80
|
+
def file_is_attached
|
|
81
|
+
return if file.attached?
|
|
82
|
+
|
|
83
|
+
errors.add(
|
|
84
|
+
:file,
|
|
85
|
+
I18n.t("lesli.items.attachments.errors.required")
|
|
86
|
+
)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def file_size_is_allowed
|
|
90
|
+
return unless file.attached?
|
|
91
|
+
return if file.blob.byte_size <= self.class.lesli_attachment_max_file_size
|
|
92
|
+
|
|
93
|
+
errors.add(
|
|
94
|
+
:file,
|
|
95
|
+
I18n.t(
|
|
96
|
+
"lesli.items.attachments.errors.too_large",
|
|
97
|
+
size: ActiveSupport::NumberHelper.number_to_human_size(
|
|
98
|
+
self.class.lesli_attachment_max_file_size
|
|
99
|
+
)
|
|
100
|
+
)
|
|
101
|
+
)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def file_type_is_allowed
|
|
105
|
+
return unless file.attached?
|
|
106
|
+
return if self.class.lesli_attachment_content_types.include?(
|
|
107
|
+
file.blob.content_type
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
errors.add(
|
|
111
|
+
:file,
|
|
112
|
+
I18n.t("lesli.items.attachments.errors.unsupported_type")
|
|
113
|
+
)
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
@@ -2,9 +2,13 @@ module Lesli
|
|
|
2
2
|
module Items
|
|
3
3
|
class Discussion < ApplicationRecord
|
|
4
4
|
self.abstract_class = true
|
|
5
|
-
|
|
6
|
-
belongs_to :
|
|
5
|
+
|
|
6
|
+
belongs_to :author,
|
|
7
|
+
class_name: "Lesli::User",
|
|
8
|
+
foreign_key: :user_id
|
|
7
9
|
belongs_to :discussable, polymorphic: true
|
|
10
|
+
|
|
11
|
+
validates :message, presence: true
|
|
8
12
|
end
|
|
9
13
|
end
|
|
10
14
|
end
|
|
@@ -34,9 +34,11 @@ module Lesli
|
|
|
34
34
|
module Items
|
|
35
35
|
class Task < ApplicationRecord
|
|
36
36
|
self.abstract_class = true
|
|
37
|
+
|
|
37
38
|
belongs_to :user, class_name: "Lesli::User"
|
|
38
|
-
belongs_to :account, class_name: "Lesli::Account"
|
|
39
39
|
belongs_to :taskable, polymorphic: true
|
|
40
|
+
|
|
41
|
+
validates :title, presence: true
|
|
40
42
|
end
|
|
41
43
|
end
|
|
42
44
|
end
|
data/app/models/lesli/user.rb
CHANGED
|
@@ -41,17 +41,6 @@ module Lesli
|
|
|
41
41
|
uniqueness: true
|
|
42
42
|
);
|
|
43
43
|
|
|
44
|
-
# devise implementation
|
|
45
|
-
devise(
|
|
46
|
-
:database_authenticatable,
|
|
47
|
-
:registerable,
|
|
48
|
-
:rememberable,
|
|
49
|
-
:recoverable,
|
|
50
|
-
:validatable,
|
|
51
|
-
:confirmable,
|
|
52
|
-
:trackable
|
|
53
|
-
);
|
|
54
|
-
|
|
55
44
|
# users belongs to an account only...
|
|
56
45
|
belongs_to :account, optional: true
|
|
57
46
|
|
|
@@ -64,20 +53,22 @@ module Lesli
|
|
|
64
53
|
# users data extensions
|
|
65
54
|
has_many :tokens
|
|
66
55
|
has_many :settings
|
|
67
|
-
has_many :sessions, class_name: 'LesliShield::User::Session'
|
|
68
56
|
|
|
69
|
-
|
|
70
|
-
#
|
|
57
|
+
# Authentication and authorization records are implemented by
|
|
58
|
+
# LesliShield. These associations remain explicit here because they
|
|
59
|
+
# are part of the public API used throughout the platform.
|
|
60
|
+
has_many :sessions, class_name: "LesliShield::User::Session"
|
|
71
61
|
has_many :user_roles, class_name: "LesliShield::User::Role"
|
|
72
62
|
has_many :roles, through: :user_roles, source: :role
|
|
73
63
|
has_many :privileges, through: :roles
|
|
74
64
|
|
|
75
|
-
|
|
76
|
-
#
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
65
|
+
# Audit records are implemented by LesliAudit. The associations are
|
|
66
|
+
# declared here because requests, journals, and logs form part of the
|
|
67
|
+
# user API consumed by the platform.
|
|
68
|
+
has_many :requests, class_name: "LesliAudit::UserRequest", foreign_key: :user_id, inverse_of: :user
|
|
69
|
+
has_many :journals, class_name: "LesliAudit::UserJournal", foreign_key: :user_id, inverse_of: :user
|
|
70
|
+
has_many :logs, class_name: "LesliAudit::UserLog", foreign_key: :user_id, inverse_of: :user
|
|
71
|
+
has_many :account_logs, class_name: "LesliAudit::AccountLog", foreign_key: :user_id, inverse_of: :user
|
|
81
72
|
|
|
82
73
|
before_create :before_create_user
|
|
83
74
|
|
|
@@ -86,7 +77,7 @@ module Lesli
|
|
|
86
77
|
def before_create_user
|
|
87
78
|
self.uid ||= loop do
|
|
88
79
|
candidate = generate_resource_uid(prefix:'LID')
|
|
89
|
-
break candidate unless self.
|
|
80
|
+
break candidate unless self.class.exists?(uid: candidate)
|
|
90
81
|
end
|
|
91
82
|
end
|
|
92
83
|
|
|
@@ -272,12 +272,9 @@
|
|
|
272
272
|
</div>
|
|
273
273
|
|
|
274
274
|
<nav class="welcome-access" aria-label="Application access">
|
|
275
|
-
<% unless defined?(LesliShield) %>
|
|
276
|
-
<%= link_to("Open applications", "/apps") unless defined?(LesliDashboard) %>
|
|
277
|
-
<% end %>
|
|
278
275
|
<% if defined?(LesliShield) %>
|
|
279
|
-
<%= link_to("
|
|
280
|
-
<%= link_to("
|
|
276
|
+
<%= link_to("Applications", "/lesli/apps") if user_signed_in? %>
|
|
277
|
+
<%= link_to("Dashboard", "/dashboard") if user_signed_in? && defined?(LesliDashboard) %>
|
|
281
278
|
<%= link_to("Log out", "/logout") if user_signed_in? %>
|
|
282
279
|
<%= link_to("Log in", "/login") unless user_signed_in? %>
|
|
283
280
|
<% end %>
|
|
@@ -1,32 +1,18 @@
|
|
|
1
|
-
<style>
|
|
2
|
-
.error-404 .material-symbols {
|
|
3
|
-
font-size: 8rem;
|
|
4
|
-
color: rgb(190,190,190);
|
|
5
|
-
}
|
|
6
|
-
.error-404 h2 {
|
|
7
|
-
font-size: 2rem;
|
|
8
|
-
color: #3d211b;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
.error-404 p {
|
|
12
|
-
font-size: 1.4rem;
|
|
13
|
-
font-weight: 300;
|
|
14
|
-
}
|
|
15
|
-
</style>
|
|
16
1
|
<%= render(LesliView::Layout::Container.new("accounts")) do %>
|
|
17
|
-
<section class="
|
|
18
|
-
<div class="
|
|
2
|
+
<section class="px-4 py-24 sm:px-6 sm:py-36" aria-labelledby="not-found-title">
|
|
3
|
+
<div class="mx-auto max-w-2xl text-center">
|
|
19
4
|
|
|
20
|
-
<div class="mb-6">
|
|
5
|
+
<div class="mb-6" aria-hidden="true">
|
|
21
6
|
<%= render(LesliView::Elements::Empty.new(text:nil)) %>
|
|
22
7
|
</div>
|
|
23
8
|
|
|
24
|
-
<h2 class="mb-4
|
|
9
|
+
<h2 id="not-found-title" class="mb-4 text-[2rem] font-semibold leading-tight text-[#3d211b]">
|
|
10
|
+
404 <%= I18n.t("lesli.shared.message_error_not_found") %>
|
|
11
|
+
</h2>
|
|
25
12
|
|
|
26
|
-
<p>
|
|
13
|
+
<p class="text-[1.4rem] font-light leading-relaxed text-neutral-700">
|
|
27
14
|
<%= @message %>
|
|
28
15
|
</p>
|
|
29
16
|
</div>
|
|
30
17
|
</section>
|
|
31
18
|
<% end %>
|
|
32
|
-
|