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
|
@@ -33,93 +33,163 @@ Building a better future, one line of code at a time.
|
|
|
33
33
|
module Lesli
|
|
34
34
|
module Items
|
|
35
35
|
class TasksController < ApplicationLesliController
|
|
36
|
-
before_action :
|
|
37
|
-
before_action :
|
|
36
|
+
before_action :set_taskable, only: :create
|
|
37
|
+
before_action :set_task, only: :update
|
|
38
38
|
|
|
39
|
-
def
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
39
|
+
def create
|
|
40
|
+
task = @taskable.add_task!(
|
|
41
|
+
creator: current_user,
|
|
42
|
+
title: task_params[:title]
|
|
43
|
+
)
|
|
44
|
+
refresh_tasks
|
|
45
|
+
|
|
46
|
+
message = I18n.t("lesli.items.tasks.created")
|
|
47
|
+
respond_with_lesli(
|
|
48
|
+
turbo: [
|
|
49
|
+
stream_notification_success(message),
|
|
50
|
+
tasks_stream
|
|
51
|
+
],
|
|
52
|
+
json: {
|
|
53
|
+
message: message,
|
|
54
|
+
task: task_payload(task)
|
|
55
|
+
},
|
|
56
|
+
status: :created
|
|
57
|
+
)
|
|
58
|
+
rescue ActiveRecord::RecordInvalid => error
|
|
59
|
+
respond_with_task_errors(error.record)
|
|
44
60
|
end
|
|
45
61
|
|
|
46
|
-
def
|
|
62
|
+
def update
|
|
63
|
+
@task.update!(done: !@task.done?)
|
|
64
|
+
|
|
65
|
+
message = task_status_message
|
|
66
|
+
respond_with_lesli(
|
|
67
|
+
turbo: [
|
|
68
|
+
stream_notification_success(message),
|
|
69
|
+
turbo_stream.replace(
|
|
70
|
+
helpers.dom_id(@task, scope_key)
|
|
71
|
+
) do
|
|
72
|
+
render_task(@task)
|
|
73
|
+
end
|
|
74
|
+
],
|
|
75
|
+
json: {
|
|
76
|
+
message: message,
|
|
77
|
+
task: task_payload(@task)
|
|
78
|
+
}
|
|
79
|
+
)
|
|
80
|
+
rescue ActiveRecord::RecordInvalid => error
|
|
81
|
+
respond_with_task_errors(error.record)
|
|
47
82
|
end
|
|
48
83
|
|
|
49
|
-
|
|
50
|
-
task = task_class.new(task_params)
|
|
84
|
+
private
|
|
51
85
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
86
|
+
def scope_key
|
|
87
|
+
ActiveSupport::Inflector
|
|
88
|
+
.underscore(task_class.name)
|
|
89
|
+
.tr("/", "_")
|
|
90
|
+
end
|
|
55
91
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
stream_notification_success("Task created"),
|
|
59
|
-
turbo_stream.prepend("#{scope_key}-lesli-items-tasks-list") do
|
|
60
|
-
LesliView::Items::Task.new(task, scope_key).render_in(view_context)
|
|
61
|
-
end
|
|
62
|
-
])
|
|
63
|
-
else
|
|
64
|
-
respond_with_lesli(
|
|
65
|
-
:turbo => stream_notification_danger(task.errors.full_messages)
|
|
66
|
-
)
|
|
67
|
-
end
|
|
92
|
+
def task_class
|
|
93
|
+
self.class.module_parent.const_get("Task")
|
|
68
94
|
end
|
|
69
95
|
|
|
70
|
-
def
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
end
|
|
78
|
-
])
|
|
96
|
+
def taskable_class
|
|
97
|
+
allowed_taskable_classes.find do |klass|
|
|
98
|
+
klass.name == params.require(:taskable_type)
|
|
99
|
+
end || raise(
|
|
100
|
+
ActiveRecord::RecordNotFound,
|
|
101
|
+
"Unauthorized taskable type"
|
|
102
|
+
)
|
|
79
103
|
end
|
|
80
104
|
|
|
81
|
-
|
|
105
|
+
def allowed_taskable_classes
|
|
106
|
+
[]
|
|
107
|
+
end
|
|
82
108
|
|
|
83
|
-
|
|
84
|
-
|
|
109
|
+
def set_task
|
|
110
|
+
@task = task_class.find(params[:id])
|
|
111
|
+
ensure_taskable_access!(@task.taskable)
|
|
112
|
+
end
|
|
85
113
|
|
|
86
|
-
|
|
87
|
-
|
|
114
|
+
def set_taskable
|
|
115
|
+
@taskable = taskable_class.find(
|
|
116
|
+
params.require(:taskable_id)
|
|
117
|
+
)
|
|
118
|
+
ensure_taskable_access!(@taskable)
|
|
119
|
+
end
|
|
88
120
|
|
|
89
|
-
|
|
90
|
-
|
|
121
|
+
def ensure_taskable_access!(taskable)
|
|
122
|
+
allowed = taskable.respond_to?(:lesli_item_accessible_by?) &&
|
|
123
|
+
taskable.lesli_item_accessible_by?(current_user)
|
|
91
124
|
|
|
92
|
-
|
|
93
|
-
|
|
125
|
+
raise ActiveRecord::RecordNotFound unless allowed
|
|
126
|
+
end
|
|
94
127
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
render json: { error: "Unauthorized taskable type" }, status: :forbidden and return
|
|
99
|
-
end
|
|
128
|
+
def task_params
|
|
129
|
+
params.require(scope_key.to_sym).permit(:title)
|
|
130
|
+
end
|
|
100
131
|
|
|
101
|
-
|
|
132
|
+
def render_task(task)
|
|
133
|
+
LesliView::Items::Task
|
|
134
|
+
.new(task, scope_key)
|
|
135
|
+
.render_in(view_context)
|
|
102
136
|
end
|
|
103
137
|
|
|
104
|
-
def
|
|
105
|
-
|
|
138
|
+
def task_payload(task)
|
|
139
|
+
{
|
|
140
|
+
id: task.id,
|
|
141
|
+
title: task.title,
|
|
142
|
+
done: task.done?,
|
|
143
|
+
taskable_type: task.taskable_type,
|
|
144
|
+
taskable_id: task.taskable_id,
|
|
145
|
+
creator: {
|
|
146
|
+
id: task.user_id,
|
|
147
|
+
name: task.user.name
|
|
148
|
+
},
|
|
149
|
+
created_at: task.created_at,
|
|
150
|
+
updated_at: task.updated_at
|
|
151
|
+
}
|
|
106
152
|
end
|
|
107
153
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
type = params[:taskable_type].presence
|
|
112
|
-
id = params[:taskable_id].presence
|
|
113
|
-
return if type.blank? || id.blank?
|
|
154
|
+
def refresh_tasks
|
|
155
|
+
association_name =
|
|
156
|
+
@taskable.class.lesli_tasks_association_name
|
|
114
157
|
|
|
115
|
-
taskable
|
|
158
|
+
@taskable.public_send(association_name).reset
|
|
159
|
+
end
|
|
116
160
|
|
|
117
|
-
|
|
118
|
-
|
|
161
|
+
def task_status_message
|
|
162
|
+
status = @task.done? ? :completed : :reopened
|
|
163
|
+
I18n.t("lesli.items.tasks.#{status}")
|
|
119
164
|
end
|
|
120
165
|
|
|
121
|
-
def
|
|
122
|
-
|
|
166
|
+
def tasks_stream(task: nil)
|
|
167
|
+
turbo_stream.replace(
|
|
168
|
+
helpers.dom_id(@taskable, :tasks)
|
|
169
|
+
) do
|
|
170
|
+
LesliView::Items::Tasks
|
|
171
|
+
.new(@taskable, task: task)
|
|
172
|
+
.render_in(view_context)
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def respond_with_task_errors(task)
|
|
177
|
+
turbo = [
|
|
178
|
+
stream_notification_danger(
|
|
179
|
+
task.errors.full_messages.to_sentence
|
|
180
|
+
)
|
|
181
|
+
]
|
|
182
|
+
|
|
183
|
+
if task.new_record?
|
|
184
|
+
refresh_tasks
|
|
185
|
+
turbo << tasks_stream(task: task)
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
respond_with_lesli(
|
|
189
|
+
turbo: turbo,
|
|
190
|
+
json: { errors: task.errors },
|
|
191
|
+
status: :unprocessable_content
|
|
192
|
+
)
|
|
123
193
|
end
|
|
124
194
|
end
|
|
125
195
|
end
|
|
@@ -37,24 +37,6 @@ module Lesli
|
|
|
37
37
|
link_to(body, url, class: "link", data: { turbo_frame: '_top' })
|
|
38
38
|
end
|
|
39
39
|
|
|
40
|
-
# Meta-programming to define flash setter methods dynamically
|
|
41
|
-
# success("Everything worked!")
|
|
42
|
-
# danger("Oops, there was an error.")
|
|
43
|
-
# info("Just an informational message.")
|
|
44
|
-
# warning("This is a warning.")
|
|
45
|
-
[:info, :success, :warning, :danger].each do |flash_type|
|
|
46
|
-
define_method(flash_type) do |message|
|
|
47
|
-
flash[flash_type] = message
|
|
48
|
-
end
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
def stream_notification_success(message)
|
|
52
|
-
success(message)
|
|
53
|
-
turbo_stream.replace("application-lesli-notifications") do
|
|
54
|
-
render("lesli/partials/application-lesli-notifications")
|
|
55
|
-
end
|
|
56
|
-
end
|
|
57
|
-
|
|
58
40
|
def render_form_stream(partial_path, locals = {})
|
|
59
41
|
|
|
60
42
|
# Detect the current engine's namespace
|
|
@@ -33,6 +33,10 @@ Building a better future, one line of code at a time.
|
|
|
33
33
|
module Lesli
|
|
34
34
|
module ResponderInterface
|
|
35
35
|
|
|
36
|
+
# Lesli-specific response status used to tell JSON clients that they
|
|
37
|
+
# must execute the action included in the response body.
|
|
38
|
+
ACTION_REQUIRED_STATUS = 490
|
|
39
|
+
|
|
36
40
|
# Standard json structure for paginated data,
|
|
37
41
|
# this is compatible for json, html and turbo responses
|
|
38
42
|
# NOTE: LesliView::Element::Table require this structure
|
|
@@ -58,25 +62,37 @@ module Lesli
|
|
|
58
62
|
}
|
|
59
63
|
end
|
|
60
64
|
|
|
61
|
-
#
|
|
62
|
-
#
|
|
63
|
-
#
|
|
64
|
-
#
|
|
65
|
-
#
|
|
66
|
-
#
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
65
|
+
# Build a response for HTML, JSON, or Turbo Stream requests.
|
|
66
|
+
#
|
|
67
|
+
# `html` is passed to `render`. When omitted, Rails renders the current
|
|
68
|
+
# action's template. Use `redirect` for HTML redirects instead of
|
|
69
|
+
# invoking `redirect_to` before calling this method. `status` applies to
|
|
70
|
+
# rendered responses; redirects use `redirect_status`.
|
|
71
|
+
def respond_with_lesli(
|
|
72
|
+
turbo: nil,
|
|
73
|
+
json: nil,
|
|
74
|
+
html: nil,
|
|
75
|
+
redirect: nil,
|
|
76
|
+
status: :ok,
|
|
77
|
+
redirect_status: :see_other
|
|
78
|
+
)
|
|
79
|
+
if html && redirect
|
|
80
|
+
raise ArgumentError, "Use either html: or redirect:, not both"
|
|
81
|
+
end
|
|
82
|
+
|
|
76
83
|
respond_to do |format|
|
|
77
|
-
format.html
|
|
78
|
-
|
|
79
|
-
|
|
84
|
+
format.html do
|
|
85
|
+
if redirect
|
|
86
|
+
redirect_to(redirect, status: redirect_status)
|
|
87
|
+
elsif html
|
|
88
|
+
render(html, status: status)
|
|
89
|
+
else
|
|
90
|
+
render(status: status)
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
format.json { respond_with_http(status, json) }
|
|
95
|
+
format.turbo_stream { render(turbo_stream: turbo, status: status) }
|
|
80
96
|
end
|
|
81
97
|
end
|
|
82
98
|
|
|
@@ -95,12 +111,13 @@ module Lesli
|
|
|
95
111
|
end
|
|
96
112
|
end
|
|
97
113
|
|
|
98
|
-
#
|
|
99
|
-
#
|
|
100
|
-
#
|
|
101
|
-
#
|
|
102
|
-
#
|
|
103
|
-
|
|
114
|
+
# Respond with HTTP 403 when an authenticated user does not have the
|
|
115
|
+
# privileges required to perform the requested action.
|
|
116
|
+
#
|
|
117
|
+
# This method is used by LesliShield's authorization interface after
|
|
118
|
+
# authentication has succeeded. `respond_with_unauthorized` remains
|
|
119
|
+
# available as a backwards-compatible alias for existing engines.
|
|
120
|
+
def respond_with_forbidden(details = {})
|
|
104
121
|
|
|
105
122
|
@error_object = {
|
|
106
123
|
error_role: nil,
|
|
@@ -117,11 +134,13 @@ module Lesli
|
|
|
117
134
|
|
|
118
135
|
respond_to do |format|
|
|
119
136
|
format.json { respond_with_json_unauthorized(@error_object) }
|
|
120
|
-
format.html { render('lesli/errors/unauthorized', status: :
|
|
121
|
-
format.turbo_stream { render('lesli/errors/unauthorized', status: :
|
|
137
|
+
format.html { render('lesli/errors/unauthorized', status: :forbidden) }
|
|
138
|
+
format.turbo_stream { render('lesli/errors/unauthorized', status: :forbidden) }
|
|
122
139
|
end
|
|
123
140
|
end
|
|
124
141
|
|
|
142
|
+
alias_method :respond_with_unauthorized, :respond_with_forbidden
|
|
143
|
+
|
|
125
144
|
|
|
126
145
|
# ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~
|
|
127
146
|
# Auxiliar methods for responders
|
|
@@ -143,7 +162,7 @@ module Lesli
|
|
|
143
162
|
|
|
144
163
|
# Return the stream object so you can call:
|
|
145
164
|
# render turbo_stream: stream_notification_success("Done!")
|
|
146
|
-
turbo_stream.
|
|
165
|
+
turbo_stream.replace(
|
|
147
166
|
"application-lesli-notifications",
|
|
148
167
|
partial: "lesli/partials/application-lesli-notifications"
|
|
149
168
|
)
|
|
@@ -162,7 +181,7 @@ module Lesli
|
|
|
162
181
|
|
|
163
182
|
# Just a simple json success response for JSON
|
|
164
183
|
def respond_with_json(payload = nil)
|
|
165
|
-
respond_with_http(
|
|
184
|
+
respond_with_http(:ok, payload)
|
|
166
185
|
end
|
|
167
186
|
|
|
168
187
|
# Just a simple json not found response for JSON
|
|
@@ -185,19 +204,20 @@ module Lesli
|
|
|
185
204
|
end
|
|
186
205
|
|
|
187
206
|
# Just a simple http response for JSON
|
|
188
|
-
def respond_with_http(status, payload)
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
:json => payload.nil? ? "" : payload.to_json
|
|
193
|
-
)
|
|
207
|
+
def respond_with_http(status, payload = nil)
|
|
208
|
+
return head(status) if payload.nil?
|
|
209
|
+
|
|
210
|
+
render(:status => status, :json => payload)
|
|
194
211
|
end
|
|
195
212
|
|
|
196
|
-
#
|
|
197
|
-
#
|
|
198
|
-
#
|
|
213
|
+
# Tell a JSON client to execute an action before it can continue.
|
|
214
|
+
# Clients using the Lesli API must handle ACTION_REQUIRED_STATUS and
|
|
215
|
+
# dispatch the action included in the response body.
|
|
199
216
|
def respond_with_action(action, message = "Action Required")
|
|
200
|
-
respond_with_http(
|
|
217
|
+
respond_with_http(
|
|
218
|
+
ACTION_REQUIRED_STATUS,
|
|
219
|
+
{ :message => message, :action => action }
|
|
220
|
+
)
|
|
201
221
|
end
|
|
202
222
|
end
|
|
203
223
|
end
|
|
@@ -34,7 +34,12 @@ module Lesli
|
|
|
34
34
|
class ApplicationLesliMailer < ActionMailer::Base
|
|
35
35
|
|
|
36
36
|
# Set a dynamic template according to the engine that is sending the email
|
|
37
|
-
default(
|
|
37
|
+
default(
|
|
38
|
+
template_path: (Lesli.config.mailer || {}).fetch(
|
|
39
|
+
:templates,
|
|
40
|
+
"lesli_assets/emails"
|
|
41
|
+
)
|
|
42
|
+
)
|
|
38
43
|
|
|
39
44
|
#after_action :log_mail_requests
|
|
40
45
|
|
|
@@ -48,9 +53,16 @@ module Lesli
|
|
|
48
53
|
end
|
|
49
54
|
|
|
50
55
|
def build_url(path, params = {})
|
|
51
|
-
default_url =
|
|
56
|
+
default_url = mailer_url_options
|
|
52
57
|
host = default_url[:host].to_s.sub(/\Ahttps?:\/\//, "")
|
|
53
|
-
scheme = default_url[:protocol]&.delete(":") || "http"
|
|
58
|
+
scheme = default_url[:protocol].presence&.to_s&.delete(":") || "http"
|
|
59
|
+
|
|
60
|
+
if host.blank?
|
|
61
|
+
raise ArgumentError, <<~MESSAGE.squish
|
|
62
|
+
Action Mailer requires default_url_options[:host] to build
|
|
63
|
+
absolute links in Lesli emails
|
|
64
|
+
MESSAGE
|
|
65
|
+
end
|
|
54
66
|
|
|
55
67
|
URI::Generic.build(
|
|
56
68
|
scheme: scheme,
|
|
@@ -63,19 +75,32 @@ module Lesli
|
|
|
63
75
|
|
|
64
76
|
protected
|
|
65
77
|
|
|
66
|
-
def email(
|
|
78
|
+
def email(
|
|
79
|
+
params = {},
|
|
80
|
+
user: nil,
|
|
81
|
+
to:,
|
|
82
|
+
subject:,
|
|
83
|
+
template_name:,
|
|
84
|
+
**headers
|
|
85
|
+
)
|
|
67
86
|
|
|
68
87
|
@params = @params.merge(params)
|
|
69
88
|
|
|
70
89
|
build_app_from_params(params)
|
|
71
90
|
|
|
72
91
|
mail(
|
|
73
|
-
to: to,
|
|
92
|
+
to: to,
|
|
74
93
|
subject: subject,
|
|
94
|
+
**headers,
|
|
75
95
|
template_name: template_name
|
|
76
96
|
)
|
|
77
97
|
end
|
|
78
98
|
|
|
99
|
+
def mailer_url_options
|
|
100
|
+
options = Rails.application.config.action_mailer.default_url_options
|
|
101
|
+
(options || {}).to_h.symbolize_keys
|
|
102
|
+
end
|
|
103
|
+
|
|
79
104
|
|
|
80
105
|
private
|
|
81
106
|
|
|
@@ -88,11 +113,12 @@ module Lesli
|
|
|
88
113
|
tag_line: "",
|
|
89
114
|
}
|
|
90
115
|
|
|
91
|
-
|
|
116
|
+
account = params[:user]&.account
|
|
117
|
+
return if account.blank?
|
|
92
118
|
|
|
93
119
|
@app[:company] = {
|
|
94
|
-
id:
|
|
95
|
-
name:
|
|
120
|
+
id: account.id,
|
|
121
|
+
name: account.name,
|
|
96
122
|
#tag_line: params[:user].account.company_tag_line,
|
|
97
123
|
}
|
|
98
124
|
end
|
|
@@ -39,17 +39,17 @@ module Lesli
|
|
|
39
39
|
|
|
40
40
|
# platform role for owners
|
|
41
41
|
owner = self.roles
|
|
42
|
-
.create_with({ permission_level: 2147483647 })
|
|
42
|
+
.create_with({ permission_level: 2147483647, active: true })
|
|
43
43
|
.find_or_create_by(:name => "owner")
|
|
44
44
|
|
|
45
45
|
# platform role for administrators
|
|
46
46
|
admin = self.roles
|
|
47
|
-
.create_with({ permission_level: 100000})
|
|
47
|
+
.create_with({ permission_level: 100000, active: true })
|
|
48
48
|
.find_or_create_by(name: "admin")
|
|
49
49
|
|
|
50
50
|
# platform role for guests
|
|
51
51
|
guest = self.roles
|
|
52
|
-
.create_with({ permission_level: 10, path_default: "/administration/profile" })
|
|
52
|
+
.create_with({ permission_level: 10, active: true , path_default: "/administration/profile" })
|
|
53
53
|
.find_or_create_by(name: "guest")
|
|
54
54
|
|
|
55
55
|
|
|
@@ -64,6 +64,7 @@ module Lesli
|
|
|
64
64
|
|
|
65
65
|
# initialize engines for new accounts
|
|
66
66
|
def initialize_engines
|
|
67
|
+
initialized_engines = []
|
|
67
68
|
|
|
68
69
|
LesliSystem.engines.each do |engine, data|
|
|
69
70
|
|
|
@@ -72,6 +73,13 @@ module Lesli
|
|
|
72
73
|
# Create an associated account if the attribute is blank
|
|
73
74
|
engine.constantize::Account.find_or_create_by!(account: self)
|
|
74
75
|
|
|
76
|
+
initialized_engines << engine
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Emit initialization events only after every engine account exists.
|
|
80
|
+
# In particular, this makes the result independent from the position
|
|
81
|
+
# of LesliAudit in the engine registry.
|
|
82
|
+
initialized_engines.each do |engine|
|
|
75
83
|
self.log(:account_initialization, "Engine: #{engine} initialized successfully")
|
|
76
84
|
end
|
|
77
85
|
end
|
|
@@ -35,25 +35,14 @@ module Lesli
|
|
|
35
35
|
extend ActiveSupport::Concern
|
|
36
36
|
|
|
37
37
|
def log operation, description=nil, user:nil
|
|
38
|
-
return unless defined?(LesliAudit)
|
|
39
|
-
log_anonimous(operation, description) unless self.audit
|
|
40
|
-
log_account(operation, description) if self.audit
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
private
|
|
44
|
-
|
|
45
|
-
def log_anonimous operation, description
|
|
46
|
-
LesliAudit::AccountLog.create({
|
|
47
|
-
operation: operation,
|
|
48
|
-
description: description
|
|
49
|
-
})
|
|
50
|
-
end
|
|
38
|
+
return unless defined?(LesliAudit::ActivityAudit)
|
|
51
39
|
|
|
52
|
-
|
|
53
|
-
|
|
40
|
+
LesliAudit::ActivityAudit.record_account!(
|
|
41
|
+
account: self,
|
|
42
|
+
actor: user,
|
|
54
43
|
operation: operation,
|
|
55
44
|
description: description
|
|
56
|
-
|
|
45
|
+
)
|
|
57
46
|
end
|
|
58
47
|
end
|
|
59
48
|
end
|
|
@@ -34,9 +34,13 @@ module Lesli
|
|
|
34
34
|
module Items
|
|
35
35
|
module Activities
|
|
36
36
|
extend ActiveSupport::Concern
|
|
37
|
+
include Lesli::Items::Ownership
|
|
37
38
|
|
|
38
39
|
included do
|
|
39
40
|
class_attribute :lesli_activities_class, instance_accessor: false
|
|
41
|
+
class_attribute :lesli_activities_association_name,
|
|
42
|
+
instance_accessor: false,
|
|
43
|
+
default: :activities
|
|
40
44
|
lesli_activities_setup
|
|
41
45
|
end
|
|
42
46
|
|
|
@@ -52,13 +56,7 @@ module Lesli
|
|
|
52
56
|
end
|
|
53
57
|
|
|
54
58
|
self.lesli_activities_class = klass
|
|
55
|
-
|
|
56
|
-
# ✅ Auto-fix table name (works even when engine defines table_name_prefix)
|
|
57
|
-
expected_table = lesli_expected_activity_table_name_for(klass)
|
|
58
|
-
|
|
59
|
-
if klass.table_name != expected_table
|
|
60
|
-
klass.table_name = expected_table
|
|
61
|
-
end
|
|
59
|
+
self.lesli_activities_association_name = association_name.to_sym
|
|
62
60
|
|
|
63
61
|
# Set associations
|
|
64
62
|
# Example:
|
|
@@ -66,41 +64,39 @@ module Lesli
|
|
|
66
64
|
# has_many :activities, class_name: "LesliSupport::Items::Activity"
|
|
67
65
|
# end
|
|
68
66
|
has_many(association_name,
|
|
69
|
-
-> {
|
|
67
|
+
-> {
|
|
68
|
+
where(deleted_at: nil)
|
|
69
|
+
.order(created_at: :desc, id: :desc)
|
|
70
|
+
},
|
|
70
71
|
as: as,
|
|
71
72
|
class_name: klass.name,
|
|
72
73
|
dependent: :destroy
|
|
73
74
|
)
|
|
74
|
-
|
|
75
|
-
# rescue NameError
|
|
76
|
-
# raise NameError,
|
|
77
|
-
# "Task class not found for #{name}. Expected #{name.deconstantize}::Activity or pass `use:`"
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
private
|
|
81
|
-
|
|
82
|
-
# Build: "lesli_support_item_activities" from "LesliSupport::Item::Activity"
|
|
83
|
-
#
|
|
84
|
-
# We DO NOT rely on ActiveRecord's table_name_prefix, because that’s what breaks your case.
|
|
85
|
-
def lesli_expected_activity_table_name_for(klass)
|
|
86
|
-
parts = klass.name.split("::").map(&:underscore) # ["lesli_support", "item", "activity"]
|
|
87
|
-
"#{parts.join('_').pluralize}" # "lesli_support_item_activities"
|
|
88
75
|
end
|
|
89
76
|
end
|
|
90
77
|
|
|
91
|
-
def
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
78
|
+
def record_activity!(
|
|
79
|
+
actor:,
|
|
80
|
+
code:,
|
|
81
|
+
description: nil,
|
|
82
|
+
metadata: {},
|
|
83
|
+
activity_type: :activity
|
|
84
|
+
)
|
|
85
|
+
activity_association.create!(
|
|
86
|
+
actor: actor,
|
|
87
|
+
account: lesli_item_account,
|
|
88
|
+
activity_code: code,
|
|
89
|
+
activity_type: activity_type,
|
|
90
|
+
description: description,
|
|
91
|
+
metadata: metadata
|
|
102
92
|
)
|
|
103
93
|
end
|
|
94
|
+
|
|
95
|
+
private
|
|
96
|
+
|
|
97
|
+
def activity_association
|
|
98
|
+
public_send(self.class.lesli_activities_association_name)
|
|
99
|
+
end
|
|
104
100
|
end
|
|
105
101
|
end
|
|
106
102
|
end
|