motor-admin 0.1.35 → 0.1.41
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 +4 -4
 - data/app/controllers/concerns/motor/load_and_authorize_dynamic_resource.rb +70 -0
 - data/app/controllers/motor/alerts_controller.rb +12 -6
 - data/app/controllers/motor/audits_controller.rb +16 -0
 - data/app/controllers/motor/configs_controller.rb +1 -0
 - data/app/controllers/motor/dashboards_controller.rb +4 -0
 - data/app/controllers/motor/data_controller.rb +2 -57
 - data/app/controllers/motor/forms_controller.rb +4 -0
 - data/app/controllers/motor/queries_controller.rb +4 -0
 - data/app/controllers/motor/resources_controller.rb +1 -0
 - data/app/controllers/motor/ui_controller.rb +4 -0
 - data/app/models/motor/alert.rb +4 -2
 - data/app/models/motor/application_record.rb +10 -0
 - data/app/models/motor/audit.rb +9 -0
 - data/app/models/motor/config.rb +2 -0
 - data/app/models/motor/dashboard.rb +3 -1
 - data/app/models/motor/form.rb +3 -1
 - data/app/models/motor/query.rb +4 -1
 - data/app/models/motor/resource.rb +2 -0
 - data/app/views/motor/ui/show.html.erb +1 -1
 - data/config/routes.rb +1 -0
 - data/lib/generators/motor/templates/install.rb +40 -16
 - data/lib/motor.rb +12 -3
 - data/lib/motor/active_record_utils/defined_scopes_extension.rb +1 -1
 - data/lib/motor/admin.rb +8 -0
 - data/lib/motor/alerts/persistance.rb +17 -3
 - data/lib/motor/alerts/scheduler.rb +1 -1
 - data/lib/motor/api_query/apply_scope.rb +12 -5
 - data/lib/motor/api_query/sort.rb +1 -1
 - data/lib/motor/build_schema.rb +3 -3
 - data/lib/motor/build_schema/find_display_column.rb +4 -4
 - data/lib/motor/build_schema/load_from_rails.rb +20 -10
 - data/lib/motor/build_schema/merge_schema_configs.rb +8 -4
 - data/lib/motor/build_schema/reorder_schema.rb +10 -4
 - data/lib/motor/configs.rb +17 -0
 - data/lib/motor/configs/build_configs_hash.rb +83 -0
 - data/lib/motor/configs/build_ui_app_tag.rb +71 -0
 - data/lib/motor/configs/load_from_cache.rb +81 -0
 - data/lib/motor/configs/sync_from_file.rb +36 -0
 - data/lib/motor/configs/sync_from_hash.rb +126 -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/dashboards/persistance.rb +15 -5
 - data/lib/motor/forms/persistance.rb +15 -5
 - data/lib/motor/net_http_utils.rb +38 -0
 - data/lib/motor/queries/persistance.rb +13 -3
 - data/lib/motor/railtie.rb +11 -0
 - data/lib/motor/tasks/motor.rake +37 -0
 - data/lib/motor/version.rb +1 -1
 - data/ui/dist/{main-03c3b1d3390877206e02.css.gz → main-0ef3be65da8d3b0dbabb.css.gz} +0 -0
 - data/ui/dist/main-0ef3be65da8d3b0dbabb.js.gz +0 -0
 - data/ui/dist/manifest.json +5 -5
 - metadata +33 -5
 - data/lib/motor/ui_configs.rb +0 -82
 - data/ui/dist/main-03c3b1d3390877206e02.js.gz +0 -0
 
| 
         @@ -0,0 +1,17 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Motor
         
     | 
| 
      
 4 
     | 
    
         
            +
              module Configs
         
     | 
| 
      
 5 
     | 
    
         
            +
                FILE_PATH = 'config/motor.yml'
         
     | 
| 
      
 6 
     | 
    
         
            +
                SYNC_API_PATH = '/motor_configs_sync'
         
     | 
| 
      
 7 
     | 
    
         
            +
                SYNC_ACCESS_KEY = ENV.fetch('MOTOR_SYNC_API_KEY', '')
         
     | 
| 
      
 8 
     | 
    
         
            +
              end
         
     | 
| 
      
 9 
     | 
    
         
            +
            end
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
            require_relative './configs/load_from_cache'
         
     | 
| 
      
 12 
     | 
    
         
            +
            require_relative './configs/build_ui_app_tag'
         
     | 
| 
      
 13 
     | 
    
         
            +
            require_relative './configs/build_configs_hash'
         
     | 
| 
      
 14 
     | 
    
         
            +
            require_relative './configs/write_to_file'
         
     | 
| 
      
 15 
     | 
    
         
            +
            require_relative './configs/sync_from_hash'
         
     | 
| 
      
 16 
     | 
    
         
            +
            require_relative './configs/sync_from_file'
         
     | 
| 
      
 17 
     | 
    
         
            +
            require_relative './configs/sync_with_remote'
         
     | 
| 
         @@ -0,0 +1,83 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Motor
         
     | 
| 
      
 4 
     | 
    
         
            +
              module Configs
         
     | 
| 
      
 5 
     | 
    
         
            +
                module BuildConfigsHash
         
     | 
| 
      
 6 
     | 
    
         
            +
                  module_function
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                  def call
         
     | 
| 
      
 9 
     | 
    
         
            +
                    cache_keys = LoadFromCache.load_cache_keys
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
                    normalize_hash(
         
     | 
| 
      
 12 
     | 
    
         
            +
                      engine_version: Motor::VERSION,
         
     | 
| 
      
 13 
     | 
    
         
            +
                      file_version: cache_keys.values.compact.max.to_time,
         
     | 
| 
      
 14 
     | 
    
         
            +
                      resources: build_resources_hash(cache_keys[:resources]),
         
     | 
| 
      
 15 
     | 
    
         
            +
                      configs: build_configs_hash(cache_keys[:configs]),
         
     | 
| 
      
 16 
     | 
    
         
            +
                      queries: build_queries_hash(cache_keys[:queries]),
         
     | 
| 
      
 17 
     | 
    
         
            +
                      dashboards: build_dashboards_hash(cache_keys[:dashboards]),
         
     | 
| 
      
 18 
     | 
    
         
            +
                      forms: build_forms_hash(cache_keys[:forms]),
         
     | 
| 
      
 19 
     | 
    
         
            +
                      alerts: build_alerts_hash(cache_keys[:alerts])
         
     | 
| 
      
 20 
     | 
    
         
            +
                    )
         
     | 
| 
      
 21 
     | 
    
         
            +
                  end
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                  def build_queries_hash(cache_key = nil)
         
     | 
| 
      
 24 
     | 
    
         
            +
                    Motor::Configs::LoadFromCache.load_queries(cache_key: cache_key).sort_by(&:id).map do |query|
         
     | 
| 
      
 25 
     | 
    
         
            +
                      query.slice(%i[id name sql_body description preferences])
         
     | 
| 
      
 26 
     | 
    
         
            +
                           .merge(tags: query.tags.map(&:name), updated_at: query.updated_at.to_time)
         
     | 
| 
      
 27 
     | 
    
         
            +
                    end
         
     | 
| 
      
 28 
     | 
    
         
            +
                  end
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
                  def build_dashboards_hash(cache_key = nil)
         
     | 
| 
      
 31 
     | 
    
         
            +
                    Motor::Configs::LoadFromCache.load_dashboards(cache_key: cache_key).sort_by(&:id).map do |dashboard|
         
     | 
| 
      
 32 
     | 
    
         
            +
                      dashboard.slice(%i[id title description preferences])
         
     | 
| 
      
 33 
     | 
    
         
            +
                               .merge(tags: dashboard.tags.map(&:name), updated_at: dashboard.updated_at.to_time)
         
     | 
| 
      
 34 
     | 
    
         
            +
                    end
         
     | 
| 
      
 35 
     | 
    
         
            +
                  end
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
                  def build_alerts_hash(cache_key = nil)
         
     | 
| 
      
 38 
     | 
    
         
            +
                    Motor::Configs::LoadFromCache.load_alerts(cache_key: cache_key).sort_by(&:id).map do |alert|
         
     | 
| 
      
 39 
     | 
    
         
            +
                      alert.slice(%i[id name query_id to_emails is_enabled description preferences])
         
     | 
| 
      
 40 
     | 
    
         
            +
                           .merge(tags: alert.tags.map(&:name), updated_at: alert.updated_at.to_time)
         
     | 
| 
      
 41 
     | 
    
         
            +
                    end
         
     | 
| 
      
 42 
     | 
    
         
            +
                  end
         
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
                  def build_forms_hash(cache_key = nil)
         
     | 
| 
      
 45 
     | 
    
         
            +
                    Motor::Configs::LoadFromCache.load_forms(cache_key: cache_key).sort_by(&:id).map do |form|
         
     | 
| 
      
 46 
     | 
    
         
            +
                      form.slice(%i[id name http_method api_path description preferences])
         
     | 
| 
      
 47 
     | 
    
         
            +
                          .merge(tags: form.tags.map(&:name), updated_at: form.updated_at.to_time)
         
     | 
| 
      
 48 
     | 
    
         
            +
                    end
         
     | 
| 
      
 49 
     | 
    
         
            +
                  end
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
      
 51 
     | 
    
         
            +
                  def build_configs_hash(cache_key = nil)
         
     | 
| 
      
 52 
     | 
    
         
            +
                    Motor::Configs::LoadFromCache.load_configs(cache_key: cache_key).sort_by(&:key).map do |config|
         
     | 
| 
      
 53 
     | 
    
         
            +
                      {
         
     | 
| 
      
 54 
     | 
    
         
            +
                        key: config.key,
         
     | 
| 
      
 55 
     | 
    
         
            +
                        value: config.value,
         
     | 
| 
      
 56 
     | 
    
         
            +
                        updated_at: config.updated_at.to_time
         
     | 
| 
      
 57 
     | 
    
         
            +
                      }
         
     | 
| 
      
 58 
     | 
    
         
            +
                    end
         
     | 
| 
      
 59 
     | 
    
         
            +
                  end
         
     | 
| 
      
 60 
     | 
    
         
            +
             
     | 
| 
      
 61 
     | 
    
         
            +
                  def build_resources_hash(cache_key = nil)
         
     | 
| 
      
 62 
     | 
    
         
            +
                    Motor::Configs::LoadFromCache.load_resources(cache_key: cache_key).sort_by(&:name).map do |resource|
         
     | 
| 
      
 63 
     | 
    
         
            +
                      {
         
     | 
| 
      
 64 
     | 
    
         
            +
                        name: resource.name,
         
     | 
| 
      
 65 
     | 
    
         
            +
                        preferences: resource.preferences,
         
     | 
| 
      
 66 
     | 
    
         
            +
                        updated_at: resource.updated_at.to_time
         
     | 
| 
      
 67 
     | 
    
         
            +
                      }
         
     | 
| 
      
 68 
     | 
    
         
            +
                    end
         
     | 
| 
      
 69 
     | 
    
         
            +
                  end
         
     | 
| 
      
 70 
     | 
    
         
            +
             
     | 
| 
      
 71 
     | 
    
         
            +
                  def normalize_hash(value)
         
     | 
| 
      
 72 
     | 
    
         
            +
                    case value
         
     | 
| 
      
 73 
     | 
    
         
            +
                    when Hash, HashWithIndifferentAccess
         
     | 
| 
      
 74 
     | 
    
         
            +
                      value.to_h.stringify_keys.transform_values { |v| normalize_hash(v) }
         
     | 
| 
      
 75 
     | 
    
         
            +
                    when Array
         
     | 
| 
      
 76 
     | 
    
         
            +
                      value.map { |e| normalize_hash(e) }
         
     | 
| 
      
 77 
     | 
    
         
            +
                    else
         
     | 
| 
      
 78 
     | 
    
         
            +
                      value
         
     | 
| 
      
 79 
     | 
    
         
            +
                    end
         
     | 
| 
      
 80 
     | 
    
         
            +
                  end
         
     | 
| 
      
 81 
     | 
    
         
            +
                end
         
     | 
| 
      
 82 
     | 
    
         
            +
              end
         
     | 
| 
      
 83 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,71 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Motor
         
     | 
| 
      
 4 
     | 
    
         
            +
              module Configs
         
     | 
| 
      
 5 
     | 
    
         
            +
                module BuildUiAppTag
         
     | 
| 
      
 6 
     | 
    
         
            +
                  CACHE_STORE =
         
     | 
| 
      
 7 
     | 
    
         
            +
                    if Motor.development?
         
     | 
| 
      
 8 
     | 
    
         
            +
                      ActiveSupport::Cache::NullStore.new
         
     | 
| 
      
 9 
     | 
    
         
            +
                    else
         
     | 
| 
      
 10 
     | 
    
         
            +
                      ActiveSupport::Cache::MemoryStore.new(size: 5.megabytes)
         
     | 
| 
      
 11 
     | 
    
         
            +
                    end
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                  module_function
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                  def call
         
     | 
| 
      
 16 
     | 
    
         
            +
                    cache_keys = LoadFromCache.load_cache_keys
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                    CACHE_STORE.fetch(cache_keys.hash) do
         
     | 
| 
      
 19 
     | 
    
         
            +
                      CACHE_STORE.clear
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                      Motor::ApplicationController.helpers.content_tag(
         
     | 
| 
      
 22 
     | 
    
         
            +
                        :div, '', id: 'app', data: build_data(cache_keys)
         
     | 
| 
      
 23 
     | 
    
         
            +
                      )
         
     | 
| 
      
 24 
     | 
    
         
            +
                    end
         
     | 
| 
      
 25 
     | 
    
         
            +
                  end
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
                  # @return [Hash]
         
     | 
| 
      
 28 
     | 
    
         
            +
                  def build_data(cache_keys = {})
         
     | 
| 
      
 29 
     | 
    
         
            +
                    {
         
     | 
| 
      
 30 
     | 
    
         
            +
                      base_path: Motor::Admin.routes.url_helpers.motor_path,
         
     | 
| 
      
 31 
     | 
    
         
            +
                      schema: Motor::BuildSchema.call(cache_keys),
         
     | 
| 
      
 32 
     | 
    
         
            +
                      header_links: header_links_data_hash(cache_keys[:configs]),
         
     | 
| 
      
 33 
     | 
    
         
            +
                      queries: queries_data_hash(cache_keys[:queries]),
         
     | 
| 
      
 34 
     | 
    
         
            +
                      dashboards: dashboards_data_hash(cache_keys[:dashboards]),
         
     | 
| 
      
 35 
     | 
    
         
            +
                      alerts: alerts_data_hash(cache_keys[:alerts]),
         
     | 
| 
      
 36 
     | 
    
         
            +
                      forms: forms_data_hash(cache_keys[:forms])
         
     | 
| 
      
 37 
     | 
    
         
            +
                    }
         
     | 
| 
      
 38 
     | 
    
         
            +
                  end
         
     | 
| 
      
 39 
     | 
    
         
            +
             
     | 
| 
      
 40 
     | 
    
         
            +
                  def header_links_data_hash(cache_key = nil)
         
     | 
| 
      
 41 
     | 
    
         
            +
                    configs = Motor::Configs::LoadFromCache.load_configs(cache_key: cache_key)
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
                    configs.find { |c| c.key == 'header.links' }&.value || []
         
     | 
| 
      
 44 
     | 
    
         
            +
                  end
         
     | 
| 
      
 45 
     | 
    
         
            +
             
     | 
| 
      
 46 
     | 
    
         
            +
                  def queries_data_hash(cache_key = nil)
         
     | 
| 
      
 47 
     | 
    
         
            +
                    Motor::Configs::LoadFromCache.load_queries(cache_key: cache_key)
         
     | 
| 
      
 48 
     | 
    
         
            +
                                                 .as_json(only: %i[id name updated_at],
         
     | 
| 
      
 49 
     | 
    
         
            +
                                                          include: { tags: { only: %i[id name] } })
         
     | 
| 
      
 50 
     | 
    
         
            +
                  end
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
                  def dashboards_data_hash(cache_key = nil)
         
     | 
| 
      
 53 
     | 
    
         
            +
                    Motor::Configs::LoadFromCache.load_dashboards(cache_key: cache_key)
         
     | 
| 
      
 54 
     | 
    
         
            +
                                                 .as_json(only: %i[id title updated_at],
         
     | 
| 
      
 55 
     | 
    
         
            +
                                                          include: { tags: { only: %i[id name] } })
         
     | 
| 
      
 56 
     | 
    
         
            +
                  end
         
     | 
| 
      
 57 
     | 
    
         
            +
             
     | 
| 
      
 58 
     | 
    
         
            +
                  def alerts_data_hash(cache_key = nil)
         
     | 
| 
      
 59 
     | 
    
         
            +
                    Motor::Configs::LoadFromCache.load_alerts(cache_key: cache_key)
         
     | 
| 
      
 60 
     | 
    
         
            +
                                                 .as_json(only: %i[id name is_enabled updated_at],
         
     | 
| 
      
 61 
     | 
    
         
            +
                                                          include: { tags: { only: %i[id name] } })
         
     | 
| 
      
 62 
     | 
    
         
            +
                  end
         
     | 
| 
      
 63 
     | 
    
         
            +
             
     | 
| 
      
 64 
     | 
    
         
            +
                  def forms_data_hash(cache_key = nil)
         
     | 
| 
      
 65 
     | 
    
         
            +
                    Motor::Configs::LoadFromCache.load_forms(cache_key: cache_key)
         
     | 
| 
      
 66 
     | 
    
         
            +
                                                 .as_json(only: %i[id name updated_at],
         
     | 
| 
      
 67 
     | 
    
         
            +
                                                          include: { tags: { only: %i[id name] } })
         
     | 
| 
      
 68 
     | 
    
         
            +
                  end
         
     | 
| 
      
 69 
     | 
    
         
            +
                end
         
     | 
| 
      
 70 
     | 
    
         
            +
              end
         
     | 
| 
      
 71 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,81 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Motor
         
     | 
| 
      
 4 
     | 
    
         
            +
              module Configs
         
     | 
| 
      
 5 
     | 
    
         
            +
                module LoadFromCache
         
     | 
| 
      
 6 
     | 
    
         
            +
                  CACHE_STORE = ActiveSupport::Cache::MemoryStore.new(size: 10.megabytes)
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                  module_function
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
                  def call
         
     | 
| 
      
 11 
     | 
    
         
            +
                    cache_keys = load_cache_keys
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                    {
         
     | 
| 
      
 14 
     | 
    
         
            +
                      configs: load_configs(cache_key: cache_keys[:configs]),
         
     | 
| 
      
 15 
     | 
    
         
            +
                      resources: load_resources(cache_key: cache_keys[:resources]),
         
     | 
| 
      
 16 
     | 
    
         
            +
                      queries: load_queries(cache_key: cache_keys[:queries]),
         
     | 
| 
      
 17 
     | 
    
         
            +
                      dashboards: load_dashboards(cache_key: cache_keys[:dashboards]),
         
     | 
| 
      
 18 
     | 
    
         
            +
                      alerts: load_alerts(cache_key: cache_keys[:alerts]),
         
     | 
| 
      
 19 
     | 
    
         
            +
                      forms: load_forms(cache_key: cache_keys[:forms])
         
     | 
| 
      
 20 
     | 
    
         
            +
                    }
         
     | 
| 
      
 21 
     | 
    
         
            +
                  end
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                  def load_configs(cache_key: nil)
         
     | 
| 
      
 24 
     | 
    
         
            +
                    maybe_fetch_from_cache('configs', cache_key) do
         
     | 
| 
      
 25 
     | 
    
         
            +
                      Motor::Config.all.load
         
     | 
| 
      
 26 
     | 
    
         
            +
                    end
         
     | 
| 
      
 27 
     | 
    
         
            +
                  end
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
                  def load_resources(cache_key: nil)
         
     | 
| 
      
 30 
     | 
    
         
            +
                    maybe_fetch_from_cache('resources', cache_key) do
         
     | 
| 
      
 31 
     | 
    
         
            +
                      Motor::Resource.all.load
         
     | 
| 
      
 32 
     | 
    
         
            +
                    end
         
     | 
| 
      
 33 
     | 
    
         
            +
                  end
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
                  def load_queries(cache_key: nil)
         
     | 
| 
      
 36 
     | 
    
         
            +
                    maybe_fetch_from_cache('queries', cache_key) do
         
     | 
| 
      
 37 
     | 
    
         
            +
                      Motor::Query.all.active.preload(:tags).load
         
     | 
| 
      
 38 
     | 
    
         
            +
                    end
         
     | 
| 
      
 39 
     | 
    
         
            +
                  end
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
                  def load_dashboards(cache_key: nil)
         
     | 
| 
      
 42 
     | 
    
         
            +
                    maybe_fetch_from_cache('dashboards', cache_key) do
         
     | 
| 
      
 43 
     | 
    
         
            +
                      Motor::Dashboard.all.active.preload(:tags).load
         
     | 
| 
      
 44 
     | 
    
         
            +
                    end
         
     | 
| 
      
 45 
     | 
    
         
            +
                  end
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
      
 47 
     | 
    
         
            +
                  def load_alerts(cache_key: nil)
         
     | 
| 
      
 48 
     | 
    
         
            +
                    maybe_fetch_from_cache('alerts', cache_key) do
         
     | 
| 
      
 49 
     | 
    
         
            +
                      Motor::Alert.all.active.preload(:tags).load
         
     | 
| 
      
 50 
     | 
    
         
            +
                    end
         
     | 
| 
      
 51 
     | 
    
         
            +
                  end
         
     | 
| 
      
 52 
     | 
    
         
            +
             
     | 
| 
      
 53 
     | 
    
         
            +
                  def load_forms(cache_key: nil)
         
     | 
| 
      
 54 
     | 
    
         
            +
                    maybe_fetch_from_cache('forms', cache_key) do
         
     | 
| 
      
 55 
     | 
    
         
            +
                      Motor::Form.all.active.preload(:tags).load
         
     | 
| 
      
 56 
     | 
    
         
            +
                    end
         
     | 
| 
      
 57 
     | 
    
         
            +
                  end
         
     | 
| 
      
 58 
     | 
    
         
            +
             
     | 
| 
      
 59 
     | 
    
         
            +
                  def maybe_fetch_from_cache(type, cache_key, &block)
         
     | 
| 
      
 60 
     | 
    
         
            +
                    return block.call unless cache_key
         
     | 
| 
      
 61 
     | 
    
         
            +
             
     | 
| 
      
 62 
     | 
    
         
            +
                    CACHE_STORE.fetch(type + cache_key.to_s, &block)
         
     | 
| 
      
 63 
     | 
    
         
            +
                  end
         
     | 
| 
      
 64 
     | 
    
         
            +
             
     | 
| 
      
 65 
     | 
    
         
            +
                  def load_cache_keys
         
     | 
| 
      
 66 
     | 
    
         
            +
                    ActiveRecord::Base.connection.execute(
         
     | 
| 
      
 67 
     | 
    
         
            +
                      "(#{
         
     | 
| 
      
 68 
     | 
    
         
            +
                        [
         
     | 
| 
      
 69 
     | 
    
         
            +
                          Motor::Config.select("'configs', MAX(updated_at)").to_sql,
         
     | 
| 
      
 70 
     | 
    
         
            +
                          Motor::Resource.select("'resources', MAX(updated_at)").to_sql,
         
     | 
| 
      
 71 
     | 
    
         
            +
                          Motor::Dashboard.select("'dashboards', MAX(updated_at)").to_sql,
         
     | 
| 
      
 72 
     | 
    
         
            +
                          Motor::Alert.select("'alerts', MAX(updated_at)").to_sql,
         
     | 
| 
      
 73 
     | 
    
         
            +
                          Motor::Query.select("'queries', MAX(updated_at)").to_sql,
         
     | 
| 
      
 74 
     | 
    
         
            +
                          Motor::Form.select("'forms', MAX(updated_at)").to_sql
         
     | 
| 
      
 75 
     | 
    
         
            +
                        ].join(') UNION (')
         
     | 
| 
      
 76 
     | 
    
         
            +
                      })"
         
     | 
| 
      
 77 
     | 
    
         
            +
                    ).to_a.map(&:values).to_h.with_indifferent_access
         
     | 
| 
      
 78 
     | 
    
         
            +
                  end
         
     | 
| 
      
 79 
     | 
    
         
            +
                end
         
     | 
| 
      
 80 
     | 
    
         
            +
              end
         
     | 
| 
      
 81 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,36 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Motor
         
     | 
| 
      
 4 
     | 
    
         
            +
              module Configs
         
     | 
| 
      
 5 
     | 
    
         
            +
                module SyncFromFile
         
     | 
| 
      
 6 
     | 
    
         
            +
                  FILE_PATH = Motor::Configs::FILE_PATH
         
     | 
| 
      
 7 
     | 
    
         
            +
                  MUTEXT = Mutex.new
         
     | 
| 
      
 8 
     | 
    
         
            +
                  FILE_TIMESTAMPS_STORE = ActiveSupport::Cache::MemoryStore.new(size: 1.megabyte)
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
                  module_function
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                  def call(with_exception: false)
         
     | 
| 
      
 13 
     | 
    
         
            +
                    MUTEXT.synchronize do
         
     | 
| 
      
 14 
     | 
    
         
            +
                      file = Rails.root.join(FILE_PATH)
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
                      file_timestamp =
         
     | 
| 
      
 17 
     | 
    
         
            +
                        begin
         
     | 
| 
      
 18 
     | 
    
         
            +
                          file.ctime
         
     | 
| 
      
 19 
     | 
    
         
            +
                        rescue Errno::ENOENT
         
     | 
| 
      
 20 
     | 
    
         
            +
                          raise if with_exception
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
                          nil
         
     | 
| 
      
 23 
     | 
    
         
            +
                        end
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
                      next unless file_timestamp
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
                      FILE_TIMESTAMPS_STORE.fetch(file_timestamp.to_s) do
         
     | 
| 
      
 28 
     | 
    
         
            +
                        Motor::Configs::SyncFromHash.call(
         
     | 
| 
      
 29 
     | 
    
         
            +
                          YAML.safe_load(file.read, permitted_classes: [Time, Date])
         
     | 
| 
      
 30 
     | 
    
         
            +
                        )
         
     | 
| 
      
 31 
     | 
    
         
            +
                      end
         
     | 
| 
      
 32 
     | 
    
         
            +
                    end
         
     | 
| 
      
 33 
     | 
    
         
            +
                  end
         
     | 
| 
      
 34 
     | 
    
         
            +
                end
         
     | 
| 
      
 35 
     | 
    
         
            +
              end
         
     | 
| 
      
 36 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,126 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Motor
         
     | 
| 
      
 4 
     | 
    
         
            +
              module Configs
         
     | 
| 
      
 5 
     | 
    
         
            +
                module SyncFromHash
         
     | 
| 
      
 6 
     | 
    
         
            +
                  module_function
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                  def call(configs_hash)
         
     | 
| 
      
 9 
     | 
    
         
            +
                    return if configs_hash.blank?
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
                    configs_hash = configs_hash.with_indifferent_access
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                    Motor::ApplicationRecord.transaction do
         
     | 
| 
      
 14 
     | 
    
         
            +
                      sync_queries(configs_hash)
         
     | 
| 
      
 15 
     | 
    
         
            +
                      sync_alerts(configs_hash)
         
     | 
| 
      
 16 
     | 
    
         
            +
                      sync_dashboards(configs_hash)
         
     | 
| 
      
 17 
     | 
    
         
            +
                      sync_forms(configs_hash)
         
     | 
| 
      
 18 
     | 
    
         
            +
                      sync_configs(configs_hash)
         
     | 
| 
      
 19 
     | 
    
         
            +
                      sync_resources(configs_hash)
         
     | 
| 
      
 20 
     | 
    
         
            +
                    end
         
     | 
| 
      
 21 
     | 
    
         
            +
                  end
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                  def sync_queries(configs_hash)
         
     | 
| 
      
 24 
     | 
    
         
            +
                    sync_taggable(
         
     | 
| 
      
 25 
     | 
    
         
            +
                      Motor::Configs::LoadFromCache.load_queries,
         
     | 
| 
      
 26 
     | 
    
         
            +
                      configs_hash[:queries],
         
     | 
| 
      
 27 
     | 
    
         
            +
                      configs_hash[:file_version],
         
     | 
| 
      
 28 
     | 
    
         
            +
                      Motor::Queries::Persistance.method(:update_from_params!)
         
     | 
| 
      
 29 
     | 
    
         
            +
                    )
         
     | 
| 
      
 30 
     | 
    
         
            +
                  end
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
                  def sync_alerts(configs_hash)
         
     | 
| 
      
 33 
     | 
    
         
            +
                    sync_taggable(
         
     | 
| 
      
 34 
     | 
    
         
            +
                      Motor::Configs::LoadFromCache.load_alerts,
         
     | 
| 
      
 35 
     | 
    
         
            +
                      configs_hash[:alerts],
         
     | 
| 
      
 36 
     | 
    
         
            +
                      configs_hash[:file_version],
         
     | 
| 
      
 37 
     | 
    
         
            +
                      Motor::Alerts::Persistance.method(:update_from_params!)
         
     | 
| 
      
 38 
     | 
    
         
            +
                    )
         
     | 
| 
      
 39 
     | 
    
         
            +
                  end
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
                  def sync_forms(configs_hash)
         
     | 
| 
      
 42 
     | 
    
         
            +
                    sync_taggable(
         
     | 
| 
      
 43 
     | 
    
         
            +
                      Motor::Configs::LoadFromCache.load_forms,
         
     | 
| 
      
 44 
     | 
    
         
            +
                      configs_hash[:forms],
         
     | 
| 
      
 45 
     | 
    
         
            +
                      configs_hash[:file_version],
         
     | 
| 
      
 46 
     | 
    
         
            +
                      Motor::Forms::Persistance.method(:update_from_params!)
         
     | 
| 
      
 47 
     | 
    
         
            +
                    )
         
     | 
| 
      
 48 
     | 
    
         
            +
                  end
         
     | 
| 
      
 49 
     | 
    
         
            +
             
     | 
| 
      
 50 
     | 
    
         
            +
                  def sync_dashboards(configs_hash)
         
     | 
| 
      
 51 
     | 
    
         
            +
                    sync_taggable(
         
     | 
| 
      
 52 
     | 
    
         
            +
                      Motor::Configs::LoadFromCache.load_dashboards,
         
     | 
| 
      
 53 
     | 
    
         
            +
                      configs_hash[:dashboards],
         
     | 
| 
      
 54 
     | 
    
         
            +
                      configs_hash[:file_version],
         
     | 
| 
      
 55 
     | 
    
         
            +
                      Motor::Dashboards::Persistance.method(:update_from_params!)
         
     | 
| 
      
 56 
     | 
    
         
            +
                    )
         
     | 
| 
      
 57 
     | 
    
         
            +
                  end
         
     | 
| 
      
 58 
     | 
    
         
            +
             
     | 
| 
      
 59 
     | 
    
         
            +
                  def sync_configs(configs_hash)
         
     | 
| 
      
 60 
     | 
    
         
            +
                    configs_index = Motor::Configs::LoadFromCache.load_configs.index_by(&:key)
         
     | 
| 
      
 61 
     | 
    
         
            +
             
     | 
| 
      
 62 
     | 
    
         
            +
                    configs_hash[:configs].each do |attrs|
         
     | 
| 
      
 63 
     | 
    
         
            +
                      record = configs_index[attrs[:key]] || Motor::Config.new
         
     | 
| 
      
 64 
     | 
    
         
            +
             
     | 
| 
      
 65 
     | 
    
         
            +
                      next if record.updated_at && attrs[:updated_at] <= record.updated_at
         
     | 
| 
      
 66 
     | 
    
         
            +
             
     | 
| 
      
 67 
     | 
    
         
            +
                      record.update!(attrs)
         
     | 
| 
      
 68 
     | 
    
         
            +
                    end
         
     | 
| 
      
 69 
     | 
    
         
            +
                  end
         
     | 
| 
      
 70 
     | 
    
         
            +
             
     | 
| 
      
 71 
     | 
    
         
            +
                  def sync_resources(configs_hash)
         
     | 
| 
      
 72 
     | 
    
         
            +
                    resources_index = Motor::Configs::LoadFromCache.load_resources.index_by(&:name)
         
     | 
| 
      
 73 
     | 
    
         
            +
             
     | 
| 
      
 74 
     | 
    
         
            +
                    configs_hash[:resources].each do |attrs|
         
     | 
| 
      
 75 
     | 
    
         
            +
                      record = resources_index[attrs[:name]] || Motor::Resource.new
         
     | 
| 
      
 76 
     | 
    
         
            +
             
     | 
| 
      
 77 
     | 
    
         
            +
                      next if record.updated_at && attrs[:updated_at] <= record.updated_at
         
     | 
| 
      
 78 
     | 
    
         
            +
             
     | 
| 
      
 79 
     | 
    
         
            +
                      record.update!(attrs)
         
     | 
| 
      
 80 
     | 
    
         
            +
                    end
         
     | 
| 
      
 81 
     | 
    
         
            +
                  end
         
     | 
| 
      
 82 
     | 
    
         
            +
             
     | 
| 
      
 83 
     | 
    
         
            +
                  def sync_taggable(records, config_items, configs_timestamp, update_proc)
         
     | 
| 
      
 84 
     | 
    
         
            +
                    processed_records, create_items = update_taggable_items(records, config_items, update_proc)
         
     | 
| 
      
 85 
     | 
    
         
            +
             
     | 
| 
      
 86 
     | 
    
         
            +
                    create_taggable_items(create_items, records.klass, update_proc)
         
     | 
| 
      
 87 
     | 
    
         
            +
             
     | 
| 
      
 88 
     | 
    
         
            +
                    archive_taggable_items(records - processed_records, configs_timestamp)
         
     | 
| 
      
 89 
     | 
    
         
            +
             
     | 
| 
      
 90 
     | 
    
         
            +
                    ActiveRecord::Base.connection.reset_pk_sequence!(records.klass.table_name)
         
     | 
| 
      
 91 
     | 
    
         
            +
                  end
         
     | 
| 
      
 92 
     | 
    
         
            +
             
     | 
| 
      
 93 
     | 
    
         
            +
                  def update_taggable_items(records, config_items, update_proc)
         
     | 
| 
      
 94 
     | 
    
         
            +
                    record_ids_hash = records.index_by(&:id)
         
     | 
| 
      
 95 
     | 
    
         
            +
             
     | 
| 
      
 96 
     | 
    
         
            +
                    config_items.each_with_object([[], []]) do |attrs, (processed_acc, create_acc)|
         
     | 
| 
      
 97 
     | 
    
         
            +
                      record = record_ids_hash[attrs[:id]]
         
     | 
| 
      
 98 
     | 
    
         
            +
             
     | 
| 
      
 99 
     | 
    
         
            +
                      next create_acc << attrs unless record
         
     | 
| 
      
 100 
     | 
    
         
            +
             
     | 
| 
      
 101 
     | 
    
         
            +
                      processed_acc << record if record
         
     | 
| 
      
 102 
     | 
    
         
            +
             
     | 
| 
      
 103 
     | 
    
         
            +
                      next if record.updated_at >= attrs[:updated_at]
         
     | 
| 
      
 104 
     | 
    
         
            +
             
     | 
| 
      
 105 
     | 
    
         
            +
                      update_proc.call(record, attrs, force_replace: true)
         
     | 
| 
      
 106 
     | 
    
         
            +
                    end
         
     | 
| 
      
 107 
     | 
    
         
            +
                  end
         
     | 
| 
      
 108 
     | 
    
         
            +
             
     | 
| 
      
 109 
     | 
    
         
            +
                  def create_taggable_items(create_items, records_class, update_proc)
         
     | 
| 
      
 110 
     | 
    
         
            +
                    create_items.each do |attrs|
         
     | 
| 
      
 111 
     | 
    
         
            +
                      record = records_class.find_or_initialize_by(id: attrs[:id]).tap { |e| e.deleted_at = nil }
         
     | 
| 
      
 112 
     | 
    
         
            +
             
     | 
| 
      
 113 
     | 
    
         
            +
                      update_proc.call(record, attrs, force_replace: true)
         
     | 
| 
      
 114 
     | 
    
         
            +
                    end
         
     | 
| 
      
 115 
     | 
    
         
            +
                  end
         
     | 
| 
      
 116 
     | 
    
         
            +
             
     | 
| 
      
 117 
     | 
    
         
            +
                  def archive_taggable_items(records_to_remove, configs_timestamp)
         
     | 
| 
      
 118 
     | 
    
         
            +
                    records_to_remove.each do |record|
         
     | 
| 
      
 119 
     | 
    
         
            +
                      next if record.updated_at > configs_timestamp
         
     | 
| 
      
 120 
     | 
    
         
            +
             
     | 
| 
      
 121 
     | 
    
         
            +
                      record.update!(deleted_at: Time.current) if record.deleted_at.blank?
         
     | 
| 
      
 122 
     | 
    
         
            +
                    end
         
     | 
| 
      
 123 
     | 
    
         
            +
                  end
         
     | 
| 
      
 124 
     | 
    
         
            +
                end
         
     | 
| 
      
 125 
     | 
    
         
            +
              end
         
     | 
| 
      
 126 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,72 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Motor
         
     | 
| 
      
 4 
     | 
    
         
            +
              module Configs
         
     | 
| 
      
 5 
     | 
    
         
            +
                class SyncMiddleware
         
     | 
| 
      
 6 
     | 
    
         
            +
                  KeyNotSpecified = Class.new(StandardError)
         
     | 
| 
      
 7 
     | 
    
         
            +
                  NotAuthenticated = Class.new(StandardError)
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                  def initialize(app)
         
     | 
| 
      
 10 
     | 
    
         
            +
                    @app = app
         
     | 
| 
      
 11 
     | 
    
         
            +
                  end
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                  def call(env)
         
     | 
| 
      
 14 
     | 
    
         
            +
                    if env['PATH_INFO'] == Motor::Configs::SYNC_API_PATH
         
     | 
| 
      
 15 
     | 
    
         
            +
                      authenticate!(env['HTTP_X_AUTHORIZATION'])
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                      case env['REQUEST_METHOD']
         
     | 
| 
      
 18 
     | 
    
         
            +
                      when 'GET'
         
     | 
| 
      
 19 
     | 
    
         
            +
                        respond_with_configs
         
     | 
| 
      
 20 
     | 
    
         
            +
                      when 'POST'
         
     | 
| 
      
 21 
     | 
    
         
            +
                        input = env['rack.input']
         
     | 
| 
      
 22 
     | 
    
         
            +
                        input.rewind
         
     | 
| 
      
 23 
     | 
    
         
            +
                        sync_configs(input.read)
         
     | 
| 
      
 24 
     | 
    
         
            +
                      else
         
     | 
| 
      
 25 
     | 
    
         
            +
                        @app.call(env)
         
     | 
| 
      
 26 
     | 
    
         
            +
                      end
         
     | 
| 
      
 27 
     | 
    
         
            +
                    else
         
     | 
| 
      
 28 
     | 
    
         
            +
                      @app.call(env)
         
     | 
| 
      
 29 
     | 
    
         
            +
                    end
         
     | 
| 
      
 30 
     | 
    
         
            +
                  rescue NotAuthenticated
         
     | 
| 
      
 31 
     | 
    
         
            +
                    [403, {}, ['Invalid synchronization API key']]
         
     | 
| 
      
 32 
     | 
    
         
            +
                  rescue KeyNotSpecified
         
     | 
| 
      
 33 
     | 
    
         
            +
                    [404, {}, ['Set `MOTOR_SYNC_API_KEY` environment variable in order to sync configs']]
         
     | 
| 
      
 34 
     | 
    
         
            +
                  end
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
                  private
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
                  def authenticate!(token)
         
     | 
| 
      
 39 
     | 
    
         
            +
                    raise KeyNotSpecified if Motor::Configs::SYNC_ACCESS_KEY.blank?
         
     | 
| 
      
 40 
     | 
    
         
            +
                    raise NotAuthenticated if token.blank?
         
     | 
| 
      
 41 
     | 
    
         
            +
             
     | 
| 
      
 42 
     | 
    
         
            +
                    is_token_valid =
         
     | 
| 
      
 43 
     | 
    
         
            +
                      ActiveSupport::SecurityUtils.secure_compare(
         
     | 
| 
      
 44 
     | 
    
         
            +
                        Digest::SHA256.hexdigest(token),
         
     | 
| 
      
 45 
     | 
    
         
            +
                        Digest::SHA256.hexdigest(Motor::Configs::SYNC_ACCESS_KEY)
         
     | 
| 
      
 46 
     | 
    
         
            +
                      )
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
      
 48 
     | 
    
         
            +
                    raise NotAuthenticated unless is_token_valid
         
     | 
| 
      
 49 
     | 
    
         
            +
                  end
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
      
 51 
     | 
    
         
            +
                  def respond_with_configs
         
     | 
| 
      
 52 
     | 
    
         
            +
                    [
         
     | 
| 
      
 53 
     | 
    
         
            +
                      200,
         
     | 
| 
      
 54 
     | 
    
         
            +
                      { 'Content-Type' => 'application/json' },
         
     | 
| 
      
 55 
     | 
    
         
            +
                      [Motor::Configs::BuildConfigsHash.call.to_json]
         
     | 
| 
      
 56 
     | 
    
         
            +
                    ]
         
     | 
| 
      
 57 
     | 
    
         
            +
                  rescue StandardError => e
         
     | 
| 
      
 58 
     | 
    
         
            +
                    [500, {}, [e.message]]
         
     | 
| 
      
 59 
     | 
    
         
            +
                  end
         
     | 
| 
      
 60 
     | 
    
         
            +
             
     | 
| 
      
 61 
     | 
    
         
            +
                  def sync_configs(body)
         
     | 
| 
      
 62 
     | 
    
         
            +
                    configs_hash = JSON.parse(body)
         
     | 
| 
      
 63 
     | 
    
         
            +
             
     | 
| 
      
 64 
     | 
    
         
            +
                    Motor::Configs::SyncFromHash.call(configs_hash)
         
     | 
| 
      
 65 
     | 
    
         
            +
             
     | 
| 
      
 66 
     | 
    
         
            +
                    [200, {}, []]
         
     | 
| 
      
 67 
     | 
    
         
            +
                  rescue StandardError => e
         
     | 
| 
      
 68 
     | 
    
         
            +
                    [500, {}, [e.message]]
         
     | 
| 
      
 69 
     | 
    
         
            +
                  end
         
     | 
| 
      
 70 
     | 
    
         
            +
                end
         
     | 
| 
      
 71 
     | 
    
         
            +
              end
         
     | 
| 
      
 72 
     | 
    
         
            +
            end
         
     |