cangaroo 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/Rakefile +32 -0
- data/app/controllers/cangaroo/application_controller.rb +4 -0
- data/app/controllers/cangaroo/endpoint_controller.rb +49 -0
- data/app/interactors/cangaroo/authenticate_connection.rb +19 -0
- data/app/interactors/cangaroo/count_json_object.rb +20 -0
- data/app/interactors/cangaroo/handle_request.rb +10 -0
- data/app/interactors/cangaroo/perform_jobs.rb +31 -0
- data/app/interactors/cangaroo/validate_json_schema.rb +31 -0
- data/app/jobs/cangaroo/job.rb +63 -0
- data/app/models/cangaroo/connection.rb +12 -0
- data/config/routes.rb +3 -0
- data/db/migrate/20151028172151_create_cangaroo_connections.rb +12 -0
- data/db/migrate/20151030140821_add_parameters_to_cangaroo_connection.rb +5 -0
- data/lib/cangaroo.rb +10 -0
- data/lib/cangaroo/engine.rb +17 -0
- data/lib/cangaroo/version.rb +3 -0
- data/lib/cangaroo/webhook/client.rb +44 -0
- data/lib/cangaroo/webhook/error.rb +6 -0
- data/lib/tasks/cangaroo_tasks.rake +4 -0
- data/spec/controllers/cangaroo/endpoint_controller_spec.rb +72 -0
- data/spec/dummy/README.rdoc +28 -0
- data/spec/dummy/Rakefile +6 -0
- data/spec/dummy/app/assets/javascripts/application.js +13 -0
- data/spec/dummy/app/assets/stylesheets/application.css +15 -0
- data/spec/dummy/app/controllers/application_controller.rb +5 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/bin/bundle +3 -0
- data/spec/dummy/bin/rails +4 -0
- data/spec/dummy/bin/rake +4 -0
- data/spec/dummy/bin/setup +29 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/config/application.rb +31 -0
- data/spec/dummy/config/boot.rb +5 -0
- data/spec/dummy/config/database.yml +11 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +41 -0
- data/spec/dummy/config/environments/production.rb +79 -0
- data/spec/dummy/config/environments/test.rb +42 -0
- data/spec/dummy/config/initializers/assets.rb +11 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/cookies_serializer.rb +3 -0
- data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/spec/dummy/config/initializers/inflections.rb +16 -0
- data/spec/dummy/config/initializers/mime_types.rb +4 -0
- data/spec/dummy/config/initializers/session_store.rb +3 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +9 -0
- data/spec/dummy/config/locales/en.yml +23 -0
- data/spec/dummy/config/routes.rb +4 -0
- data/spec/dummy/config/secrets.yml +22 -0
- data/spec/dummy/db/development.sqlite3 +0 -0
- data/spec/dummy/db/schema.rb +29 -0
- data/spec/dummy/db/test.sqlite3 +0 -0
- data/spec/dummy/log/cangaroo.log +0 -0
- data/spec/dummy/log/development.log +3466 -0
- data/spec/dummy/log/test.log +139640 -0
- data/spec/dummy/public/404.html +67 -0
- data/spec/dummy/public/422.html +67 -0
- data/spec/dummy/public/500.html +66 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/factories/cangaroo_connections.rb +9 -0
- data/spec/fixtures/json_payload_no_id.json +21 -0
- data/spec/fixtures/json_payload_ok.json +22 -0
- data/spec/fixtures/json_payload_wrong_key.json +22 -0
- data/spec/interactors/cangaroo/authenticate_connection_spec.rb +46 -0
- data/spec/interactors/cangaroo/count_json_object_spec.rb +17 -0
- data/spec/interactors/cangaroo/handle_request_spec.rb +18 -0
- data/spec/interactors/cangaroo/perform_jobs_spec.rb +42 -0
- data/spec/interactors/cangaroo/validate_json_schema_spec.rb +51 -0
- data/spec/jobs/cangaroo/job_spec.rb +55 -0
- data/spec/lib/cangaroo/webhook/client_spec.rb +69 -0
- data/spec/models/cangaroo/connection_spec.rb +15 -0
- data/spec/rails_helper.rb +85 -0
- data/spec/spec_helper.rb +9 -0
- data/spec/support/spec_helpers.rb +11 -0
- metadata +345 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 2d7be05c09c24fab1c3762ff2987c408f29171af
|
4
|
+
data.tar.gz: 627378f34edd49f3dd92283f736b2b07635057cd
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 12591483c6345eb3b72f1d61ebd8f309a7241d10f50699a441536b762276168987171a43735e6e12e7181c230b28770f4848b88f72e308b5e8c293c8f0cbe43e
|
7
|
+
data.tar.gz: c46a47b09ca10ca5d4cb562c314c7a93e300ff86f5a6d95061074165b3cbcb64ac459d3eaeb89d525fc030a3e3708b2fb752d168aa494631166ac9eb57d51e1b
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2015 Nebulab
|
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.
|
data/Rakefile
ADDED
@@ -0,0 +1,32 @@
|
|
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 = 'Cangaroo'
|
12
|
+
rdoc.options << '--line-numbers'
|
13
|
+
rdoc.rdoc_files.include('README.rdoc')
|
14
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
15
|
+
end
|
16
|
+
|
17
|
+
APP_RAKEFILE = File.expand_path('../spec/dummy/Rakefile', __FILE__)
|
18
|
+
load 'rails/tasks/engine.rake'
|
19
|
+
|
20
|
+
load 'rails/tasks/statistics.rake'
|
21
|
+
|
22
|
+
Bundler::GemHelper.install_tasks
|
23
|
+
|
24
|
+
Dir[File.join(File.dirname(__FILE__), 'tasks/**/*.rake')].each { |f| load f }
|
25
|
+
|
26
|
+
require 'rspec/core'
|
27
|
+
require 'rspec/core/rake_task'
|
28
|
+
|
29
|
+
desc 'Run all specs in spec directory (excluding plugin specs)'
|
30
|
+
RSpec::Core::RakeTask.new(spec: 'app:db:test:prepare')
|
31
|
+
|
32
|
+
task default: :spec
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require_dependency 'cangaroo/application_controller'
|
2
|
+
|
3
|
+
module Cangaroo
|
4
|
+
class EndpointController < ApplicationController
|
5
|
+
before_action :ensure_json_request
|
6
|
+
before_action :handle_request
|
7
|
+
|
8
|
+
rescue_from Exception, with: :handle_error
|
9
|
+
|
10
|
+
def create
|
11
|
+
if @command.success?
|
12
|
+
render json: @command.object_count, status: 202
|
13
|
+
else
|
14
|
+
render json: { error: @command.message },
|
15
|
+
status: @command.error_code
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def handle_error
|
22
|
+
unless Rails.env.development?
|
23
|
+
render json: { error: 'Something went wrong!' }, status: 500
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def handle_request
|
28
|
+
@command = HandleRequest.call(
|
29
|
+
key: key,
|
30
|
+
token: token,
|
31
|
+
json_body: params[:endpoint].to_json,
|
32
|
+
jobs: Rails.configuration.cangaroo.jobs
|
33
|
+
)
|
34
|
+
end
|
35
|
+
|
36
|
+
def ensure_json_request
|
37
|
+
return if request.headers['Content-Type'] == 'application/json'
|
38
|
+
render nothing: true, status: 406
|
39
|
+
end
|
40
|
+
|
41
|
+
def key
|
42
|
+
request.headers['X-Hub-Store']
|
43
|
+
end
|
44
|
+
|
45
|
+
def token
|
46
|
+
request.headers['X-Hub-Access-Token']
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Cangaroo
|
2
|
+
class AuthenticateConnection
|
3
|
+
include Interactor
|
4
|
+
|
5
|
+
before :prepare_context
|
6
|
+
|
7
|
+
def call
|
8
|
+
context.source_connection ||
|
9
|
+
context.fail!(message: 'wrong credentials', error_code: 401)
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def prepare_context
|
15
|
+
context.source_connection =
|
16
|
+
Cangaroo::Connection.authenticate(context.key, context.token)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Cangaroo
|
2
|
+
class CountJsonObject
|
3
|
+
include Interactor
|
4
|
+
|
5
|
+
before :prepare_context
|
6
|
+
|
7
|
+
def call
|
8
|
+
context.object_count = context.data.inject({}) do |o, (k, v)|
|
9
|
+
o[k] = v.size
|
10
|
+
o
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def prepare_context
|
17
|
+
context.data = JSON.parse(context.json_body)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Cangaroo
|
2
|
+
class PerformJobs
|
3
|
+
include Interactor
|
4
|
+
|
5
|
+
def call
|
6
|
+
data.each do |type, payloads|
|
7
|
+
payloads.each { |payload| enqueue_jobs(type, payload) }
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def data
|
14
|
+
@data ||= JSON.parse(context.json_body)
|
15
|
+
end
|
16
|
+
|
17
|
+
def enqueue_jobs(type, payload)
|
18
|
+
initialize_jobs(type, payload).select(&:perform?).each(&:enqueue)
|
19
|
+
end
|
20
|
+
|
21
|
+
def initialize_jobs(type, payload)
|
22
|
+
context.jobs.map do |klass|
|
23
|
+
klass.new(
|
24
|
+
connection: context.source_connection,
|
25
|
+
type: type,
|
26
|
+
payload: payload
|
27
|
+
)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Cangaroo
|
2
|
+
class ValidateJsonSchema
|
3
|
+
include Interactor
|
4
|
+
|
5
|
+
SCHEMA = {
|
6
|
+
'type': 'object',
|
7
|
+
'minProperties': 1,
|
8
|
+
'additionalProperties': false,
|
9
|
+
'patternProperties': {
|
10
|
+
'^[a-z]*$': {
|
11
|
+
'type': 'array',
|
12
|
+
'minItems': 1,
|
13
|
+
'items': {
|
14
|
+
'type': 'object',
|
15
|
+
'required': ['id'],
|
16
|
+
'properties': {
|
17
|
+
'id': {
|
18
|
+
'type': 'string'
|
19
|
+
}
|
20
|
+
}
|
21
|
+
}
|
22
|
+
}
|
23
|
+
}
|
24
|
+
}.freeze
|
25
|
+
|
26
|
+
def call
|
27
|
+
JSON::Validator.fully_validate(SCHEMA, context.json_body).empty? ||
|
28
|
+
context.fail!(message: 'wrong json schema', error_code: 500)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module Cangaroo
|
2
|
+
class Job < ActiveJob::Base
|
3
|
+
queue_as :cangaroo
|
4
|
+
|
5
|
+
class_attribute :connection_name, :webhook_path, :webhook_parameters
|
6
|
+
class << self
|
7
|
+
def connection(name)
|
8
|
+
self.connection_name = name
|
9
|
+
end
|
10
|
+
|
11
|
+
def path(path)
|
12
|
+
self.webhook_path = path
|
13
|
+
end
|
14
|
+
|
15
|
+
def parameters(parameters)
|
16
|
+
self.webhook_parameters = parameters
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def perform(*)
|
21
|
+
Cangaroo::Webhook::Client.new(destination_connection, path)
|
22
|
+
.post(transform, @job_id, parameters)
|
23
|
+
end
|
24
|
+
|
25
|
+
def perform?
|
26
|
+
fail NotImplementedError
|
27
|
+
end
|
28
|
+
|
29
|
+
def transform
|
30
|
+
{ type.singularize => payload }
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def source_connection
|
36
|
+
arguments.first.fetch(:connection)
|
37
|
+
end
|
38
|
+
|
39
|
+
def type
|
40
|
+
arguments.first.fetch(:type)
|
41
|
+
end
|
42
|
+
|
43
|
+
def payload
|
44
|
+
arguments.first.fetch(:payload)
|
45
|
+
end
|
46
|
+
|
47
|
+
def destination_connection
|
48
|
+
@connection ||= Cangaroo::Connection.find_by!(name: connection_name)
|
49
|
+
end
|
50
|
+
|
51
|
+
def connection_name
|
52
|
+
self.class.connection_name
|
53
|
+
end
|
54
|
+
|
55
|
+
def path
|
56
|
+
self.class.webhook_path || ''
|
57
|
+
end
|
58
|
+
|
59
|
+
def parameters
|
60
|
+
self.class.webhook_parameters || {}
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Cangaroo
|
2
|
+
class Connection < ActiveRecord::Base
|
3
|
+
serialize :parameters
|
4
|
+
|
5
|
+
validates :name, :url, :key, :token, presence: true
|
6
|
+
validates :name, :url, :key, :token, uniqueness: true
|
7
|
+
|
8
|
+
def self.authenticate(key, token)
|
9
|
+
where(key: key, token: token).first
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
data/config/routes.rb
ADDED
data/lib/cangaroo.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
module Cangaroo
|
2
|
+
class Engine < ::Rails::Engine
|
3
|
+
isolate_namespace Cangaroo
|
4
|
+
|
5
|
+
config.generators do |g|
|
6
|
+
g.test_framework :rspec, fixture: false
|
7
|
+
g.fixture_replacement :factory_girl, dir: 'spec/factories'
|
8
|
+
g.assets false
|
9
|
+
g.helper false
|
10
|
+
end
|
11
|
+
|
12
|
+
config.before_configuration do
|
13
|
+
Rails.configuration.cangaroo = ActiveSupport::OrderedOptions.new
|
14
|
+
Rails.configuration.cangaroo.jobs = []
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Cangaroo
|
2
|
+
module Webhook
|
3
|
+
class Client
|
4
|
+
include HTTParty
|
5
|
+
|
6
|
+
format :json
|
7
|
+
|
8
|
+
attr_accessor :connection, :path
|
9
|
+
|
10
|
+
def initialize(connection, path)
|
11
|
+
@connection = connection
|
12
|
+
@path = path
|
13
|
+
end
|
14
|
+
|
15
|
+
def post(payload, request_id, parameters)
|
16
|
+
request_body = body(payload, request_id, parameters).to_json
|
17
|
+
req = self.class.post(url, headers: headers, body: request_body)
|
18
|
+
if req.response.code == '200'
|
19
|
+
req.parsed_response
|
20
|
+
else
|
21
|
+
fail Cangaroo::Webhook::Error, req.parsed_response['summary']
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def url
|
28
|
+
URI.parse(
|
29
|
+
HTTParty.normalize_base_uri(connection.url)
|
30
|
+
).merge(path.to_s).to_s
|
31
|
+
end
|
32
|
+
|
33
|
+
def headers
|
34
|
+
{ 'X_HUB_TOKEN' => connection.token }
|
35
|
+
end
|
36
|
+
|
37
|
+
def body(payload, request_id, parameters)
|
38
|
+
{ request_id: request_id,
|
39
|
+
parameters: connection.parameters.deep_merge(parameters)
|
40
|
+
}.merge(payload)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'rails_helper'
|
2
|
+
|
3
|
+
module Cangaroo
|
4
|
+
RSpec.describe EndpointController, type: :controller do
|
5
|
+
routes { Cangaroo::Engine.routes }
|
6
|
+
let(:connection) { create :cangaroo_connection }
|
7
|
+
let(:request_payload) { JSON.parse(load_fixture('json_payload_ok.json')) }
|
8
|
+
|
9
|
+
before do
|
10
|
+
request.headers['Content-Type'] = 'application/json'
|
11
|
+
request.headers['X-Hub-Store'] = connection.key
|
12
|
+
request.headers['X-Hub-Access-Token'] = connection.token
|
13
|
+
end
|
14
|
+
|
15
|
+
describe '#create' do
|
16
|
+
before do
|
17
|
+
post :create, request_payload
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'accepts only application/json requests' do
|
21
|
+
expect(response.status).to eq(202)
|
22
|
+
|
23
|
+
request.headers['Content-Type'] = 'application/text'
|
24
|
+
post :create
|
25
|
+
expect(response.status).to eq(406)
|
26
|
+
end
|
27
|
+
|
28
|
+
context 'when success' do
|
29
|
+
let(:auth_headers) {}
|
30
|
+
|
31
|
+
it 'responds with 200' do
|
32
|
+
expect(response.status).to eq(202)
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'responds with the number of objects received in payload' do
|
36
|
+
res = JSON.parse(response.body)
|
37
|
+
expect(res).to eq('orders' => 2, 'shipments' => 2)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
context 'when error' do
|
42
|
+
before do
|
43
|
+
request.headers['X-Hub-Access-Token'] = 'wrongtoken'
|
44
|
+
post :create, request_payload
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'responds with the command error code' do
|
48
|
+
expect(response.status).to eq(401)
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'responds with error messages in the body' do
|
52
|
+
expect(JSON.parse(response.body)['error']).to be_present
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
context 'when an exception was raised' do
|
57
|
+
before do
|
58
|
+
HandleRequest.stub(:call).and_raise('An error')
|
59
|
+
post :create, request_payload
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'responds with 500' do
|
63
|
+
expect(response.status).to eq(500)
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'responds with error messages in the body' do
|
67
|
+
expect(JSON.parse(response.body)['error']).to eq 'Something went wrong!'
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|