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.
- checksums.yaml +7 -0
- data/LICENSE +661 -0
- data/README.md +230 -0
- data/Rakefile +11 -0
- data/app/channels/motor/application_cable/channel.rb +14 -0
- data/app/channels/motor/application_cable/connection.rb +27 -0
- data/app/channels/motor/notes_channel.rb +9 -0
- data/app/channels/motor/notifications_channel.rb +9 -0
- data/app/controllers/concerns/motor/current_ability.rb +21 -0
- data/app/controllers/concerns/motor/current_user_method.rb +18 -0
- data/app/controllers/concerns/motor/load_and_authorize_dynamic_resource.rb +73 -0
- data/app/controllers/concerns/motor/wrap_io_params.rb +25 -0
- data/app/controllers/motor/active_storage_attachments_controller.rb +64 -0
- data/app/controllers/motor/alerts_controller.rb +82 -0
- data/app/controllers/motor/api_base_controller.rb +33 -0
- data/app/controllers/motor/api_configs_controller.rb +54 -0
- data/app/controllers/motor/application_controller.rb +8 -0
- data/app/controllers/motor/assets_controller.rb +43 -0
- data/app/controllers/motor/audits_controller.rb +16 -0
- data/app/controllers/motor/auth_tokens_controller.rb +36 -0
- data/app/controllers/motor/configs_controller.rb +33 -0
- data/app/controllers/motor/dashboards_controller.rb +64 -0
- data/app/controllers/motor/data_controller.rb +88 -0
- data/app/controllers/motor/forms_controller.rb +61 -0
- data/app/controllers/motor/icons_controller.rb +22 -0
- data/app/controllers/motor/note_tags_controller.rb +13 -0
- data/app/controllers/motor/notes_controller.rb +58 -0
- data/app/controllers/motor/notifications_controller.rb +33 -0
- data/app/controllers/motor/queries_controller.rb +64 -0
- data/app/controllers/motor/reminders_controller.rb +38 -0
- data/app/controllers/motor/resource_default_queries_controller.rb +23 -0
- data/app/controllers/motor/resource_methods_controller.rb +23 -0
- data/app/controllers/motor/resources_controller.rb +26 -0
- data/app/controllers/motor/run_api_requests_controller.rb +56 -0
- data/app/controllers/motor/run_graphql_requests_controller.rb +48 -0
- data/app/controllers/motor/run_queries_controller.rb +77 -0
- data/app/controllers/motor/schema_controller.rb +31 -0
- data/app/controllers/motor/send_alerts_controller.rb +26 -0
- data/app/controllers/motor/sessions_controller.rb +23 -0
- data/app/controllers/motor/slack_conversations_controller.rb +11 -0
- data/app/controllers/motor/tags_controller.rb +11 -0
- data/app/controllers/motor/ui_controller.rb +51 -0
- data/app/controllers/motor/users_for_autocomplete_controller.rb +23 -0
- data/app/jobs/motor/alert_sending_job.rb +13 -0
- data/app/jobs/motor/application_job.rb +6 -0
- data/app/jobs/motor/notify_note_mentions_job.rb +9 -0
- data/app/jobs/motor/notify_reminder_job.rb +9 -0
- data/app/mailers/motor/alerts_mailer.rb +39 -0
- data/app/mailers/motor/application_mailer.rb +33 -0
- data/app/mailers/motor/notifications_mailer.rb +33 -0
- data/app/models/motor/alert.rb +30 -0
- data/app/models/motor/alert_lock.rb +7 -0
- data/app/models/motor/api_config.rb +28 -0
- data/app/models/motor/application_record.rb +18 -0
- data/app/models/motor/audit.rb +13 -0
- data/app/models/motor/config.rb +13 -0
- data/app/models/motor/dashboard.rb +26 -0
- data/app/models/motor/form.rb +23 -0
- data/app/models/motor/note.rb +18 -0
- data/app/models/motor/note_tag.rb +7 -0
- data/app/models/motor/note_tag_tag.rb +8 -0
- data/app/models/motor/notification.rb +14 -0
- data/app/models/motor/query.rb +33 -0
- data/app/models/motor/reminder.rb +13 -0
- data/app/models/motor/resource.rb +15 -0
- data/app/models/motor/tag.rb +7 -0
- data/app/models/motor/taggable_tag.rb +8 -0
- data/app/views/layouts/motor/application.html.erb +17 -0
- data/app/views/layouts/motor/mailer.html.erb +72 -0
- data/app/views/motor/alerts_mailer/alert_email.html.erb +54 -0
- data/app/views/motor/notifications_mailer/notify_mention_email.html.erb +28 -0
- data/app/views/motor/notifications_mailer/notify_reminder_email.html.erb +28 -0
- data/app/views/motor/ui/show.html.erb +1 -0
- data/config/locales/el.yml +420 -0
- data/config/locales/en.yml +340 -0
- data/config/locales/es.yml +420 -0
- data/config/locales/ja.yml +340 -0
- data/config/locales/pt.yml +416 -0
- data/config/routes.rb +65 -0
- data/lib/generators/motor/install_generator.rb +24 -0
- data/lib/generators/motor/install_notes_generator.rb +22 -0
- data/lib/generators/motor/migration.rb +17 -0
- data/lib/generators/motor/templates/install.rb +271 -0
- data/lib/generators/motor/templates/install_api_configs.rb +86 -0
- data/lib/generators/motor/templates/install_notes.rb +83 -0
- data/lib/generators/motor/templates/upgrade_motor_api_actions.rb +71 -0
- data/lib/generators/motor/upgrade_generator.rb +43 -0
- data/lib/motor/active_record_utils/action_text_attribute_patch.rb +19 -0
- data/lib/motor/active_record_utils/active_record_connection_column_patch.rb +14 -0
- data/lib/motor/active_record_utils/active_record_filter.rb +405 -0
- data/lib/motor/active_record_utils/active_storage_blob_patch.rb +30 -0
- data/lib/motor/active_record_utils/active_storage_links_extension.rb +11 -0
- data/lib/motor/active_record_utils/defined_scopes_extension.rb +25 -0
- data/lib/motor/active_record_utils/fetch_methods.rb +24 -0
- data/lib/motor/active_record_utils/types.rb +64 -0
- data/lib/motor/active_record_utils.rb +45 -0
- data/lib/motor/admin.rb +141 -0
- data/lib/motor/alerts/persistance.rb +97 -0
- data/lib/motor/alerts/scheduled_alerts_cache.rb +29 -0
- data/lib/motor/alerts/scheduler.rb +30 -0
- data/lib/motor/alerts/slack_sender.rb +74 -0
- data/lib/motor/alerts.rb +52 -0
- data/lib/motor/api_configs.rb +41 -0
- data/lib/motor/api_query/apply_scope.rb +44 -0
- data/lib/motor/api_query/build_json.rb +171 -0
- data/lib/motor/api_query/build_meta.rb +20 -0
- data/lib/motor/api_query/filter.rb +125 -0
- data/lib/motor/api_query/paginate.rb +19 -0
- data/lib/motor/api_query/search.rb +60 -0
- data/lib/motor/api_query/sort.rb +64 -0
- data/lib/motor/api_query.rb +24 -0
- data/lib/motor/assets.rb +62 -0
- data/lib/motor/build_schema/active_storage_attachment_schema.rb +125 -0
- data/lib/motor/build_schema/adjust_devise_model_schema.rb +60 -0
- data/lib/motor/build_schema/apply_permissions.rb +64 -0
- data/lib/motor/build_schema/defaults.rb +66 -0
- data/lib/motor/build_schema/find_display_column.rb +65 -0
- data/lib/motor/build_schema/find_icon.rb +135 -0
- data/lib/motor/build_schema/find_searchable_columns.rb +33 -0
- data/lib/motor/build_schema/load_from_rails.rb +361 -0
- data/lib/motor/build_schema/merge_schema_configs.rb +157 -0
- data/lib/motor/build_schema/reorder_schema.rb +88 -0
- data/lib/motor/build_schema/utils.rb +31 -0
- data/lib/motor/build_schema.rb +125 -0
- data/lib/motor/cancan_utils/ability_patch.rb +31 -0
- data/lib/motor/cancan_utils/can_manage_all.rb +14 -0
- data/lib/motor/cancan_utils.rb +9 -0
- data/lib/motor/configs/build_configs_hash.rb +90 -0
- data/lib/motor/configs/build_ui_app_tag.rb +177 -0
- data/lib/motor/configs/load_from_cache.rb +110 -0
- data/lib/motor/configs/sync_from_file.rb +35 -0
- data/lib/motor/configs/sync_from_hash.rb +159 -0
- data/lib/motor/configs/sync_middleware.rb +72 -0
- data/lib/motor/configs/sync_with_remote.rb +47 -0
- data/lib/motor/configs/write_to_file.rb +36 -0
- data/lib/motor/configs.rb +39 -0
- data/lib/motor/dashboards/persistance.rb +73 -0
- data/lib/motor/dashboards.rb +8 -0
- data/lib/motor/forms/persistance.rb +93 -0
- data/lib/motor/forms.rb +8 -0
- data/lib/motor/hash_serializer.rb +21 -0
- data/lib/motor/net_http_utils.rb +50 -0
- data/lib/motor/notes/notify_mentions.rb +71 -0
- data/lib/motor/notes/notify_reminder.rb +48 -0
- data/lib/motor/notes/persist.rb +36 -0
- data/lib/motor/notes/reminders_scheduler.rb +39 -0
- data/lib/motor/notes/tags.rb +34 -0
- data/lib/motor/notes.rb +12 -0
- data/lib/motor/queries/persistance.rb +90 -0
- data/lib/motor/queries/postgresql_exec_query.rb +28 -0
- data/lib/motor/queries/render_sql_template.rb +61 -0
- data/lib/motor/queries/run_query.rb +289 -0
- data/lib/motor/queries.rb +11 -0
- data/lib/motor/railtie.rb +11 -0
- data/lib/motor/resources/custom_sql_columns_cache.rb +17 -0
- data/lib/motor/resources/fetch_configured_model.rb +269 -0
- data/lib/motor/resources/persist_configs.rb +232 -0
- data/lib/motor/resources.rb +19 -0
- data/lib/motor/slack/client.rb +62 -0
- data/lib/motor/slack.rb +16 -0
- data/lib/motor/tags.rb +32 -0
- data/lib/motor/tasks/motor.rake +54 -0
- data/lib/motor/version.rb +5 -0
- data/lib/motor-admin-cstham8.rb +3 -0
- data/lib/motor.rb +87 -0
- data/ui/dist/manifest.json +1990 -0
- metadata +303 -0
data/README.md
ADDED
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
[](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
|
+
[](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
|
+

|
|
100
|
+
|
|
101
|
+

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

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

|
|
130
|
+
|
|
131
|
+
### Forms Builder
|
|
132
|
+
|
|
133
|
+

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

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

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

|
|
154
|
+
|
|
155
|
+
SQL queries can be organized into dashboards to create a convenient representation of the data.
|
|
156
|
+
|
|
157
|
+
### Email Alerts
|
|
158
|
+
|
|
159
|
+

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

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

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