hasura_handler 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 95be3b9811df02080b282112b53e0b0a6aacd4520cd98de9de851af3349028a8
4
+ data.tar.gz: 41a54a270f42447b21ae308b79e23bd1ad24851cbc00d1c5101d8ca478342ef5
5
+ SHA512:
6
+ metadata.gz: f03c0e96d4c8f4e06de84c7ed087fb79a3c86d0fe8be6a29fd8d465d581c1a0735e81bf7cb3015eb1c7ac4bc2e25416ceef712716aa805a9163c6940d83aaa59
7
+ data.tar.gz: b3c696453cd65e2c16b9d0e2888d0681456d7bba76d1e78d93b5cbcf510096c5618ecd58e5d81c5c7bdbfdffe8d68a651963e8737e293b93476fd9d6d7c8c720
@@ -0,0 +1,20 @@
1
+ Copyright 2020 Kaz Walker
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,28 @@
1
+ # HasuraHandler
2
+ Short description and motivation.
3
+
4
+ ## Usage
5
+ How to use my plugin.
6
+
7
+ ## Installation
8
+ Add this line to your application's Gemfile:
9
+
10
+ ```ruby
11
+ gem 'hasura_handler'
12
+ ```
13
+
14
+ And then execute:
15
+ ```bash
16
+ $ bundle
17
+ ```
18
+
19
+ Or install it yourself as:
20
+ ```bash
21
+ $ gem install hasura_handler
22
+ ```
23
+
24
+ ## Contributing
25
+ Contribution directions go here.
26
+
27
+ ## License
28
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,29 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'HasuraHandler'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.md')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+ load 'rails/tasks/statistics.rake'
18
+
19
+ require 'bundler/gem_tasks'
20
+
21
+ require 'rake/testtask'
22
+
23
+ Rake::TestTask.new(:test) do |t|
24
+ t.libs << 'test'
25
+ t.pattern = 'test/**/*_test.rb'
26
+ t.verbose = false
27
+ end
28
+
29
+ task default: :test
@@ -0,0 +1,30 @@
1
+ require_dependency 'hasura_handler/application_controller'
2
+
3
+ module HasuraHandler
4
+ class ActionsController < ApplicationController
5
+ def process
6
+ unless HasuraHandler::Action.hasura_actions.keys.include?(action_params[:action][:name])
7
+ render json: { error: true, message: 'action name not registered' }, status: 404
8
+ return
9
+ end
10
+
11
+ klass = HasuraHandler::Action.hasura_actions[action_params[:action][:name]]
12
+ action = klass.new(action_params[:session_variables], action_params[:input])
13
+ action.run
14
+
15
+ if action.error_message.present?
16
+ render json: { error: true, message: action.error_message }, status: 400
17
+ else
18
+ render json: action.output
19
+ end
20
+ end
21
+
22
+ def action_params
23
+ params.permit(
24
+ action: [:name],
25
+ input: {},
26
+ session_variables: {}
27
+ )
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,13 @@
1
+ module HasuraHandler
2
+ class ApplicationController < ActionController::API
3
+ before_action :check_header
4
+
5
+ private
6
+
7
+ def check_header
8
+ unless request.headers[HasuraHandler.auth_header] == HasuraHandler.auth_key
9
+ render json: { error: true, message: 'unable to authenticate request' }, status: 401
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,58 @@
1
+ require_dependency 'hasura_handler/application_controller'
2
+
3
+ module HasuraHandler
4
+ class EventsController < ApplicationController
5
+ def process
6
+ processor = HasuraHandler::EventHandler.new(event_params)
7
+
8
+ unless processor.event.valid?
9
+ error_response(processor.event.errors)
10
+ return
11
+ end
12
+
13
+ if HasuraHandler.async_events
14
+ processor.process_later
15
+ render json: { queued: true }, status: 202
16
+ return
17
+ end
18
+
19
+ processor.process
20
+ if processor.success?
21
+ render json: { success: true }
22
+ else
23
+ error_response(processor.errors)
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def error_response(errors)
30
+ response.set_header('Retry-After', HasuraHandler.retry_after)
31
+ render json: { success: false, errors: errors }, status: 400
32
+ end
33
+
34
+ def event_params
35
+ params.permit(
36
+ :id,
37
+ :created_at,
38
+ table: [
39
+ :schema,
40
+ :name
41
+ ],
42
+ trigger: [
43
+ :name
44
+ ],
45
+ event: [
46
+ :op,
47
+ {
48
+ session_variables: {},
49
+ data: {
50
+ new: {},
51
+ old: {}
52
+ }
53
+ }
54
+ ]
55
+ )
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,4 @@
1
+ module HasuraHandler
2
+ class ApplicationJob < ActiveJob::Base
3
+ end
4
+ end
@@ -0,0 +1,11 @@
1
+ module HasuraHandler
2
+ class EventHandlerJob < ApplicationJob
3
+ queue_as HasuraHandler.event_handler_job_queue
4
+
5
+ def perform(handler_class, event)
6
+ klass = handler_class.constantize
7
+ handler = klass.new(event)
8
+ handler.run
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,9 @@
1
+ module HasuraHandler
2
+ class EventJob < ApplicationJob
3
+ queue_as HasuraHandler.event_job_queue
4
+
5
+ def perform(event)
6
+ HasuraHandler::EventHandler.new(event).process
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ HasuraHandler::Engine.routes.draw do
2
+ if HasuraHandler.events_enabled
3
+ post '/events', to: 'events#process'
4
+ end
5
+
6
+ if HasuraHandler.actions_enabled
7
+ post '/actions', to: 'actions#process'
8
+ end
9
+ end
@@ -0,0 +1,34 @@
1
+ require 'hasura_handler/engine'
2
+ require 'hasura_handler/event_handler'
3
+ require 'hasura_handler/event'
4
+ require 'hasura_handler/action'
5
+
6
+ module HasuraHandler
7
+ class << self
8
+ mattr_accessor :auth_header,
9
+ :auth_key,
10
+ :events_enabled,
11
+ :actions_enabled,
12
+ :event_job_queue,
13
+ :event_handler_job_queue,
14
+ :async_events,
15
+ :fanout_events,
16
+ :retry_after
17
+
18
+ self.auth_header = 'X-Hasura-Service-Key'
19
+ self.events_enabled = true
20
+ self.async_events = true
21
+ self.fanout_events = true
22
+ self.actions_enabled = true
23
+ self.event_job_queue = :hasura_event
24
+ self.event_handler_job_queue = :hasura_event
25
+ self.retry_after = 5
26
+ end
27
+
28
+ def self.setup(&block)
29
+ yield self
30
+ [:auth_key].each do |key|
31
+ raise "HasuraHandler requires the #{key} to be configured." if self.send(key).blank?
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,32 @@
1
+ module HasuraHandler
2
+ class Action
3
+ @@hasura_actions = {}
4
+ class << self
5
+ attr_reader :hasura_action_name
6
+
7
+ def action_name(action_name)
8
+ raise 'run method not defined' unless new(nil, nil).respond_to?(:run)
9
+ raise 'action_name must be a symbol or string' unless action_name.is_a?(Symbol) || action_name.is_a?(String)
10
+ raise 'action_name already used' if @@hasura_actions.keys.include?(action_name.to_s)
11
+
12
+ @@hasura_actions[action_name.to_s] = self
13
+ @hasura_action_name = action_name.to_s
14
+ end
15
+
16
+ def hasura_actions
17
+ @@hasura_actions
18
+ end
19
+ end
20
+
21
+ attr_reader :session_variables,
22
+ :input,
23
+ :output,
24
+ :error_message
25
+
26
+ def initialize(session_variables, input)
27
+ @session_variables = session_variables
28
+ @input = input
29
+ @output = {}
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,6 @@
1
+ module HasuraHandler
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace HasuraHandler
4
+ config.generators.api_only = true
5
+ end
6
+ end
@@ -0,0 +1,86 @@
1
+ module HasuraHandler
2
+ class Event
3
+ attr_reader :id,
4
+ :table,
5
+ :trigger,
6
+ :event,
7
+ :created_at,
8
+ :raw_event,
9
+ :errors,
10
+ :valid
11
+
12
+ def initialize(event)
13
+ @id = event[:id]
14
+ @table = event[:table]
15
+ @trigger = event[:trigger]
16
+ @event = event[:event]
17
+ @created_at = event[:created_at]
18
+ @raw_event = event
19
+ @errors = {}
20
+
21
+ [
22
+ :validate_simple_fields,
23
+ :validate_table,
24
+ :validate_trigger,
25
+ :validate_event
26
+ ].each{ |check| self.send(check) }
27
+ end
28
+
29
+ def valid?
30
+ @errors.blank?
31
+ end
32
+
33
+ private
34
+
35
+ def validate_simple_fields
36
+ [
37
+ :id,
38
+ :created_at
39
+ ].each do |field|
40
+ @errors[field.to_s] = 'missing' unless self.send(field).present?
41
+ end
42
+ end
43
+
44
+ def validate_table
45
+ unless @table.is_a?(Hash)
46
+ @errors['table'] = 'not a hash'
47
+ return
48
+ end
49
+
50
+ string_fields?(@table, 'table', [:schema, :name])
51
+ end
52
+
53
+ def validate_trigger
54
+ unless @trigger.is_a?(Hash)
55
+ @errors['trigger'] = 'not a hash'
56
+ return
57
+ end
58
+
59
+ string_fields?(@trigger, 'trigger', [:name])
60
+ end
61
+
62
+ def validate_event
63
+ unless @event.is_a?(Hash)
64
+ @errors['event'] = 'not a hash'
65
+ return
66
+ end
67
+
68
+ @errors['event.session_variables'] = 'not a hash' unless @event[:session_variables].is_a?(Hash)
69
+ string_fields?(@event, 'event', [:op])
70
+
71
+ [:new, :old].each do |field|
72
+ unless @event[field].nil? || @event[field].is_a?(Hash)
73
+ @errors["event.data.#{field}"] = 'not a hash'
74
+ end
75
+ end
76
+ end
77
+
78
+ def string_fields?(field, error_key, subfields)
79
+ subfields.each do |subfield|
80
+ unless field[subfield].present? && field[subfield].is_a?(String)
81
+ errors["#{error_key}.#{subfield}"] = 'missing'
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,25 @@
1
+ module HasuraHandler
2
+ class EventHandler
3
+ class << self
4
+ attr_reader :hasura_matchers
5
+
6
+ def match_by(matchers)
7
+ raise 'matcher must be a hash' unless matchers.is_a?(Hash)
8
+ allowed_matchers = [:table, :trigger, :op]
9
+
10
+ matchers.keys.each do |matcher|
11
+ raise 'invalid matcher' unless allowed_matchers.include?(matcher)
12
+ raise 'invalid matcher value' unless matchers[matcher].is_a?(String)
13
+ end
14
+
15
+ raise 'run method not defined' unless new(nil).respond_to?(:run)
16
+
17
+ @hasura_matchers = matchers
18
+ end
19
+ end
20
+
21
+ def initialize(event)
22
+ @event = event
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,43 @@
1
+ module HasuraHandler
2
+ class EventProcessor
3
+ attr_accessor :event
4
+
5
+ def initialize(event)
6
+ @event = HasuraHandler::Event.new(event)
7
+ end
8
+
9
+ def process_later
10
+ HasuraHandler::EventJob.perform_later(@event.raw_event)
11
+ end
12
+
13
+ def process
14
+ event_handlers.each do |handler_class|
15
+ if HasuraHandler.fanout_events
16
+ HasuraHandler::EventHandlerJob.perform_later(handler_class.to_s, @event.raw_event)
17
+ else
18
+ handler = handler_class.new(@event)
19
+ handler.run
20
+ end
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ def event_handlers
27
+ HasuraHandler::EventHandler.
28
+ descendants.
29
+ map{ |klass| [klass, klass.hasura_matchers] }.
30
+ to_h.
31
+ map{ |klass,matchers| [klass, check_matchers(matchers)] }.
32
+ to_h.
33
+ select{ |klass,match| match }.
34
+ keys
35
+ end
36
+
37
+ def check_matchers(matchers)
38
+ matchers.all? do |matcher|
39
+ @event.send(matcher.first) == matcher.last
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,3 @@
1
+ module HasuraHandler
2
+ VERSION = '0.1.0'
3
+ end
@@ -0,0 +1,4 @@
1
+ # desc 'Explaining what the task does'
2
+ # task :hasura_handler do
3
+ # # Task goes here
4
+ # end
metadata ADDED
@@ -0,0 +1,95 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: hasura_handler
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Kaz Walker
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-07-25 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 6.0.3
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 6.0.3.2
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: 6.0.3
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 6.0.3.2
33
+ - !ruby/object:Gem::Dependency
34
+ name: graphql-client
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: 0.16.0
40
+ type: :runtime
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: 0.16.0
47
+ description: Provides an easy way to build custom backends for Hasura.
48
+ email:
49
+ - me@kaz.codes
50
+ executables: []
51
+ extensions: []
52
+ extra_rdoc_files: []
53
+ files:
54
+ - MIT-LICENSE
55
+ - README.md
56
+ - Rakefile
57
+ - app/controllers/hasura_handler/actions_controller.rb
58
+ - app/controllers/hasura_handler/application_controller.rb
59
+ - app/controllers/hasura_handler/events_controller.rb
60
+ - app/jobs/hasura_handler/application_job.rb
61
+ - app/jobs/hasura_handler/event_handler_job.rb
62
+ - app/jobs/hasura_handler/event_job.rb
63
+ - config/routes.rb
64
+ - lib/hasura_handler.rb
65
+ - lib/hasura_handler/action.rb
66
+ - lib/hasura_handler/engine.rb
67
+ - lib/hasura_handler/event.rb
68
+ - lib/hasura_handler/event_handler.rb
69
+ - lib/hasura_handler/event_processor.rb
70
+ - lib/hasura_handler/version.rb
71
+ - lib/tasks/hasura_handler_tasks.rake
72
+ homepage: https://github.com/KazW/HasuraHandler
73
+ licenses:
74
+ - MIT
75
+ metadata: {}
76
+ post_install_message:
77
+ rdoc_options: []
78
+ require_paths:
79
+ - lib
80
+ required_ruby_version: !ruby/object:Gem::Requirement
81
+ requirements:
82
+ - - ">="
83
+ - !ruby/object:Gem::Version
84
+ version: '0'
85
+ required_rubygems_version: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ requirements: []
91
+ rubygems_version: 3.1.4
92
+ signing_key:
93
+ specification_version: 4
94
+ summary: Integrates Hasura with Rails
95
+ test_files: []