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
data/README.md ADDED
@@ -0,0 +1,230 @@
1
+ <div align="center">
2
+
3
+ [![Motor Admin Rails](https://user-images.githubusercontent.com/5418788/140520844-a947845d-b579-4b3f-9b49-c539ad3cf580.png)](https://www.getmotoradmin.com/ruby-on-rails)
4
+
5
+ # Motor Admin Rails
6
+
7
+ Low-code Admin panel and Business intelligence Rails engine **(no DSL - configurable from the UI)**.
8
+
9
+ 🤓 [Demo App](https://app.getmotoradmin.com/demo/) | 👀 [Features overview](https://www.youtube.com/watch?v=ngVoci8Hll4&list=PLu7llEMh0KcOkR3Uy_RJT0cXPZQKAYVsq&index=1) | ⭐ [Pro](https://www.getmotoradmin.com/ruby-on-rails)
10
+ </div>
11
+
12
+ [![Admin Panel](https://user-images.githubusercontent.com/5418788/119318538-1f30e300-bc82-11eb-94a4-107c31c93b13.png)](https://app.getmotoradmin.com/demo/)
13
+
14
+ ## Development
15
+
16
+ 1. Clone the repository
17
+ ```ruby
18
+ git clone git@github.com:cstham8/motor-admin-rails.git
19
+ ```
20
+
21
+ 2. Update the following files.
22
+
23
+ * **Gemfile:** Provides a user-friendly web interface for generating short URLs.
24
+ * **database.yml:** Use postgresql
25
+ * **spec/dummy/config/routes.rb** Use devise gem to enable authentication
26
+ * **manifest.js:** Remove motor_manifest.js from links
27
+
28
+ 3. Use the following commands to initialize the project and ensure that the node version is 18.
29
+
30
+ ```ruby
31
+ bundle
32
+ nvm use 18
33
+ rails app:webpacker:install
34
+ yarn add --dev webpack webpack-cli webpack-dev-server
35
+ yarn serve
36
+ rake app:db:create && rake app:db:setup
37
+ ```
38
+
39
+ 4. Update the following file.
40
+
41
+ * **dummy/app/views/layouts/application.html.erb:** Remove javascript_link_tag generated by app:webpacker:install command in previous step.
42
+
43
+ 3. Start rails server
44
+
45
+ ```ruby
46
+ MOTOR_DEVELOPMENT=true rails s
47
+ ```
48
+ Go to http://localhost:3000 to access the motor rails app.
49
+
50
+ ## Installation
51
+ Add this line to your application's Gemfile:
52
+
53
+ ```ruby
54
+ gem 'motor-admin'
55
+ ```
56
+
57
+ And then execute:
58
+ ```bash
59
+ $ bundle install
60
+ ```
61
+
62
+ Create and run migration:
63
+ ```bash
64
+ $ rails motor:install && rake db:migrate
65
+ ```
66
+
67
+ ## Features
68
+
69
+ - [Motor Admin Rails](#motor-admin-rails)
70
+ - [Development](#development)
71
+ - [Installation](#installation)
72
+ - [Features](#features)
73
+ - [Pro](#pro)
74
+ - [Customizable CRUD](#customizable-crud)
75
+ - [Custom Actions](#custom-actions)
76
+ - [Virtual attributes](#virtual-attributes)
77
+ - [Forms Builder](#forms-builder)
78
+ - [SQL Queries](#sql-queries)
79
+ - [Data Visualization](#data-visualization)
80
+ - [Dashboards](#dashboards)
81
+ - [Email Alerts](#email-alerts)
82
+ - [Intelligence Search](#intelligence-search)
83
+ - [Authorization](#authorization)
84
+ - [Active Storage](#active-storage)
85
+ - [I18n](#i18n)
86
+ - [Optimized for Mobile](#optimized-for-mobile)
87
+ - [Configurations Sync](#configurations-sync)
88
+ - [Authentication](#authentication)
89
+
90
+ ## [Pro](https://www.getmotoradmin.com/ruby-on-rails)
91
+
92
+ * Custom styling and logo (white label)
93
+ * Multi-factor authentication
94
+ * SSO/SAML
95
+ * [learn more](https://www.getmotoradmin.com/ruby-on-rails)
96
+
97
+ ### Customizable CRUD
98
+
99
+ ![Resource settings](https://user-images.githubusercontent.com/5418788/119318569-2a840e80-bc82-11eb-9ba3-f3964eb6f997.png)
100
+
101
+ ![Settings UI](https://user-images.githubusercontent.com/5418788/119263883-90708780-bbe9-11eb-9f9f-f76fed0b7f27.png)
102
+
103
+ Everything in the admin panel can be configured using the intuitive settings UI, which can be opened via the icon in the top right corner.
104
+
105
+ Data displayed on the resource page can be completely customized via [SQL queries](#sql-queries) and [dashboards](#dashboards) attached to the resource as a tab. Usually, queries used to display resource data should contain `{{resource_name_id}}` [variable](#sql-queries).
106
+
107
+ [Learn more about resource customizations](https://github.com/motor-admin/motor-admin-rails/blob/master/guides/customizing_resource_table.md)
108
+
109
+ ### Custom Actions
110
+
111
+ ![Custom actions](https://user-images.githubusercontent.com/5418788/119266132-3c1dd580-bbf2-11eb-9666-09e1640eaf7b.png)
112
+
113
+ Custom resource actions can be added via Active Record method call, API endpoint, or [custom forms](#forms-builder). Also, it's possible to override default create/update/delete actions.
114
+
115
+ ### Virtual attributes
116
+
117
+ Any ActiveRecord model method or attribute can be exposed to the admin panel by adding a new column with the name that matches the method name from the resource model:
118
+
119
+ ```ruby
120
+ class Customer < ApplicationRecord
121
+ has_many :orders
122
+
123
+ def lifetime_value
124
+ orders.sum(&:total_price)
125
+ end
126
+ end
127
+ ```
128
+
129
+ ![Virtual attribute](https://user-images.githubusercontent.com/5418788/123373683-76321c80-d58e-11eb-914d-675444b7eb2a.png)
130
+
131
+ ### Forms Builder
132
+
133
+ ![Custom form](https://user-images.githubusercontent.com/5418788/119264008-1391dd80-bbea-11eb-9f14-cb405e77fb60.png)
134
+
135
+ Values from the form fields can be used in API path via `{field_name}` syntax: `/api/some-endpoint/{resource_id}/apply`.
136
+
137
+ [Learn more about custom forms builder](https://github.com/motor-admin/motor-admin-rails/blob/master/guides/building_custom_forms.md)
138
+
139
+ ### SQL Queries
140
+
141
+ ![SQL query](https://user-images.githubusercontent.com/5418788/119264127-84d19080-bbea-11eb-9903-ef465d1d2c97.png)
142
+
143
+ Queries can include embedded variables via `{{variable}}` syntax ([mustache](https://mustache.github.io)). `{{#variable}} ... {{/variable}}` syntax allows to decide if conditions inside the scope should be included in the query.
144
+
145
+ ### Data Visualization
146
+
147
+ ![motor-visualization](https://user-images.githubusercontent.com/5418788/119264625-a2075e80-bbec-11eb-986c-6106dd6e47ce.png)
148
+
149
+ Data from the SQL query can be represented as: table, number, line chart, bar chart, pie chart, funnel, markdown.
150
+
151
+ ### Dashboards
152
+
153
+ ![Dashboard](https://user-images.githubusercontent.com/5418788/119264726-f579ac80-bbec-11eb-852e-8055f8aba200.png)
154
+
155
+ SQL queries can be organized into dashboards to create a convenient representation of the data.
156
+
157
+ ### Email Alerts
158
+
159
+ ![Email alert](https://user-images.githubusercontent.com/5418788/119265049-feb74900-bbed-11eb-8070-bcc8d6113b9b.png)
160
+
161
+ Query data can be sent via email periodically using the alerts feature. Interval of the alert email can be specified using natural language, e.g., `every day at midnight`, `every Monday at 8 PM`, `every weekday at 6AM and 6PM`, `every minute`.
162
+
163
+ Sender address can be specified using `MOTOR_ALERTS_FROM_ADDRESS` environment variable.
164
+
165
+ ### Intelligence Search
166
+
167
+ ![Intelligence search](https://user-images.githubusercontent.com/5418788/119266559-eea26800-bbf3-11eb-8cb3-d0538aa386a9.png)
168
+
169
+ Intelligence search can be opened via the top right corner button or using <kbd>Cmd</kbd> + <kbd>K</kbd> shortcut.
170
+
171
+ ### Authorization
172
+
173
+ Motor Admin allows to set row-level and column-level permissions via [cancan](https://github.com/CanCanCommunity/cancancan) gem. Admin UI permissions should be defined in `app/models/motor/ability.rb` file in `Motor::Ability` class. See [Motor Admin guide](https://github.com/motor-admin/motor-admin-rails/blob/master/guides/defining_permissions.md) and [CanCan documentation](https://github.com/CanCanCommunity/cancancan/blob/develop/docs/Defining-Abilities.md) to learn how to define user permissions.
174
+
175
+ ### Active Storage
176
+
177
+ Motor Admin is configured by default to perform uploads to the provider you configured in your `storage.yml` file for Active Storage. If you are using large uploads within Motor Admin you will need to enable direct uploads by setting the following ENV variable.
178
+
179
+ ```sh
180
+ MOTOR_ACTIVE_STORAGE_DIRECT_UPLOADS_ENABLED=true
181
+ ```
182
+
183
+ _Note: At the moment, this will enable direct uploads globally_
184
+
185
+ ### I18n
186
+
187
+ Motor Admin can use Rails ActiveRecord i18n keys to render resource translations:
188
+
189
+ ```yml
190
+ es:
191
+ activerecord:
192
+ models:
193
+ customer:
194
+ one: Cliente
195
+ other: Clientes
196
+ attributes:
197
+ customer:
198
+ name: Nombre
199
+ scopes:
200
+ customer:
201
+ enabled: Activado
202
+ ```
203
+
204
+ ### Optimized for Mobile
205
+
206
+ ![motor-mobile](https://user-images.githubusercontent.com/5418788/119269566-03392d00-bc01-11eb-9e9d-1f6a58fe0749.png)
207
+
208
+
209
+ ### Configurations Sync
210
+
211
+ All admin panel configurations are automatically stored in the `config/motor.yml` file. It's recommended to include this file in the application git repository to always have the admin panel configurations in sync across different local and remote environments.
212
+
213
+ It's possible to sync local development admin panel configurations with remote production application via `rake motor:sync` task:
214
+
215
+ ```bash
216
+ MOTOR_SYNC_REMOTE_URL=https://remote-app-url/ MOTOR_SYNC_API_KEY=secure-random-string rake motor:sync
217
+ ```
218
+
219
+
220
+ ## Authentication
221
+
222
+ Admin panel can be secured with 'Basic authentication' by specifying `MOTOR_AUTH_USERNAME` and `MOTOR_AUTH_PASSWORD` environment variables.
223
+
224
+ Alternatively, it can be secured with [devise](https://github.com/heartcombo/devise/wiki/How-To:-Define-resource-actions-that-require-authentication-using-routes.rb) or any other authentication library used by the application:
225
+
226
+ ```ruby
227
+ authenticate :admin_user do
228
+ mount Motor::Admin => '/admin'
229
+ end
230
+ ```
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+
5
+ APP_RAKEFILE = File.expand_path('spec/dummy/Rakefile', __dir__)
6
+
7
+ load 'rails/tasks/engine.rake'
8
+
9
+ load 'rails/tasks/statistics.rake'
10
+
11
+ require 'bundler/gem_tasks'
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Motor
4
+ class DummyChannel
5
+ def self.broadcast_to(*_args)
6
+ nil
7
+ end
8
+ end
9
+
10
+ module ApplicationCable
11
+ class Channel < defined?(ActionCable) ? ActionCable::Channel::Base : Motor::DummyChannel
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Motor
4
+ module ApplicationCable
5
+ class Connection < defined?(ActionCable) ? ActionCable::Connection::Base : Object
6
+ identified_by :current_user if defined?(ActionCable)
7
+
8
+ def connect
9
+ self.current_user = find_verified_user
10
+ end
11
+
12
+ private
13
+
14
+ def find_verified_user
15
+ return unless env['warden']
16
+
17
+ if env['warden'].respond_to?(:admin_user)
18
+ env['warden'].admin_user
19
+ elsif env['warden'].respond_to?(:admin)
20
+ env['warden'].admin
21
+ else
22
+ env['warden'].user
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Motor
4
+ class NotesChannel < ::Motor::ApplicationCable::Channel
5
+ def subscribed
6
+ stream_from "motor:notes:#{params[:room]}"
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Motor
4
+ class NotificationsChannel < ::Motor::ApplicationCable::Channel
5
+ def subscribed
6
+ stream_for current_user if respond_to?(:current_user)
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Motor
4
+ module CurrentAbility
5
+ def current_ability
6
+ @current_ability ||=
7
+ if defined?(Motor::Ability)
8
+ klass = Motor::Ability.dup.tap do |k|
9
+ k.prepend(Motor::CancanUtils::AbilityPatch)
10
+ end
11
+
12
+ params = [current_user]
13
+ params << request if Motor::Ability.instance_method(:initialize).arity == 2
14
+
15
+ klass.new(*params)
16
+ else
17
+ Motor::CancanUtils::CanManageAll.new
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Motor
4
+ module CurrentUserMethod
5
+ def current_user
6
+ @current_user ||=
7
+ if defined?(current_admin)
8
+ current_admin
9
+ elsif defined?(current_admin_user)
10
+ return Motor::AdminUser.public if current_admin_user.nil? && Motor.with_public_access?
11
+
12
+ current_admin_user
13
+ elsif defined?(super)
14
+ super
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Motor
4
+ module LoadAndAuthorizeDynamicResource
5
+ extend ActiveSupport::Concern
6
+
7
+ MUTEX = Mutex.new
8
+
9
+ INSTANCE_VARIABLE_NAME = 'resource'
10
+ ASSOCIATION_INSTANCE_VARIABLE_NAME = 'associated_resource'
11
+
12
+ included do
13
+ before_action :load_and_authorize_resource
14
+ before_action :load_and_authorize_association
15
+ end
16
+
17
+ def resource_class
18
+ @resource_class ||= MUTEX.synchronize do
19
+ Motor::Resources::FetchConfiguredModel.call(
20
+ Motor::BuildSchema::Utils.classify_slug(resource_name_prefix + params[:resource]),
21
+ cache_key: Motor::Resource.maximum(:updated_at)
22
+ )
23
+ end
24
+ end
25
+
26
+ def resource_name_prefix
27
+ ''
28
+ end
29
+
30
+ def load_and_authorize_resource
31
+ options = {
32
+ class: resource_class,
33
+ parent: params[:association].present?,
34
+ instance_name: INSTANCE_VARIABLE_NAME
35
+ }
36
+
37
+ if params[:resource_id].present?
38
+ options = options.merge(
39
+ parent: true,
40
+ id_param: :resource_id
41
+ )
42
+ end
43
+
44
+ CanCan::ControllerResource.new(
45
+ self,
46
+ options
47
+ ).load_and_authorize_resource
48
+ rescue ActiveRecord::RecordNotFound
49
+ head :not_found
50
+ end
51
+
52
+ def load_and_authorize_association
53
+ return if params[:association].blank?
54
+
55
+ association = resource_class.reflections[params[:association]]
56
+
57
+ if association
58
+ CanCan::ControllerResource.new(
59
+ self,
60
+ class: association.klass,
61
+ parent: false,
62
+ through: :resource,
63
+ through_association: params[:association].to_sym,
64
+ instance_name: params[:action] == 'create' ? ASSOCIATION_INSTANCE_VARIABLE_NAME : INSTANCE_VARIABLE_NAME
65
+ ).load_and_authorize_resource
66
+ else
67
+ render json: { message: 'Unknown association' }, status: :not_found
68
+ end
69
+ rescue ActiveRecord::RecordNotFound
70
+ head :not_found
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Motor
4
+ module WrapIoParams
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ before_action :wrap_io_params, only: %i[update create]
9
+ end
10
+
11
+ private
12
+
13
+ def wrap_io_params(hash = params)
14
+ hash.each do |key, value|
15
+ if key == 'io'
16
+ hash[key] = StringIO.new(value.encode('ISO-8859-1'))
17
+ elsif value.is_a?(ActionController::Parameters)
18
+ wrap_io_params(value)
19
+ end
20
+ end
21
+
22
+ hash
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Motor
4
+ class ActiveStorageAttachmentsController < ApiBaseController
5
+ include Motor::WrapIoParams
6
+
7
+ wrap_parameters :data, except: %i[include fields]
8
+
9
+ load_and_authorize_resource :attachment, class: 'ActiveStorage::Attachment', parent: false
10
+
11
+ def create
12
+ blob = if file_params[:key]
13
+ ActiveStorage::Blob.create!(file_params)
14
+ else
15
+ ActiveStorage::Blob.create_and_upload!(file_params)
16
+ end
17
+
18
+ @attachment.assign_attributes(blob: blob, record: record)
19
+ @attachment.assign_attributes(record_type: '', record_id: 0) unless record
20
+
21
+ if @attachment.save(validate: false)
22
+ render json: { data: Motor::ApiQuery::BuildJson.call(@attachment, params, current_ability) }
23
+ else
24
+ render json: { errors: @attachment.errors }, status: :unprocessable_entity
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ def record
31
+ @record ||=
32
+ if @attachment.record
33
+ record_pk = @attachment.record.class.primary_key
34
+
35
+ Motor::Resources::FetchConfiguredModel.call(
36
+ @attachment.record.class,
37
+ cache_key: Motor::Resource.maximum(:updated_at)
38
+ ).find_by(record_pk => @attachment.record[record_pk])
39
+ else
40
+ current_user
41
+ end
42
+ end
43
+
44
+ def file_params
45
+ attrs = params.require(:data).require(:file).permit(:io, :filename, :key,
46
+ :checksum, :byte_size,
47
+ :content_type).to_h.symbolize_keys
48
+
49
+ return attrs if params.dig(:data, :file, :base64).blank?
50
+
51
+ attrs[:io] = StringIO.new(Base64.urlsafe_decode64(params[:data][:file][:base64]))
52
+
53
+ attrs
54
+ end
55
+
56
+ def attachment_params
57
+ if params[:data].present?
58
+ params.require(:data).except(:file).permit(:name, :record_type, :record_id)
59
+ else
60
+ {}
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Motor
4
+ class AlertsController < ApiBaseController
5
+ wrap_parameters :data, except: %i[include fields]
6
+
7
+ load_and_authorize_resource :alert, only: %i[index show update destroy]
8
+
9
+ before_action :build_alert, only: :create
10
+ authorize_resource :alert, only: :create
11
+
12
+ def index
13
+ render json: { data: Motor::ApiQuery::BuildJson.call(@alerts.active, params, current_ability) }
14
+ end
15
+
16
+ def show
17
+ render json: { data: Motor::ApiQuery::BuildJson.call(@alert, params, current_ability) }
18
+ end
19
+
20
+ def create
21
+ if Motor::Alerts::Persistance.name_already_exists?(@alert)
22
+ name_already_exists_response
23
+ else
24
+ ApplicationRecord.transaction { @alert.save! }
25
+ Motor::Alerts::ScheduledAlertsCache.clear
26
+ Motor::Configs::WriteToFile.call
27
+
28
+ render json: { data: Motor::ApiQuery::BuildJson.call(@alert, params, current_ability) }
29
+ end
30
+ rescue Motor::Alerts::Persistance::InvalidInterval
31
+ invalid_interval_response
32
+ end
33
+
34
+ def update
35
+ Motor::Alerts::Persistance.update_from_params!(@alert, alert_params)
36
+ Motor::Alerts::ScheduledAlertsCache.clear
37
+ Motor::Configs::WriteToFile.call
38
+
39
+ render json: { data: Motor::ApiQuery::BuildJson.call(@alert, params, current_ability) }
40
+ rescue Motor::Alerts::Persistance::NameAlreadyExists
41
+ name_already_exists_response
42
+ rescue Motor::Alerts::Persistance::InvalidInterval
43
+ invalid_interval_response
44
+ end
45
+
46
+ def destroy
47
+ @alert.update!(deleted_at: Time.current)
48
+
49
+ Motor::Configs::WriteToFile.call
50
+
51
+ head :ok
52
+ end
53
+
54
+ private
55
+
56
+ def name_already_exists_response
57
+ render json: { errors: [{ source: 'name', detail: 'Name already exists' }] },
58
+ status: :unprocessable_entity
59
+ end
60
+
61
+ def invalid_interval_response
62
+ render json: { errors: [{ source: 'preferences.interval', detail: 'Invalid interval' }] },
63
+ status: :unprocessable_entity
64
+ end
65
+
66
+ def build_alert
67
+ @alert = Motor::Alerts::Persistance.build_from_params(alert_params)
68
+ end
69
+
70
+ def alert_params
71
+ params.require(:data).permit(
72
+ :query_id,
73
+ :name,
74
+ :description,
75
+ :to_emails,
76
+ :is_enabled,
77
+ preferences: {},
78
+ tags: []
79
+ )
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Motor
4
+ class ApiBaseController < ActionController::API
5
+ include Motor::CurrentUserMethod
6
+ include Motor::CurrentAbility
7
+
8
+ if defined?(ActionText::Content)
9
+ before_action do
10
+ ActionText::Content.renderer = Motor::ApplicationController.renderer.new(request.env)
11
+ end
12
+ end
13
+
14
+ unless Rails.env.test?
15
+ rescue_from StandardError do |e|
16
+ Rails.logger.error(e)
17
+ Rails.logger.error(e.backtrace.join("\n"))
18
+
19
+ render json: { errors: [e.message] }, status: :internal_server_error
20
+ end
21
+
22
+ rescue_from CanCan::AccessDenied do |e|
23
+ Rails.logger.error(e)
24
+
25
+ if params[:action].in?(%w[create update destroy])
26
+ render json: { errors: [I18n.t('motor.not_authorized_to_perform_action')] }, status: :forbidden
27
+ else
28
+ render json: { errors: [e.message] }, status: :forbidden
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Motor
4
+ class ApiConfigsController < ApiBaseController
5
+ wrap_parameters :data, except: %i[include fields]
6
+
7
+ before_action :find_or_initialize_api_config, only: :create
8
+ load_and_authorize_resource
9
+
10
+ def index
11
+ render json: { data: Motor::ApiQuery::BuildJson.call(@api_configs.active.order(:id), params, current_ability) }
12
+ end
13
+
14
+ def create
15
+ @api_config.save!
16
+
17
+ Motor::Configs::WriteToFile.call
18
+
19
+ render json: { data: Motor::ApiQuery::BuildJson.call(@api_config, params, current_ability) }
20
+ rescue ActiveRecord::RecordNotUnique
21
+ find_or_initialize_api_config
22
+
23
+ retry
24
+ end
25
+
26
+ def destroy
27
+ @api_config&.update!(deleted_at: Time.current)
28
+
29
+ Motor::Configs::WriteToFile.call
30
+
31
+ head :ok
32
+ end
33
+
34
+ private
35
+
36
+ def find_or_initialize_api_config
37
+ name, url, description, preferences, credentials =
38
+ api_config_params.values_at(:name, :url, :description, :preferences, :credentials)
39
+
40
+ @api_config =
41
+ Motor::ApiConfig.find_or_initialize_by(name: name).tap do |config|
42
+ config.url = url.delete_suffix('/')
43
+ config.description = description
44
+ config.preferences.merge!(preferences)
45
+ config.credentials.merge!(credentials)
46
+ config.deleted_at = nil
47
+ end
48
+ end
49
+
50
+ def api_config_params
51
+ params.require(:data).permit(:name, :url, :description, preferences: {}, credentials: {})
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Motor
4
+ class ApplicationController < ActionController::Base
5
+ include Motor::CurrentUserMethod
6
+ include Motor::CurrentAbility
7
+ end
8
+ end