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
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