motor-admin-cstham8 0.4.35

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (167) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +661 -0
  3. data/README.md +230 -0
  4. data/Rakefile +11 -0
  5. data/app/channels/motor/application_cable/channel.rb +14 -0
  6. data/app/channels/motor/application_cable/connection.rb +27 -0
  7. data/app/channels/motor/notes_channel.rb +9 -0
  8. data/app/channels/motor/notifications_channel.rb +9 -0
  9. data/app/controllers/concerns/motor/current_ability.rb +21 -0
  10. data/app/controllers/concerns/motor/current_user_method.rb +18 -0
  11. data/app/controllers/concerns/motor/load_and_authorize_dynamic_resource.rb +73 -0
  12. data/app/controllers/concerns/motor/wrap_io_params.rb +25 -0
  13. data/app/controllers/motor/active_storage_attachments_controller.rb +64 -0
  14. data/app/controllers/motor/alerts_controller.rb +82 -0
  15. data/app/controllers/motor/api_base_controller.rb +33 -0
  16. data/app/controllers/motor/api_configs_controller.rb +54 -0
  17. data/app/controllers/motor/application_controller.rb +8 -0
  18. data/app/controllers/motor/assets_controller.rb +43 -0
  19. data/app/controllers/motor/audits_controller.rb +16 -0
  20. data/app/controllers/motor/auth_tokens_controller.rb +36 -0
  21. data/app/controllers/motor/configs_controller.rb +33 -0
  22. data/app/controllers/motor/dashboards_controller.rb +64 -0
  23. data/app/controllers/motor/data_controller.rb +88 -0
  24. data/app/controllers/motor/forms_controller.rb +61 -0
  25. data/app/controllers/motor/icons_controller.rb +22 -0
  26. data/app/controllers/motor/note_tags_controller.rb +13 -0
  27. data/app/controllers/motor/notes_controller.rb +58 -0
  28. data/app/controllers/motor/notifications_controller.rb +33 -0
  29. data/app/controllers/motor/queries_controller.rb +64 -0
  30. data/app/controllers/motor/reminders_controller.rb +38 -0
  31. data/app/controllers/motor/resource_default_queries_controller.rb +23 -0
  32. data/app/controllers/motor/resource_methods_controller.rb +23 -0
  33. data/app/controllers/motor/resources_controller.rb +26 -0
  34. data/app/controllers/motor/run_api_requests_controller.rb +56 -0
  35. data/app/controllers/motor/run_graphql_requests_controller.rb +48 -0
  36. data/app/controllers/motor/run_queries_controller.rb +77 -0
  37. data/app/controllers/motor/schema_controller.rb +31 -0
  38. data/app/controllers/motor/send_alerts_controller.rb +26 -0
  39. data/app/controllers/motor/sessions_controller.rb +23 -0
  40. data/app/controllers/motor/slack_conversations_controller.rb +11 -0
  41. data/app/controllers/motor/tags_controller.rb +11 -0
  42. data/app/controllers/motor/ui_controller.rb +51 -0
  43. data/app/controllers/motor/users_for_autocomplete_controller.rb +23 -0
  44. data/app/jobs/motor/alert_sending_job.rb +13 -0
  45. data/app/jobs/motor/application_job.rb +6 -0
  46. data/app/jobs/motor/notify_note_mentions_job.rb +9 -0
  47. data/app/jobs/motor/notify_reminder_job.rb +9 -0
  48. data/app/mailers/motor/alerts_mailer.rb +39 -0
  49. data/app/mailers/motor/application_mailer.rb +33 -0
  50. data/app/mailers/motor/notifications_mailer.rb +33 -0
  51. data/app/models/motor/alert.rb +30 -0
  52. data/app/models/motor/alert_lock.rb +7 -0
  53. data/app/models/motor/api_config.rb +28 -0
  54. data/app/models/motor/application_record.rb +18 -0
  55. data/app/models/motor/audit.rb +13 -0
  56. data/app/models/motor/config.rb +13 -0
  57. data/app/models/motor/dashboard.rb +26 -0
  58. data/app/models/motor/form.rb +23 -0
  59. data/app/models/motor/note.rb +18 -0
  60. data/app/models/motor/note_tag.rb +7 -0
  61. data/app/models/motor/note_tag_tag.rb +8 -0
  62. data/app/models/motor/notification.rb +14 -0
  63. data/app/models/motor/query.rb +33 -0
  64. data/app/models/motor/reminder.rb +13 -0
  65. data/app/models/motor/resource.rb +15 -0
  66. data/app/models/motor/tag.rb +7 -0
  67. data/app/models/motor/taggable_tag.rb +8 -0
  68. data/app/views/layouts/motor/application.html.erb +17 -0
  69. data/app/views/layouts/motor/mailer.html.erb +72 -0
  70. data/app/views/motor/alerts_mailer/alert_email.html.erb +54 -0
  71. data/app/views/motor/notifications_mailer/notify_mention_email.html.erb +28 -0
  72. data/app/views/motor/notifications_mailer/notify_reminder_email.html.erb +28 -0
  73. data/app/views/motor/ui/show.html.erb +1 -0
  74. data/config/locales/el.yml +420 -0
  75. data/config/locales/en.yml +340 -0
  76. data/config/locales/es.yml +420 -0
  77. data/config/locales/ja.yml +340 -0
  78. data/config/locales/pt.yml +416 -0
  79. data/config/routes.rb +65 -0
  80. data/lib/generators/motor/install_generator.rb +24 -0
  81. data/lib/generators/motor/install_notes_generator.rb +22 -0
  82. data/lib/generators/motor/migration.rb +17 -0
  83. data/lib/generators/motor/templates/install.rb +271 -0
  84. data/lib/generators/motor/templates/install_api_configs.rb +86 -0
  85. data/lib/generators/motor/templates/install_notes.rb +83 -0
  86. data/lib/generators/motor/templates/upgrade_motor_api_actions.rb +71 -0
  87. data/lib/generators/motor/upgrade_generator.rb +43 -0
  88. data/lib/motor/active_record_utils/action_text_attribute_patch.rb +19 -0
  89. data/lib/motor/active_record_utils/active_record_connection_column_patch.rb +14 -0
  90. data/lib/motor/active_record_utils/active_record_filter.rb +405 -0
  91. data/lib/motor/active_record_utils/active_storage_blob_patch.rb +30 -0
  92. data/lib/motor/active_record_utils/active_storage_links_extension.rb +11 -0
  93. data/lib/motor/active_record_utils/defined_scopes_extension.rb +25 -0
  94. data/lib/motor/active_record_utils/fetch_methods.rb +24 -0
  95. data/lib/motor/active_record_utils/types.rb +64 -0
  96. data/lib/motor/active_record_utils.rb +45 -0
  97. data/lib/motor/admin.rb +141 -0
  98. data/lib/motor/alerts/persistance.rb +97 -0
  99. data/lib/motor/alerts/scheduled_alerts_cache.rb +29 -0
  100. data/lib/motor/alerts/scheduler.rb +30 -0
  101. data/lib/motor/alerts/slack_sender.rb +74 -0
  102. data/lib/motor/alerts.rb +52 -0
  103. data/lib/motor/api_configs.rb +41 -0
  104. data/lib/motor/api_query/apply_scope.rb +44 -0
  105. data/lib/motor/api_query/build_json.rb +171 -0
  106. data/lib/motor/api_query/build_meta.rb +20 -0
  107. data/lib/motor/api_query/filter.rb +125 -0
  108. data/lib/motor/api_query/paginate.rb +19 -0
  109. data/lib/motor/api_query/search.rb +60 -0
  110. data/lib/motor/api_query/sort.rb +64 -0
  111. data/lib/motor/api_query.rb +24 -0
  112. data/lib/motor/assets.rb +62 -0
  113. data/lib/motor/build_schema/active_storage_attachment_schema.rb +125 -0
  114. data/lib/motor/build_schema/adjust_devise_model_schema.rb +60 -0
  115. data/lib/motor/build_schema/apply_permissions.rb +64 -0
  116. data/lib/motor/build_schema/defaults.rb +66 -0
  117. data/lib/motor/build_schema/find_display_column.rb +65 -0
  118. data/lib/motor/build_schema/find_icon.rb +135 -0
  119. data/lib/motor/build_schema/find_searchable_columns.rb +33 -0
  120. data/lib/motor/build_schema/load_from_rails.rb +361 -0
  121. data/lib/motor/build_schema/merge_schema_configs.rb +157 -0
  122. data/lib/motor/build_schema/reorder_schema.rb +88 -0
  123. data/lib/motor/build_schema/utils.rb +31 -0
  124. data/lib/motor/build_schema.rb +125 -0
  125. data/lib/motor/cancan_utils/ability_patch.rb +31 -0
  126. data/lib/motor/cancan_utils/can_manage_all.rb +14 -0
  127. data/lib/motor/cancan_utils.rb +9 -0
  128. data/lib/motor/configs/build_configs_hash.rb +90 -0
  129. data/lib/motor/configs/build_ui_app_tag.rb +177 -0
  130. data/lib/motor/configs/load_from_cache.rb +110 -0
  131. data/lib/motor/configs/sync_from_file.rb +35 -0
  132. data/lib/motor/configs/sync_from_hash.rb +159 -0
  133. data/lib/motor/configs/sync_middleware.rb +72 -0
  134. data/lib/motor/configs/sync_with_remote.rb +47 -0
  135. data/lib/motor/configs/write_to_file.rb +36 -0
  136. data/lib/motor/configs.rb +39 -0
  137. data/lib/motor/dashboards/persistance.rb +73 -0
  138. data/lib/motor/dashboards.rb +8 -0
  139. data/lib/motor/forms/persistance.rb +93 -0
  140. data/lib/motor/forms.rb +8 -0
  141. data/lib/motor/hash_serializer.rb +21 -0
  142. data/lib/motor/net_http_utils.rb +50 -0
  143. data/lib/motor/notes/notify_mentions.rb +71 -0
  144. data/lib/motor/notes/notify_reminder.rb +48 -0
  145. data/lib/motor/notes/persist.rb +36 -0
  146. data/lib/motor/notes/reminders_scheduler.rb +39 -0
  147. data/lib/motor/notes/tags.rb +34 -0
  148. data/lib/motor/notes.rb +12 -0
  149. data/lib/motor/queries/persistance.rb +90 -0
  150. data/lib/motor/queries/postgresql_exec_query.rb +28 -0
  151. data/lib/motor/queries/render_sql_template.rb +61 -0
  152. data/lib/motor/queries/run_query.rb +289 -0
  153. data/lib/motor/queries.rb +11 -0
  154. data/lib/motor/railtie.rb +11 -0
  155. data/lib/motor/resources/custom_sql_columns_cache.rb +17 -0
  156. data/lib/motor/resources/fetch_configured_model.rb +269 -0
  157. data/lib/motor/resources/persist_configs.rb +232 -0
  158. data/lib/motor/resources.rb +19 -0
  159. data/lib/motor/slack/client.rb +62 -0
  160. data/lib/motor/slack.rb +16 -0
  161. data/lib/motor/tags.rb +32 -0
  162. data/lib/motor/tasks/motor.rake +54 -0
  163. data/lib/motor/version.rb +5 -0
  164. data/lib/motor-admin-cstham8.rb +3 -0
  165. data/lib/motor.rb +87 -0
  166. data/ui/dist/manifest.json +1990 -0
  167. metadata +303 -0
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Motor
4
+ class RunApiRequestsController < ApiBaseController
5
+ JWT_TTL = 2.hours
6
+
7
+ wrap_parameters :data
8
+
9
+ def show
10
+ respond_with_result
11
+ end
12
+
13
+ def create
14
+ respond_with_result
15
+ end
16
+
17
+ private
18
+
19
+ # rubocop:disable Metrics/AbcSize
20
+ def respond_with_result
21
+ response = Motor::ApiConfigs.run(find_or_initialize_api_config,
22
+ method: request_params[:method],
23
+ path: request_params[:path],
24
+ body: request_params[:body],
25
+ params: request_params[:params],
26
+ headers: { 'Authorization' => "Bearer #{current_user_jwt}" })
27
+ response.to_hash.each do |key, (value)|
28
+ next if key.casecmp('transfer-encoding').zero?
29
+
30
+ headers[key] = value
31
+ end
32
+
33
+ self.response_body = response.body
34
+ self.status = response.code.to_i
35
+ end
36
+ # rubocop:enable Metrics/AbcSize
37
+
38
+ def find_or_initialize_api_config
39
+ Motor::ApiConfig.find_by(name: request_params[:api_config_name]) ||
40
+ Motor::ApiConfig.new(url: request_params[:api_config_name])
41
+ end
42
+
43
+ def current_user_jwt
44
+ return '' unless defined?(JWT)
45
+ return '' unless current_user
46
+
47
+ payload = { uid: current_user.id, email: current_user.email, exp: JWT_TTL.from_now.to_i }
48
+
49
+ JWT.encode(payload, Rails.application.credentials.secret_key_base)
50
+ end
51
+
52
+ def request_params
53
+ params.require(:data).permit!
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Motor
4
+ class RunGraphqlRequestsController < ApiBaseController
5
+ JWT_TTL = 2.hours
6
+
7
+ wrap_parameters :data
8
+
9
+ def create
10
+ respond_with_result
11
+ end
12
+
13
+ private
14
+
15
+ def respond_with_result
16
+ response = Motor::ApiConfigs.run_grapql(find_or_initialize_api_config,
17
+ query: request_params[:query],
18
+ variables: request_params[:variables],
19
+ headers: { 'Authorization' => "Bearer #{current_user_jwt}" })
20
+ response.to_hash.each do |key, (value)|
21
+ next if key.casecmp('transfer-encoding').zero?
22
+
23
+ headers[key] = value
24
+ end
25
+
26
+ self.response_body = response.body
27
+ self.status = response.code.to_i
28
+ end
29
+
30
+ def find_or_initialize_api_config
31
+ Motor::ApiConfig.find_by(name: request_params[:api_config_name]) ||
32
+ Motor::ApiConfig.new(url: request_params[:api_config_name])
33
+ end
34
+
35
+ def current_user_jwt
36
+ return '' unless defined?(JWT)
37
+ return '' unless current_user
38
+
39
+ payload = { uid: current_user.id, email: current_user.email, exp: JWT_TTL.from_now.to_i }
40
+
41
+ JWT.encode(payload, Rails.application.credentials.secret_key_base)
42
+ end
43
+
44
+ def request_params
45
+ params.require(:data).permit!
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Motor
4
+ class RunQueriesController < ApiBaseController
5
+ wrap_parameters :data
6
+
7
+ load_and_authorize_resource :query, only: :show, parent: false
8
+
9
+ before_action :build_query, only: :create
10
+ authorize_resource :query, only: :create
11
+
12
+ def show
13
+ render_result
14
+ end
15
+
16
+ def create
17
+ render_result
18
+ end
19
+
20
+ private
21
+
22
+ def render_result
23
+ authorize_queries!(@query)
24
+
25
+ query_result = Queries::RunQuery.call(@query, variables_hash: variables_params.to_unsafe_h,
26
+ limit: params[:limit].presence,
27
+ filters: filter_params)
28
+
29
+ if query_result.error
30
+ render json: { errors: [{ detail: query_result.error }] }, status: :unprocessable_entity
31
+ else
32
+ render json: query_result_hash(query_result)
33
+ end
34
+ end
35
+
36
+ def authorize_queries!(query)
37
+ query.sql_body.to_s.scan(/query_\d+/).each do |name|
38
+ Motor::Query.accessible_by(current_ability).find(name.split('_').last)
39
+ end
40
+ end
41
+
42
+ def current_user_variables
43
+ return {} unless current_user
44
+
45
+ current_user
46
+ .attributes
47
+ .slice('id', 'email')
48
+ .transform_keys { |key| "current_user_#{key}" }
49
+ .compact
50
+ end
51
+
52
+ def query_result_hash(query_result)
53
+ {
54
+ data: query_result.data,
55
+ meta: {
56
+ columns: query_result.columns
57
+ }
58
+ }
59
+ end
60
+
61
+ def build_query
62
+ @query = Motor::Queries::Persistance.build_from_params(query_params)
63
+ end
64
+
65
+ def query_params
66
+ params.require(:data).permit(:sql_body, preferences: {})
67
+ end
68
+
69
+ def variables_params
70
+ params.fetch(:variables, {}).merge(current_user_variables)
71
+ end
72
+
73
+ def filter_params
74
+ (params[:filter] || params[:filters])&.to_unsafe_h
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Motor
4
+ class SchemaController < ApiBaseController
5
+ skip_authorization_check
6
+
7
+ before_action :authorize_resource, only: :show
8
+
9
+ def index
10
+ render json: { data: schema }
11
+ end
12
+
13
+ def show
14
+ render json: { data: schema.find { |model| model[:name] == resource_class.name.underscore } }
15
+ end
16
+
17
+ private
18
+
19
+ def schema
20
+ @schema ||= Motor::BuildSchema.call(Configs::LoadFromCache.load_cache_keys, current_ability)
21
+ end
22
+
23
+ def resource_class
24
+ @resource_class ||= Motor::BuildSchema::Utils.classify_slug(params[:resource])
25
+ end
26
+
27
+ def authorize_resource
28
+ authorize!(resource_class, :read)
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Motor
4
+ class SendAlertsController < ApiBaseController
5
+ wrap_parameters :data
6
+
7
+ before_action :build_alert, only: :create
8
+ authorize_resource :alert, only: :create
9
+
10
+ def create
11
+ Motor::Alerts.send_alert(@alert)
12
+
13
+ head :ok
14
+ end
15
+
16
+ private
17
+
18
+ def build_alert
19
+ @alert = Motor::Alerts::Persistance.build_from_params(alert_params)
20
+ end
21
+
22
+ def alert_params
23
+ params.require(:data).permit(:query_id, :name, :to_emails, :description, preferences: {})
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Motor
4
+ class SessionsController < ApiBaseController
5
+ include ActionController::Cookies
6
+
7
+ skip_authorization_check
8
+
9
+ def show
10
+ render json: {
11
+ current_user_email: current_user&.email,
12
+ current_user_id: current_user&.id
13
+ }
14
+ end
15
+
16
+ def destroy
17
+ session.clear
18
+ cookies.clear
19
+
20
+ head :ok
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Motor
4
+ class SlackConversationsController < ApiBaseController
5
+ def index
6
+ authorize!(:create, Motor::Alert)
7
+
8
+ render json: { data: Motor::Slack.conversations }
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Motor
4
+ class TagsController < ApiBaseController
5
+ load_and_authorize_resource :tag
6
+
7
+ def index
8
+ render json: { data: @tags.to_a }
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Motor
4
+ class UiController < ApplicationController
5
+ layout 'motor/application'
6
+
7
+ helper_method :current_user, :current_ability, :cache_keys, :custom_html
8
+
9
+ before_action :set_i18n_locale
10
+
11
+ def index
12
+ render_ui
13
+ end
14
+
15
+ def show
16
+ render_ui
17
+ end
18
+
19
+ def new
20
+ render_ui
21
+ end
22
+
23
+ private
24
+
25
+ def render_ui
26
+ Motor.reload! if Motor.development?
27
+
28
+ Motor::Configs::SyncFromFile.call
29
+
30
+ render :show
31
+ end
32
+
33
+ def custom_html
34
+ Motor::Admin.config.custom_html.presence || begin
35
+ configs = Motor::Configs::LoadFromCache.load_configs(cache_key: cache_keys[:configs])
36
+
37
+ configs.find { |c| c.key == 'ui.custom_html' }&.value
38
+ end
39
+ end
40
+
41
+ def set_i18n_locale
42
+ configs = Motor::Configs::LoadFromCache.load_configs(cache_key: cache_keys[:configs])
43
+
44
+ I18n.locale = configs.find { |c| c.key == 'language' }&.value&.to_sym || I18n.locale
45
+ end
46
+
47
+ def cache_keys
48
+ @cache_keys ||= Configs::LoadFromCache.load_cache_keys
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Motor
4
+ class UsersForAutocompleteController < ApiBaseController
5
+ def index
6
+ authorize!(:create, Motor::Note)
7
+
8
+ render json: { data: user_emails }
9
+ end
10
+
11
+ private
12
+
13
+ def user_emails
14
+ user_class = Motor::AdminUser if defined?(Motor::AdminUser)
15
+ user_class ||= AdminUser if defined?(AdminUser)
16
+ user_class ||= User if defined?(User)
17
+
18
+ return user_class.distinct.limit(100).pluck(:email) if user_class
19
+
20
+ []
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Motor
4
+ class AlertSendingJob < ApplicationJob
5
+ def perform(alert)
6
+ Motor::AlertLock.create!(alert_id: alert.id, lock_timestamp: alert.cron.previous_time.to_i)
7
+
8
+ Motor::Alerts.send_alert(alert)
9
+ rescue ActiveRecord::RecordNotUnique
10
+ nil
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Motor
4
+ class ApplicationJob < ActiveJob::Base
5
+ end
6
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Motor
4
+ class NotifyNoteMentionsJob < ApplicationJob
5
+ def perform(note, current_user)
6
+ Motor::Notes::NotifyMentions.call(note, current_user)
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Motor
4
+ class NotifyReminderJob < ApplicationJob
5
+ def perform(reminder)
6
+ Motor::Notes::NotifyReminder.call(reminder)
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Motor
4
+ class AlertsMailer < ApplicationMailer
5
+ SenderAddressNotSet = Class.new(StandardError)
6
+ SENDER_NOT_SET_ERROR_MESSAGE =
7
+ 'Please specify your sender address via MOTOR_ALERTS_FROM_ADDRESS environment variable'
8
+
9
+ def alert_email(alert, email: nil)
10
+ @alert = alert
11
+ @query_result = Queries::RunQuery.call(alert.query,
12
+ variables_hash: { current_user_email: email })
13
+
14
+ return if @alert.preferences[:send_empty].blank? && @query_result.data.blank?
15
+
16
+ assign_attachment(@alert, @query_result)
17
+
18
+ raise SenderAddressNotSet, SENDER_NOT_SET_ERROR_MESSAGE unless from_address
19
+
20
+ mail(
21
+ from: from_address,
22
+ to: email || alert.to_emails,
23
+ subject: alert.name.presence || @alert.query.name
24
+ )
25
+ end
26
+
27
+ private
28
+
29
+ def assign_attachment(alert, query_result)
30
+ attachments["#{alert.name.presence || 'data'}.csv"] = generate_csv(query_result)
31
+ end
32
+
33
+ def generate_csv(query_result)
34
+ rows = [query_result.columns.pluck(:name)] + query_result.data
35
+
36
+ rows.map(&:to_csv).join
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Motor
4
+ class ApplicationMailer < ActionMailer::Base
5
+ layout 'motor/mailer'
6
+
7
+ private
8
+
9
+ def from_address
10
+ from = ENV['MOTOR_ALERTS_FROM_ADDRESS'].presence || ENV['MOTOR_EMAIL_ADDRESS'].presence
11
+
12
+ from ||= application_mailer_default_from
13
+ from ||= mailer_config_from_address
14
+ from ||= "reports@#{ENV['HOST'].delete_prefix('www.')}" if ENV['HOST'].present?
15
+
16
+ from ||= 'reports@example.com' if Rails.env.development?
17
+
18
+ from
19
+ end
20
+
21
+ def application_mailer_default_from
22
+ return if !defined?(::ApplicationMailer) || ::ApplicationMailer.default[:from].to_s.include?('example.com')
23
+
24
+ ::ApplicationMailer.default[:from].presence
25
+ end
26
+
27
+ def mailer_config_from_address
28
+ return if Rails.application.config.action_mailer.default_url_options&.dig(:host).blank?
29
+
30
+ "reports@#{Rails.application.config.action_mailer.default_url_options[:host].delete_prefix('www.')}"
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Motor
4
+ class NotificationsMailer < ApplicationMailer
5
+ def notify_mention_email(notification)
6
+ @notification = notification
7
+
8
+ @note = notification.record
9
+
10
+ subject =
11
+ "#{@note.author&.email || 'Anonymous'} mentioned you in " \
12
+ "#{@note.record.class.model_name.human} ##{@note.record.id}"
13
+
14
+ mail(
15
+ from: from_address,
16
+ to: notification.recipient.email,
17
+ subject: subject
18
+ )
19
+ end
20
+
21
+ def notify_reminder_email(notification)
22
+ @notification = notification
23
+
24
+ @note = notification.record.record
25
+
26
+ mail(
27
+ from: from_address,
28
+ to: notification.recipient.email,
29
+ subject: "Reminder for #{@note.record.class.model_name.human} ##{@note.record.id}"
30
+ )
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Motor
4
+ class Alert < ::Motor::ApplicationRecord
5
+ audited
6
+
7
+ belongs_to :query
8
+ belongs_to :author, polymorphic: true, optional: true
9
+
10
+ has_many :alert_locks, dependent: :destroy
11
+ has_many :taggable_tags, as: :taggable, dependent: :destroy
12
+ has_many :tags, through: :taggable_tags, class_name: 'Motor::Tag'
13
+
14
+ attribute :preferences, default: -> { ActiveSupport::HashWithIndifferentAccess.new }
15
+
16
+ if Rails.version.to_f >= 7.1
17
+ serialize :preferences, coder: HashSerializer
18
+ else
19
+ serialize :preferences, HashSerializer
20
+ end
21
+
22
+ scope :active, -> { where(deleted_at: nil) }
23
+ scope :enabled, -> { where(is_enabled: true) }
24
+
25
+ def cron
26
+ @cron ||=
27
+ Fugit::Nat.parse("#{preferences[:interval]} #{ActiveSupport::TimeZone::MAPPING[preferences[:timezone]]}")
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Motor
4
+ class AlertLock < ::Motor::ApplicationRecord
5
+ belongs_to :alert
6
+ end
7
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Motor
4
+ class ApiConfig < ::Motor::ApplicationRecord
5
+ encrypts :credentials if defined?(::Motor::EncryptedConfig)
6
+
7
+ attribute :preferences, default: -> { ActiveSupport::HashWithIndifferentAccess.new }
8
+ attribute :credentials, default: -> { ActiveSupport::HashWithIndifferentAccess.new }
9
+
10
+ if Rails.version.to_f >= 7.1
11
+ serialize :credentials, coder: Motor::HashSerializer
12
+ serialize :preferences, coder: Motor::HashSerializer
13
+ else
14
+ serialize :credentials, Motor::HashSerializer
15
+ serialize :preferences, Motor::HashSerializer
16
+ end
17
+
18
+ has_one :form, dependent: nil, foreign_key: :api_config_name, primary_key: :name, inverse_of: :api_config
19
+
20
+ scope :active, -> { where(deleted_at: nil) }
21
+
22
+ def headers
23
+ credentials.fetch(:headers, []).each_with_object({}) do |item, acc|
24
+ acc[item[:key]] = item[:value]
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Motor
4
+ class ApplicationRecord < ActiveRecord::Base
5
+ self.abstract_class = true
6
+ self.table_name_prefix = 'motor_'
7
+
8
+ def self.audited(*args)
9
+ default_class = Audited.audit_class
10
+
11
+ Audited.audit_class = 'Motor::Audit'
12
+
13
+ super
14
+ ensure
15
+ Audited.audit_class = default_class
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Motor
4
+ class Audit < Audited::Audit
5
+ self.table_name = 'motor_audits'
6
+
7
+ if Rails.version.to_f >= 7.1
8
+ serialize :audited_changes, coder: HashSerializer
9
+ else
10
+ serialize :audited_changes, HashSerializer
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Motor
4
+ class Config < ::Motor::ApplicationRecord
5
+ audited
6
+
7
+ if Rails.version.to_f >= 7.1
8
+ serialize :value, coder: HashSerializer
9
+ else
10
+ serialize :value, HashSerializer
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Motor
4
+ class Dashboard < ::Motor::ApplicationRecord
5
+ audited
6
+
7
+ belongs_to :author, polymorphic: true, optional: true
8
+
9
+ has_many :taggable_tags, as: :taggable, dependent: :destroy
10
+ has_many :tags, through: :taggable_tags, class_name: 'Motor::Tag'
11
+
12
+ attribute :preferences, default: -> { ActiveSupport::HashWithIndifferentAccess.new }
13
+
14
+ if Rails.version.to_f >= 7.1
15
+ serialize :preferences, coder: HashSerializer
16
+ else
17
+ serialize :preferences, HashSerializer
18
+ end
19
+
20
+ scope :active, -> { where(deleted_at: nil) }
21
+
22
+ def queries
23
+ Motor::Query.where(id: preferences[:layout].pluck(:query_id))
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Motor
4
+ class Form < ::Motor::ApplicationRecord
5
+ audited
6
+
7
+ belongs_to :author, polymorphic: true, optional: true
8
+ belongs_to :api_config, foreign_key: :api_config_name, primary_key: :name, inverse_of: :form
9
+
10
+ has_many :taggable_tags, as: :taggable, dependent: :destroy
11
+ has_many :tags, through: :taggable_tags, class_name: 'Motor::Tag'
12
+
13
+ attribute :preferences, default: -> { ActiveSupport::HashWithIndifferentAccess.new }
14
+
15
+ if Rails.version.to_f >= 7.1
16
+ serialize :preferences, coder: HashSerializer
17
+ else
18
+ serialize :preferences, HashSerializer
19
+ end
20
+
21
+ scope :active, -> { where(deleted_at: nil) }
22
+ end
23
+ end