lesli 5.0.11 → 5.0.13

Sign up to get free protection for your applications and to get access to all the features.
Files changed (175) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/config/lesli_manifest.js +0 -13
  3. data/app/assets/icons/lesli/engine-security.svg +1 -0
  4. data/app/assets/icons/lesli/engine-shield.svg +1 -0
  5. data/app/assets/images/lesli/brand/app-logo.png +0 -0
  6. data/app/assets/images/lesli/lesli-logo.svg +4 -0
  7. data/app/assets/javascripts/lesli/templates/application.js +14 -0
  8. data/app/assets/javascripts/lesli/templates/public.js +14 -0
  9. data/app/assets/stylesheets/lesli/templates/application.css +1365 -293
  10. data/app/assets/stylesheets/lesli/templates/public.css +1 -1
  11. data/app/controllers/lesli/abouts_controller.rb +12 -18
  12. data/app/controllers/lesli/application_controller.rb +26 -25
  13. data/app/controllers/lesli/application_lesli_controller.rb +5 -6
  14. data/app/controllers/lesli/interfaces/application/authorization.rb +2 -2
  15. data/app/controllers/lesli/interfaces/application/customization.rb +1 -1
  16. data/app/controllers/lesli/interfaces/application/requester.rb +2 -2
  17. data/app/controllers/lesli/interfaces/application/responder.rb +8 -8
  18. data/app/controllers/lesli/interfaces/controllers/actions.rb +250 -0
  19. data/app/controllers/lesli/interfaces/controllers/activities.rb +215 -0
  20. data/app/controllers/lesli/interfaces/controllers/discussions.rb +270 -0
  21. data/app/controllers/lesli/interfaces/controllers/files.rb +467 -0
  22. data/app/controllers/lesli/interfaces/controllers/subscribers.rb +234 -0
  23. data/app/helpers/lesli/assets_helper.rb +26 -8
  24. data/app/helpers/lesli/navigation_helper.rb +53 -92
  25. data/app/lib/date2.rb +8 -0
  26. data/app/lib/lesli/system.rb +17 -4
  27. data/app/models/concerns/account_initializer.rb +46 -42
  28. data/app/models/concerns/user_extensions.rb +6 -0
  29. data/{lib/scss/devise/registrations.scss → app/models/lesli/account/detail.rb} +7 -3
  30. data/app/models/lesli/account.rb +12 -5
  31. data/app/models/lesli/cloud_object/action.rb +70 -0
  32. data/app/models/lesli/cloud_object/activity.rb +311 -0
  33. data/app/models/lesli/cloud_object/custom_field.rb +158 -0
  34. data/app/models/lesli/cloud_object/discussion.rb +219 -0
  35. data/app/models/lesli/cloud_object/subscriber.rb +186 -0
  36. data/app/models/lesli/shared/dashboard.rb +16 -5
  37. data/app/models/lesli/user/session.rb +0 -2
  38. data/app/models/lesli/user.rb +13 -13
  39. data/app/operators/lesli/controller_operator.rb +4 -1
  40. data/app/operators/lesli/user_registration_operator.rb +3 -3
  41. data/app/services/lesli/user_service.rb +1 -1
  42. data/app/views/lesli/layouts/application-devise.html.erb +6 -6
  43. data/app/views/lesli/layouts/application-lesli.html.erb +3 -1
  44. data/app/views/lesli/partials/_application-data.html.erb +5 -4
  45. data/app/views/lesli/partials/_application-lesli-engines.html.erb +14 -39
  46. data/app/views/lesli/partials/_application-lesli-header.html.erb +50 -25
  47. data/app/views/lesli/partials/_application-lesli-icons.html.erb +1 -1
  48. data/app/views/lesli/partials/_application-lesli-javascript.html.erb +2 -2
  49. data/app/views/lesli/partials/_application-lesli-navigation.html.erb +8 -1
  50. data/app/views/lesli/partials/_application-lesli-panels.html.erb +7 -7
  51. data/app/views/lesli/partials/_application-lesli-scss.html.erb +2 -2
  52. data/app/views/lesli/wrappers/_application-devise-simple.erb +1 -1
  53. data/app/views/lesli/wrappers/_application-devise.html.erb +5 -7
  54. data/config/initializers/devise.rb +335 -335
  55. data/config/initializers/lesli.rb +9 -2
  56. data/config/locales/translations.en.yml +9 -3
  57. data/config/locales/translations.es.yml +9 -3
  58. data/config/locales/translations.fr.yml +30 -0
  59. data/config/locales/translations.it.yml +30 -0
  60. data/config/locales/translations.pt.yml +30 -0
  61. data/config/routes.rb +1 -10
  62. data/db/migrate/{v1.0/0010003010_create_lesli_user_details.rb → v1/0010000110_create_lesli_accounts.rb} +19 -13
  63. data/db/migrate/{v1.0/0010000110_create_lesli_accounts.rb → v1/0010001010_create_lesli_account_details.rb} +5 -7
  64. data/db/migrate/{v1.0/0010001010_create_lesli_account_settings.rb → v1/0010001110_create_lesli_account_settings.rb} +2 -2
  65. data/db/seed/development/accounts.rb +10 -7
  66. data/db/seed/development/users.rb +20 -20
  67. data/db/seed/production/accounts.rb +10 -7
  68. data/lib/generators/application_lesli_generator.rb +164 -0
  69. data/lib/generators/lesli/spec/USAGE +8 -0
  70. data/lib/generators/lesli/spec/spec_generator.rb +25 -0
  71. data/lib/generators/lesli/spec/templates/spec-factory.template +17 -0
  72. data/lib/generators/lesli/spec/templates/spec-model.template +70 -0
  73. data/lib/lesli/configuration.rb +1 -1
  74. data/lib/lesli/engine.rb +3 -14
  75. data/lib/{scss/devise/passwords.scss → lesli/r_spec.rb} +12 -5
  76. data/lib/lesli/routing.rb +51 -20
  77. data/lib/lesli/version.rb +2 -2
  78. data/lib/lesli.rb +1 -0
  79. data/lib/scss/cloud-objects/discussion.scss +8 -5
  80. data/lib/scss/layouts/application-component.scss +1 -1
  81. data/lib/scss/{devise/sessions.scss → layouts/application-content.scss} +4 -4
  82. data/lib/scss/layouts/application-header.scss +38 -108
  83. data/lib/scss/layouts/{application-navbar.scss → application-navigation.scss} +23 -5
  84. data/lib/scss/layouts/application-search.scss +1 -1
  85. data/lib/scss/{elements/msg.scss → overrides/notification.scss} +16 -18
  86. data/lib/scss/pages/devise-simple.scss +4 -2
  87. data/lib/scss/pages/devise.scss +111 -107
  88. data/lib/scss/panels/panel-notification.scss +1 -1
  89. data/lib/scss/panels/{panel-ticket.scss → panel-support-ticket.scss} +3 -4
  90. data/lib/scss/settings/variables.scss +1 -1
  91. data/lib/scss/templates/application.scss +14 -41
  92. data/lib/scss/templates/public.scss +6 -4
  93. data/lib/tasks/lesli/controllers.rake +1 -1
  94. data/lib/tasks/lesli/db.rake +24 -12
  95. data/lib/tasks/lesli_tasks.rake +7 -7
  96. data/lib/vue/application.js +18 -15
  97. data/lib/vue/{refactor/shared/cloudobjects → cloudobjects}/discussion/content.vue +10 -8
  98. data/lib/vue/cloudobjects/discussion/element.vue +170 -0
  99. data/lib/vue/{refactor/shared/cloudobjects → cloudobjects}/discussion/filters.vue +1 -1
  100. data/lib/vue/{refactor/shared/cloudobjects → cloudobjects}/discussion/new.vue +20 -16
  101. data/lib/vue/{refactor/shared/cloudobjects → cloudobjects}/discussion.vue +25 -24
  102. data/lib/vue/{refactor/stores/cloudobjects → cloudobjects/stores}/discussion.js +7 -16
  103. data/lib/vue/layouts/application-component.vue +2 -2
  104. data/lib/vue/layouts/application-header.vue +109 -88
  105. data/lib/vue/panels/{panel-notifications.vue → panel-bell-notifications.vue} +15 -19
  106. data/lib/vue/panels/panel-support-tickets.vue +163 -0
  107. data/lib/vue/panels/stores/bell-notifications.js +46 -0
  108. data/lib/vue/panels/stores/support-tickets.js +103 -0
  109. data/lib/vue/shared/dashboards/apps/edit.vue +10 -10
  110. data/lib/vue/shared/dashboards/components/form.vue +31 -40
  111. data/lib/vue/shared/stores/dashboard.js +2 -0
  112. data/lib/vue/shared/stores/layout.js +2 -1
  113. data/lib/{scss/devise/confirmations.scss → vue/shared/stores/users.js} +22 -21
  114. data/lib/vue/stores/translations.json +119 -2
  115. data/lib/webpack/base.js +18 -12
  116. data/lib/webpack/core.js +16 -12
  117. data/lib/webpack/engines.js +3 -1
  118. data/lib/webpack/root.js +105 -0
  119. data/lib/webpack/version.js +37 -0
  120. data/readme.md +16 -15
  121. metadata +58 -92
  122. data/app/assets/icons/lesli/engine-guard.svg +0 -1
  123. data/app/assets/javascripts/lesli/users/sessions.js +0 -1
  124. data/app/assets/stylesheets/lesli/users/sessions.css +0 -1
  125. data/app/controllers/users/confirmations_controller.rb +0 -66
  126. data/app/controllers/users/omniauth_callbacks_controller.rb +0 -30
  127. data/app/controllers/users/passwords_controller.rb +0 -71
  128. data/app/controllers/users/registrations_controller.rb +0 -141
  129. data/app/controllers/users/sessions_controller.rb +0 -141
  130. data/app/controllers/users/unlocks_controller.rb +0 -30
  131. data/app/views/devise/confirmations/new.html.erb +0 -2
  132. data/app/views/devise/confirmations/show.html.erb +0 -63
  133. data/app/views/devise/mailer/confirmation_instructions.html.erb +0 -5
  134. data/app/views/devise/mailer/email_changed.html.erb +0 -7
  135. data/app/views/devise/mailer/password_change.html.erb +0 -3
  136. data/app/views/devise/mailer/reset_password_instructions.html.erb +0 -8
  137. data/app/views/devise/mailer/unlock_instructions.html.erb +0 -7
  138. data/app/views/devise/passwords/edit.html.erb +0 -79
  139. data/app/views/devise/passwords/new.html.erb +0 -75
  140. data/app/views/devise/registrations/edit.html.erb +0 -43
  141. data/app/views/devise/registrations/new.html.erb +0 -147
  142. data/app/views/devise/sessions/new.html.erb +0 -114
  143. data/app/views/devise/shared/_demo.html.erb +0 -7
  144. data/app/views/devise/shared/_error_messages.html.erb +0 -15
  145. data/app/views/devise/shared/_links.html.erb +0 -96
  146. data/app/views/devise/unlocks/new.html.erb +0 -16
  147. data/db/migrate/v1.0/0010000210_create_lesli_roles.rb +0 -59
  148. data/db/migrate/v1.0/0010000310_create_lesli_users.rb +0 -97
  149. data/db/migrate/v1.0/0010003110_create_lesli_user_settings.rb +0 -44
  150. data/db/migrate/v1.0/0010003210_create_lesli_user_sessions.rb +0 -55
  151. data/db/migrate/v1.0/0010003410_create_lesli_user_powers.rb +0 -43
  152. data/db/migrate/v1.0/0010004010_create_lesli_user_logs.rb +0 -45
  153. data/db/migrate/v1.0/0010005010_create_lesli_descriptors.rb +0 -44
  154. data/db/migrate/v1.0/0010005110_create_lesli_descriptor_privileges.rb +0 -45
  155. data/db/migrate/v1.0/0010005210_create_lesli_descriptor_activities.rb +0 -49
  156. data/db/migrate/v1.0/0010005510_create_lesli_role_powers.rb +0 -51
  157. data/db/migrate/v1.0/0010005710_create_lesli_role_privileges.rb +0 -45
  158. data/lib/scss/bulma/loader.scss +0 -92
  159. data/lib/scss/components/editor-richtext.scss +0 -88
  160. data/lib/scss/devise/oauth.scss +0 -34
  161. data/lib/scss/elements/avatar.scss +0 -48
  162. data/lib/scss/elements/calendar.scss +0 -47
  163. data/lib/scss/elements/toggle.scss +0 -102
  164. data/lib/vue/devise/confirmations.js +0 -33
  165. data/lib/vue/devise/passwords.js +0 -137
  166. data/lib/vue/devise/registrations.js +0 -157
  167. data/lib/vue/devise/sessions.js +0 -148
  168. data/lib/vue/panels/panel-tickets.vue +0 -181
  169. data/lib/vue/refactor/shared/cloudobjects/discussion/element.vue +0 -132
  170. data/lib/vue/shared/stores/account.js +0 -113
  171. /data/app/assets/icons/lesli/{engine-driver.svg → engine-calendar.svg} +0 -0
  172. /data/db/migrate/{v1.0 → v1}/0010000610_create_lesli_system_controllers.rb +0 -0
  173. /data/db/migrate/{v1.0 → v1}/0010000710_create_lesli_system_controller_actions.rb +0 -0
  174. /data/db/migrate/{v1.0 → v1}/0010001210_create_lesli_account_activities.rb +0 -0
  175. /data/db/migrate/{v1.0 → v1}/0010001410_create_lesli_account_logs.rb +0 -0
@@ -0,0 +1,158 @@
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 · Your Smart Business Assistant.
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://lesli.tech
27
+ @license GPLv3 http://www.gnu.org/licenses/gpl-3.0.en.html
28
+
29
+ // · ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~
30
+ // ·
31
+
32
+ =end
33
+ module Lesli
34
+ class CloudObject::CustomField < ApplicationLesliRecord
35
+ self.abstract_class = true
36
+ belongs_to :user_creator, class_name: "::User", foreign_key: "users_id"
37
+
38
+ # @return [User] This method will always return nil
39
+ # @description At the current time, this is a dummy method that returns nil, so the function is_editable_by? in
40
+ # ApplicationLesliRecord will work without issues
41
+ def user_main
42
+ return nil
43
+ end
44
+
45
+ =begin
46
+ # @param cloud_object [ApplicationRecord] Cloud object to which an user can subscribe to
47
+ # @param user [User] The user that is subscribing to the *cloud_ubject*
48
+ # @param event [Symbol] A valid event from this class's *event* enum to wich the *user* will be subscribed
49
+ # @param notification_type [Symbol] A valid notification_type from this class's *notification_type* enum
50
+ # @return [void]
51
+ # @description Subscribes a *user* to one or all *events* of the *cloud_object*. If no *event* is provided, the *user*
52
+ # is subscribed to all the *events*
53
+ # @example
54
+ # first_ticket = CloudHelp::Ticket.find( 1 )
55
+ # second_ticket = CloudHelp::Ticket.find( 2 )
56
+ # user = current_user
57
+ # CloudHelp::Ticket::Subscriber.add_subscriber( first_ticket, current_user )
58
+ # CloudHelp::Ticket::Subscriber.add_subscriber( second_ticket, current_user, :http_post, :email )
59
+ def self.add_subscriber(cloud_object, user, event=nil, notification_type= :web)
60
+ model = dynamic_info[:model]
61
+
62
+ if event
63
+ return cloud_object.subscribers.create(
64
+ user: user,
65
+ event: event,
66
+ notification_type: model.notification_types[notification_type]
67
+ )
68
+ end
69
+
70
+ model.events.values.each do |event|
71
+ cloud_object.subscribers.create(
72
+ user: user,
73
+ event: event,
74
+ notification_type: model.notification_types[notification_type]
75
+ )
76
+ end
77
+ end
78
+
79
+ # @param cloud_object [ApplicationRecord] Cloud object to which an user can subscribe to
80
+ # @param subject [String] The subject that will be shown in the notification
81
+ # @param event [Symbol] A valid event from this class's *event* enum
82
+ # @return [void]
83
+ # @description Notifies all the users subscribed to the *cloud_object*'s *event* using the *Courier* engine
84
+ # @example
85
+ # ticket = CloudHelp::Ticket.find( 1 )
86
+ # ClodHelp::Ticket::Subscriber.notify_subscribers(
87
+ # ticket,
88
+ # "A new comment has been added to ticket #{ticket.id}",
89
+ # :comment_created
90
+ # )
91
+ def self.notify_subscribers(cloud_object, subject, event)
92
+ module_name = dynamic_info[:module_name]
93
+ object_name = dynamic_info[:object_name]
94
+
95
+ cloud_object.subscribers.where(event: event).each do |subscriber|
96
+ Courier::Bell::Notifications.send(
97
+ user: subscriber.user,
98
+ subject: subject,
99
+ href: "#{module_name}/#{object_name}/#{cloud_object.id}",
100
+ type: subscriber.notification_type,
101
+ cloud_object_type: "#{module_name}/#{object_name}"
102
+ )
103
+ end
104
+ end
105
+
106
+ # @param cloud_object [ApplicationRecord] Cloud object to which an user can subscribe to
107
+ # @param user [User] The user that is subscribing to the *cloud_object*
108
+ # @return [Array] Array of subscriptions. There is one subscription per *event*.
109
+ # @description Generates an array of subscription, Each element contains a subscription *event*,
110
+ # information about wheter the *user* is subscribed or not and, if the user is subscribed,
111
+ # the notification type of the subscription
112
+ # @example
113
+ # ticket = CloudHelp::Ticket.find( 1 )
114
+ # subscription_events = CloudHelp::Ticket::Subscriber.subscription_events(
115
+ # ticket,
116
+ # current_user
117
+ # )
118
+ def self.subscription_events(cloud_object, user)
119
+ model = dynamic_info[:model]
120
+
121
+ data = { }
122
+ events = model.events.keys
123
+ events.each do |event|
124
+ data[event] = {
125
+ event: event,
126
+ subscribed: false,
127
+ notification_type: :web
128
+ }
129
+ end
130
+ cloud_object.subscribers.where(users_id: user.id).each do |subscriber|
131
+ data[subscriber.event][:id] = subscriber.id
132
+ data[subscriber.event][:subscribed] = true
133
+ data[subscriber.event][:notification_type] = subscriber.notification_type
134
+ end
135
+ data.values
136
+ end
137
+
138
+ private
139
+
140
+ # @return [Hash] Hash that contains information about the class
141
+ # @description Returns dynamic information based on the current implementation of this abstract class
142
+ # @example
143
+ # dynamic_info = CloudHelp::Ticket::Subscriber.dynamic_info
144
+ # puts dynamic_info[:module_name] # will print 'help'
145
+ # puts dynamic_info[:object_name] # will print 'ticket'
146
+ # dynamic_info.model.new # will return an instance of CloudHelp::Ticket::Subscriber
147
+ def self.dynamic_info
148
+ module_info = self.name.split("::")
149
+ {
150
+ module_name: module_info[0].sub("Cloud", "").downcase,
151
+ object_name: module_info[1].downcase,
152
+ model: "#{module_info[0]}::#{module_info[1]}::Subscriber".constantize
153
+ }
154
+ end
155
+ =end
156
+
157
+ end
158
+ end
@@ -0,0 +1,219 @@
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 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
+ module Lesli
33
+ class CloudObject::Discussion < ApplicationLesliRecord
34
+ self.abstract_class = true
35
+ belongs_to :user_creator, class_name: "::User", foreign_key: "users_id"
36
+
37
+ # @return [User] This method will always return nil
38
+ # @description At the current time, this is a dummy method that returns nil, so the function is_editable_by? in
39
+ # ApplicationLesliRecord will work without issues
40
+ def user_main
41
+ return nil
42
+ end
43
+
44
+ # @param account [Account] Account from current user
45
+ # @param cloud_id [Integer] Id of the *cloud_object* to which this discussion belongs to
46
+ # @param query [Query] that contains the search and pagination information
47
+ # @return [Array] Array of discussions. Each discussion contains a *responses* element,
48
+ # which is an array that has all its responses ordered by date
49
+ # @description Retrieves and returns all discussions from a *cloud_object*,
50
+ # including information of the user that published the comment
51
+ # @example
52
+ # current_user = the user making this request
53
+ # employee_id = params[:employee_id]
54
+ # discussions = CloudTeam::Employee::Discussion.index( account, employee_id, @query )
55
+ def self.index(current_user, cloud_id, query)
56
+ cloud_object_model = self.cloud_object_model
57
+ account_model = cloud_object_model.reflect_on_association(:account).klass
58
+
59
+ # get search string from query params
60
+ #search_string = query[:search].downcase.gsub(" ","%") unless query[:search].blank?
61
+
62
+ discussions = self.joins(:cloud_object)
63
+ .joins("inner join lesli_users u on #{self.table_name}.user_id = u.id")
64
+ .where("#{cloud_object_model.table_name}.id = #{cloud_id}")
65
+ .where("#{cloud_object_model.table_name}.account_id = #{current_user.account.id}")
66
+ .select(
67
+ "#{self.table_name}.id",
68
+ "#{self.table_name}.user_id",
69
+ "#{self.table_name}.content",
70
+ Date2.new.date_time.db_timestamps("#{self.table_name}"),
71
+ "u.email",
72
+ "CONCAT_WS(' ', u.first_name, u.last_name) as user_name"
73
+ )
74
+
75
+ # Filter results by search string
76
+ # unless search_string.blank?
77
+ # discussions = discussions.where("
78
+ # (LOWER(ud.first_name) SIMILAR TO '%#{search_string}%') OR
79
+ # (LOWER(ud.last_name) SIMILAR TO '%#{search_string}%') OR
80
+ # (LOWER(#{self.table_name}.content) SIMILAR TO '%#{search_string}%')
81
+ # ")
82
+ # end
83
+
84
+ discussions = self.format_discussions(discussions)
85
+
86
+ Kaminari.paginate_array(discussions).page(query[:pagination][:page]).per(query[:pagination][:perPage])
87
+ end
88
+
89
+ # @return [Hash] Information about the discussion
90
+ # @description Retrieves and returns a specific discussion, along with information about the user
91
+ # that created it
92
+ # @example
93
+ # discussion = CloudHelp::Ticket::Discussion.first
94
+ # puts discussion.show #This will display extra information about the discussion, like the user's name
95
+ def show(current_user = nil)
96
+ discussion_attributes = attributes.merge({
97
+ editable: is_editable_by?(current_user),
98
+ email: user_creator.email,
99
+ user_name: user_creator.full_name
100
+ })
101
+ discussion_attributes["created_at"] = LC::Date.to_string_datetime(discussion_attributes["created_at"])
102
+
103
+ discussion_attributes
104
+ end
105
+
106
+ # @return [Class] The class of the association 'belongs_to'
107
+ # @description All discussions belong to a *cloud_object*. This method returns the specific class of
108
+ # that cloud_object.
109
+ # @example
110
+ # puts DeutscheLeibrenten::Project::Discussion.cloud_object_model.new # This will display an instance of DeutscheLeibrenten::Project
111
+ def self.cloud_object_model
112
+ self.reflect_on_association(:cloud_object).klass
113
+ end
114
+
115
+ private
116
+
117
+ # @param discussions [Arrray] An array of discussions that were previously filtered
118
+ # @return [Array] A new array of discussions, nesting the responses within the parent discussions
119
+ # @description Geretes a new array of discussions, nesting the responses inside the parent discussions and order them by date
120
+ # This method uses the *nest_responses* method to support the nesting action recursively
121
+ # @example
122
+ # # Imagine that discussions has 2 discussions, the first one is a normal discussion, and the second one is a response to the first one
123
+ # puts discussions # will display something like
124
+ # # [
125
+ # # {
126
+ # # id: 1,
127
+ # # users_id: 2,
128
+ # # created_at: Date(),
129
+ # # content: 'This is a root discussion',
130
+ # # user_name: 'System',
131
+ # # discussions_id: null
132
+ # # }, {
133
+ # # id: 2,
134
+ # # users_id: 2,
135
+ # # created_at: Date(),
136
+ # # content: 'This is a response',
137
+ # # user_name: 'Test',
138
+ # # discussions_id: 1
139
+ # # }
140
+ # # ]
141
+ # puts Discussion.format_discussions(discussions) # Will display something like
142
+ # # [
143
+ # # {
144
+ # # id: 1,
145
+ # # users_id: 2,
146
+ # # created_at: Date(),
147
+ # # content: 'This is a root discussion',
148
+ # # user_name: 'System',
149
+ # # discussions_id: null,
150
+ # # responses: [{
151
+ # # id: 2,
152
+ # # users_id: 2,
153
+ # # created_at: Date(),
154
+ # # content: 'This is a response',
155
+ # # response_to: 'System'
156
+ # # user_name: 'Test',
157
+ # # discussions_id: 1
158
+ # # }]
159
+ # # }
160
+ # # ]
161
+ def self.format_discussions(discussions)
162
+ data = {}
163
+ root_discussions = []
164
+
165
+ # Add all responses to that discussion
166
+ discussions.each do |discussion|
167
+ discussion_data = {
168
+ data: discussion,
169
+ responses: []
170
+ }
171
+ data[discussion["id"]] = discussion_data
172
+
173
+ if discussion["#{self.table_name}_id"].present?
174
+ parent_discussion = data[discussion["#{self.table_name}_id"]]
175
+ next unless parent_discussion
176
+
177
+ if parent_discussion[:data]["#{self.table_name}_id"].present?
178
+ discussion["response_to"] = parent_discussion[:data]["user_name"]
179
+ end
180
+ parent_discussion[:responses].push(discussion["id"])
181
+ else
182
+ root_discussions.push(discussion_data)
183
+ end
184
+ end
185
+
186
+ # Nest the responses to root discussions. A root discussion is any comment that is not a response to another comment
187
+ root_discussions.each do |discussion|
188
+ responses = []
189
+ self.nest_responses(data, discussion, responses)
190
+ discussion[:responses] = responses
191
+ end
192
+
193
+ # revert the root discussions so the most recent comments appear in the top
194
+ root_discussions.reverse
195
+ end
196
+
197
+ # @param data [Hash] Hash that contains all the discussions, the key is the id of the discussion
198
+ # @param discussion [Hash] Hash that contains the information
199
+ # @param responses [Array] Array of discussions, that are the responses to the *discussion* param.
200
+ # It should start as an empty array
201
+ # @return [void]
202
+ # @description Recursive function that checks all discussions that are actually responses of other
203
+ # discussions and adds them to their *responses* param. It adds direct responses, responses of responses, etc.
204
+ # This is a void function that modifies the *discussion* param directly
205
+ # @example
206
+ # data = {}
207
+ # CloudHelp::Ticket::Discussion.all.each do |discussion|
208
+ # data[discussion.id] = discussion
209
+ # end
210
+ # CloudHelp::Ticket::Discussion.nest_responses(data, CloudHelp::Ticket::Discussion.first, [])
211
+ def self.nest_responses(data, discussion, responses)
212
+ discussion[:responses].each do |response_id|
213
+ response = data[response_id]
214
+ responses.push(response[:data])
215
+ self.nest_responses(data, response, responses)
216
+ end
217
+ end
218
+ end
219
+ end
@@ -0,0 +1,186 @@
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 · Your Smart Business Assistant.
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://lesli.tech
27
+ @license GPLv3 http://www.gnu.org/licenses/gpl-3.0.en.html
28
+
29
+ // · ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~
30
+ // ·
31
+
32
+ =end
33
+
34
+ module Lesli
35
+ class CloudObject::Subscriber < ApplicationLesliRecord
36
+ self.abstract_class = true
37
+ belongs_to :user_creator, class_name: "::User", foreign_key: "users_id"
38
+
39
+ # @return [User] This method will always return nil
40
+ # @description At the current time, this is a dummy method that returns nil, so the function is_editable_by? in
41
+ # ApplicationLesliRecord will work without issues
42
+ def user_main
43
+ return nil
44
+ end
45
+
46
+
47
+ # @attribute [Enumerable<Symbol>] action
48
+ # @return [:discussion_created, :action_created, :file_created, :activity_created, :workflow_updated, :priority_updated, :http_get, :http_post, :http_put, :http_patch, :http_update]
49
+ enum action: {
50
+ object_created: "object_created",
51
+ object_updated: "object_updated",
52
+ object_destroyed: "object_destroyed",
53
+ object_status_updated: "object_status_updated",
54
+
55
+ action_created: "action_created",
56
+ action_updated: "action_updated",
57
+ action_destroyed: "action_destroyed",
58
+
59
+ discussion_created: "discussion_created",
60
+ discussion_updated: "discussion_updated",
61
+ discussion_destroyed: "discussion_destroyed",
62
+
63
+ file_created: "file_created",
64
+ file_updated: "file_updated",
65
+ file_destroyed: "file_destroyed"
66
+ }
67
+ validates :action, presence: true, inclusion: { in: :action }
68
+
69
+
70
+ # @attribute [Enumerable<Symbol>] notification_type
71
+ # @return [:web, :email]
72
+ enum notification_type: {
73
+ email: "email", # notification sent by email only
74
+ webpush: "webpush", # notification for web interface only
75
+ mobilepush: "mobilepush", # notification for mobile only
76
+ mobiledialog: "mobiledialog", # open a dialog in the main app screen
77
+ push: "push", # webpush & mobilepush
78
+ }
79
+ validates :notification_type, presence: true, inclusion: { in: :notification_type }
80
+
81
+ # @param cloud_object [ApplicationRecord] Cloud object to which an user can subscribe to
82
+ # @param user [User] The user that is subscribing to the *cloud_ubject*
83
+ # @param action [Symbol] A valid action from this class's *action* enum to wich the *user* will be subscribed
84
+ # @param notification_type [Symbol] A valid notification_type from this class's *notification_type* enum
85
+ # @return [void]
86
+ # @description Subscribes a *user* to one or all *actions* of the *cloud_object*. If no *action* is provided, the *user*
87
+ # is subscribed to all the *actions*
88
+ # @example
89
+ # first_ticket = CloudHelp::Ticket.find( 1 )
90
+ # second_ticket = CloudHelp::Ticket.find( 2 )
91
+ # user = current_user
92
+ # CloudHelp::Ticket::Subscriber.add_subscriber( first_ticket, current_user )
93
+ # CloudHelp::Ticket::Subscriber.add_subscriber( second_ticket, current_user, :http_post, :email )
94
+ def self.add_subscriber(cloud_object, user, action=nil, notification_type= :web)
95
+
96
+ if action
97
+ return cloud_object.subscribers.create(
98
+ user_creator: user,
99
+ action: action,
100
+ notification_type: self.notification_types[notification_type]
101
+ )
102
+ end
103
+
104
+ self.actions.values.each do |action|
105
+ cloud_object.subscribers.create(
106
+ user_creator: user,
107
+ action: action,
108
+ notification_type: self.notification_types[notification_type]
109
+ )
110
+ end
111
+ end
112
+
113
+
114
+
115
+ # @param cloud_object [ApplicationRecord] Cloud object to which an user can subscribe to
116
+ # @param subject [String] The subject that will be shown in the notification
117
+ # @param action [Symbol] A valid action from this class's *action* enum
118
+ # @return [void]
119
+ # @description Notifies all the users subscribed to the *cloud_object*'s *action* using the *Courier* engine except the
120
+ # user that triggered the notification
121
+ # @example
122
+ # ticket = CloudHelp::Ticket.find( 1 )
123
+ # ClodHelp::Ticket::Subscriber.notify_subscribers(
124
+ # ticket,
125
+ # "A new comment has been added to ticket #{ticket.id}",
126
+ # :discussion_created
127
+ # )
128
+ def self.notify_subscribers(current_user, cloud_object, action, subject: nil, body: nil, url: nil)
129
+
130
+ unless cloud_object.subscribers.blank?
131
+ subscribers = cloud_object.subscribers.where(action: action)
132
+ subscribers = subscribers.where("users_id != ?", current_user.id) if current_user
133
+
134
+ subscribers.each do |subscriber|
135
+ Courier::Bell::Notification.new(
136
+ subscriber.user_main || subscriber.user_creator,
137
+ subject || action,
138
+ body: body,
139
+ url: url,
140
+ category: "info",
141
+ channel: subscriber.notification_type
142
+ )
143
+ end
144
+ end
145
+ end
146
+
147
+ # @param cloud_object [ApplicationRecord] Cloud object to which an user can subscribe to
148
+ # @param user [User] The user that is subscribing to the *cloud_object*
149
+ # @return [Array] Array of subscriptions. There is one subscription per *action*.
150
+ # @description Generates an array of subscription, Each element contains a subscription *action*,
151
+ # information about wheter the *user* is subscribed or not and, if the user is subscribed,
152
+ # the notification type of the subscription
153
+ # @example
154
+ # ticket = CloudHelp::Ticket.find( 1 )
155
+ # subscription_actions = CloudHelp::Ticket::Subscriber.subscription_actions(
156
+ # ticket,
157
+ # current_user
158
+ # )
159
+ def self.subscription_actions(cloud_object, user)
160
+ data = { }
161
+ actions = self.actions.keys
162
+ actions.each do |action|
163
+ data[action] = {
164
+ action: action,
165
+ subscribed: false,
166
+ notification_type: :email
167
+ }
168
+ end
169
+ cloud_object.subscribers.where(users_id: user.id).each do |subscriber|
170
+ data[subscriber.action][:id] = subscriber.id
171
+ data[subscriber.action][:subscribed] = true
172
+ data[subscriber.action][:notification_type] = subscriber.notification_type
173
+ end
174
+ data.values
175
+ end
176
+
177
+ # @return [Class] The class of the association 'belongs_to'
178
+ # @description All subscribers belong to a *cloud_object*. This method returns the specific class of
179
+ # that cloud_object.
180
+ # @example
181
+ # puts DeutscheLeibrenten::Project::Discussion.cloud_object_model.new # This will display an instance of DeutscheLeibrenten::Project
182
+ def self.cloud_object_model
183
+ self.reflect_on_association(:cloud_object).klass
184
+ end
185
+ end
186
+ end
@@ -52,12 +52,23 @@ module Lesli
52
52
  # # To execute this function, you must only do
53
53
  # my_account = Account.first
54
54
  # my_account.proposal = CloudProposal::Account.new
55
- def self.initialize_data(account)
56
- self.create!(
57
- account: account,
58
- name: "Default Dashboard",
55
+ def self.initialize_dashboard(modelo, account, components)
56
+
57
+ components.unshift({
58
+ name: "Engine version",
59
+ component_id: "engine-version",
60
+ layout: 2,
61
+ query_configuration: {},
62
+ custom_configuration: {}
63
+ })
64
+
65
+ modelo.create_with(
59
66
  default: true,
60
- main: false
67
+ main: false,
68
+ components_attributes: components
69
+ ).find_or_create_by!(
70
+ account: account,
71
+ name: "Default Dashboard"
61
72
  )
62
73
  end
63
74
 
@@ -30,8 +30,6 @@ Building a better future, one line of code at a time.
30
30
  // ·
31
31
  =end
32
32
 
33
- require "bcrypt"
34
-
35
33
  module Lesli
36
34
  class User::Session < ApplicationLesliRecord
37
35
  belongs_to :user
@@ -52,6 +52,7 @@ module Lesli
52
52
  has_many :sessions
53
53
  has_many :requests
54
54
  has_many :shortcuts
55
+ has_many :notifications, class_name: "LesliBell::Notification"
55
56
  has_many :activities, class_name: "User::Activity"
56
57
 
57
58
  # users can have many roles and too many privileges through the roles
@@ -62,23 +63,22 @@ module Lesli
62
63
 
63
64
 
64
65
  # devise implementation
65
- devise :database_authenticatable,
66
- :registerable,
67
- :rememberable,
68
- :recoverable,
69
- :validatable,
70
- :confirmable,
71
- :trackable
72
- #:omniauthable, omniauth_providers: [:google_oauth2, :facebook]
66
+ #if !defined?(LesliSecurity)
67
+ devise(
68
+ :database_authenticatable,
69
+ :registerable,
70
+ :rememberable,
71
+ :recoverable,
72
+ :validatable,
73
+ :confirmable,
74
+ :trackable);
75
+ #:omniauthable, omniauth_providers: [:google_oauth2, :facebook]
76
+ #end
77
+
73
78
 
74
79
  # users belongs to an account only... and must have a role
75
80
  belongs_to :account, optional: true
76
81
 
77
- # user details are saved on separate table
78
- has_one :detail, inverse_of: :user, autosave: true, dependent: :destroy
79
- accepts_nested_attributes_for :detail, update_only: true
80
-
81
-
82
82
  # users belongs to an account only... and must have a role
83
83
  belongs_to :account, optional: true
84
84
 
@@ -19,7 +19,7 @@ along with this program. If not, see http://www.gnu.org/licenses/.
19
19
 
20
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
@@ -120,6 +120,9 @@ module Lesli
120
120
  # Get the list of controllers and actions from engines
121
121
  Lesli::System.engines.each do |engine, engine_info|
122
122
 
123
+ # Do not process main Rails app
124
+ next if engine == "Root"
125
+
123
126
  # load and retrieve the list of controllers and actions from an engine
124
127
  routes = "#{engine}::Engine".constantize.routes.routes.each do |route|
125
128
  route = route.defaults
@@ -78,9 +78,9 @@ module Lesli
78
78
  # create new account for the new user only if multi-account is allowed
79
79
  if allow_multiaccount === true
80
80
  account = Account.create!({
81
- user: resource, # set user as owner of his just created account
82
- company_name: "Lesli", # temporary company name
83
- status: :active # account is active due user already confirmed his email
81
+ user: resource, # set user as owner of his just created account
82
+ name: "Lesli", # temporary company name
83
+ status: :active # account is active due user already confirmed his email
84
84
  })
85
85
  end
86
86