motor-admin-cstham8 0.4.35

Sign up to get free protection for your applications and to get access to all the features.
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