Skip to content

Commit 603ed87

Browse files
authored
Merge pull request #28 from angeljruiz/bridge-connection
Bridge Connection
2 parents 37221aa + 865b58e commit 603ed87

69 files changed

Lines changed: 1794 additions & 105 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.rubocop.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,4 @@ AllCops:
88
- "db/*/*"
99
Metrics/BlockLength:
1010
Exclude:
11-
- "spec/*/*"
11+
- "spec/**/*"

Gemfile

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ gem 'puma', '~> 4.1'
1414
# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
1515
gem 'jbuilder', '~> 2.7'
1616
# Use Redis adapter to run Action Cable in production
17-
# gem 'redis', '~> 4.0'
17+
gem 'redis', '~> 4.0'
1818
# Use Active Model has_secure_password
1919
gem 'bcrypt', '~> 3.1.7'
2020
gem 'jwt'
@@ -46,8 +46,10 @@ group :development do
4646
end
4747

4848
group :test do
49+
gem 'factory_bot_rails'
4950
# Generate test coverage reports
5051
gem 'simplecov', require: false
52+
gem 'webmock'
5153
end
5254

5355
# Windows does not include zoneinfo files, so bundle the tzinfo-data gem

Gemfile.lock

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ GEM
5656
minitest (~> 5.1)
5757
tzinfo (~> 1.1)
5858
zeitwerk (~> 2.2, >= 2.2.2)
59+
addressable (2.7.0)
60+
public_suffix (>= 2.0.2, < 5.0)
5961
ast (2.4.1)
6062
bcrypt (3.1.16)
6163
bootsnap (1.5.0)
@@ -65,13 +67,20 @@ GEM
6567
coderay (1.1.3)
6668
concurrent-ruby (1.1.7)
6769
connection_pool (2.2.3)
70+
crack (0.4.4)
6871
crass (1.0.6)
6972
diff-lcs (1.4.4)
7073
docile (1.3.2)
7174
erubi (1.9.0)
75+
factory_bot (6.1.0)
76+
activesupport (>= 5.0.0)
77+
factory_bot_rails (6.1.0)
78+
factory_bot (~> 6.1.0)
79+
railties (>= 5.0.0)
7280
ffi (1.13.1)
7381
globalid (0.4.2)
7482
activesupport (>= 4.2.0)
83+
hashdiff (1.0.1)
7584
i18n (1.8.5)
7685
concurrent-ruby (~> 1.0)
7786
jbuilder (2.10.1)
@@ -103,6 +112,7 @@ GEM
103112
pry (0.13.1)
104113
coderay (~> 1.1)
105114
method_source (~> 1.0)
115+
public_suffix (4.0.6)
106116
puma (4.3.6)
107117
nio4r (~> 2.0)
108118
rack (2.2.3)
@@ -197,6 +207,10 @@ GEM
197207
tzinfo (1.2.8)
198208
thread_safe (~> 0.1)
199209
unicode-display_width (1.7.0)
210+
webmock (3.10.0)
211+
addressable (>= 2.3.6)
212+
crack (>= 0.3.2)
213+
hashdiff (>= 0.4.0, < 2.0.0)
200214
websocket-driver (0.7.3)
201215
websocket-extensions (>= 0.1.0)
202216
websocket-extensions (0.1.5)
@@ -209,6 +223,7 @@ DEPENDENCIES
209223
bcrypt (~> 3.1.7)
210224
bootsnap (>= 1.5.0)
211225
byebug
226+
factory_bot_rails
212227
jbuilder (~> 2.7)
213228
jwt
214229
listen (~> 3.2)
@@ -217,13 +232,15 @@ DEPENDENCIES
217232
puma (~> 4.1)
218233
rack-cors
219234
rails (~> 6.0.3, >= 6.0.3.4)
235+
redis (~> 4.0)
220236
rspec-rails (~> 4.0.1)
221237
rubocop
222238
sidekiq
223239
simplecov
224240
spring
225241
spring-watcher-listen (~> 2.0.0)
226242
tzinfo-data
243+
webmock
227244

228245
RUBY VERSION
229246
ruby 2.6.6p146

app/controllers/bridges_controller.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ def bridge_params
6565
params.require(:bridge).permit(
6666
:active,
6767
:title,
68-
:method,
68+
:http_method,
6969
:retries,
7070
:delay,
7171
:outbound_url,
Lines changed: 68 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,68 +1,88 @@
11
# frozen_string_literal: true
22

33
class EventsController < ApplicationController
4-
before_action :authorize_request
5-
before_action :set_user
6-
# Needs to find all events based on bridge_id or event_id
7-
# Needs to return id for each event as well
8-
# def index
9-
# events = @current_user.bridges
10-
# events.map! do |event|
11-
# updated_at = String(event.updated_at)
12-
# date = date_format(updated_at.split(' ')[1])
13-
# time = updated_at.split(' ')[0]
14-
# { id: 1,
15-
# time: time,
16-
# date: date,
17-
# status_code: event.status_code }
18-
# end
19-
# render json: { events: events }, status: 200 # OK
20-
# end
4+
before_action :authorize_request, except: :create
5+
before_action :set_event, only: %i[show destroy]
216

22-
def show; end
7+
def index
8+
if fetch_events.empty?
9+
render json: { error: 'invalid parameters' }, status: 400 # Bad Request
10+
else
11+
render json: { events: @events }, status: 200
12+
end
13+
end
14+
15+
def show
16+
if @event
17+
render json: { event: @event }, status: 200
18+
else
19+
render json: { error: 'an event by that id was not found' }, status: 400
20+
end
21+
end
22+
23+
def destroy
24+
return render json: {}, status: 204 if @event&.destroy
25+
26+
render json: { error: 'an event by that id was not found' }, status: 400
27+
end
2328

24-
# receive bridge id + data
2529
def create
26-
bridge = Bridge.find(params[:id])
27-
data = { inbound: request.body, outbounds: [] }
28-
event = Event.new(data: data, bridge_id: bridge.id)
30+
event = create_event(find_bridge)
2931
if event.save
30-
# EventWorker.perform(id of event just created)
31-
status 201 # Created
32+
EventWorker.perform_async(event.id)
33+
render json: {}, status: 202 # Accepted
3234
else
33-
status 400 # Bad Request
35+
render json: { error: 'Invalid parameters' }, status: 400 # Bad Request
3436
end
37+
rescue JSON::ParserError
38+
render json: { error: 'Invalid request. Payload must be in JSON' }, status: 400 # Bad Request
3539
end
3640

3741
private
3842

39-
def date_format(_date)
40-
year = time.split('-')[0]
41-
month = time.split('-')[1]
42-
day = time.split('-')[2]
43-
"#{year}-#{month}-#{day}"
43+
def event_params
44+
params.permit(:id, :bridge_id, :event_id, :test)
4445
end
4546

46-
def set_user
47-
# bridge_id or #event_id
48-
# => User
47+
def fetch_events
48+
@events = if event_params[:bridge_id]
49+
Event.where(bridge_id: event_params[:bridge_id]).order(completed_at: :desc).limit(100)
50+
elsif event_params[:event_id]
51+
Event.where(bridge_id: find_event&.bridge_id).order(completed_at: :desc).limit(100)
52+
else
53+
[] # Prevent nil
54+
end
4955
end
50-
end
5156

52-
# Step 1
53-
# Convert binary to jsonb
57+
def data
58+
{
59+
'inbound' => {
60+
'payload' => JSON.parse(request.body.read),
61+
'dateTime' => DateTime.now.utc,
62+
'ip' => request.ip,
63+
'contentLength' => request.content_length
64+
},
65+
'outbound' => []
66+
}
67+
end
5468

55-
# Step 2
56-
# payload:
57-
{
58-
test: 'user entered string from editor',
59-
production: 'user entered string from editor'
60-
}
69+
def create_event(bridge)
70+
Event.new(
71+
data: data.to_json,
72+
bridge_id: bridge&.id,
73+
test: event_params[:test] || false
74+
)
75+
end
6176

62-
# Step 3
63-
#
77+
def set_event
78+
@event = find_event
79+
end
6480

65-
# NB: Need to run `bundle exec sidekiq` in separate terminal
66-
# localhost:3000/sidekiq to monitor sidekiq while running
67-
# after turning it on
68-
# => mount Sidekiq::Web => '/sidekiq
81+
def find_event
82+
Event.find_by(id: event_params[:event_id])
83+
end
84+
85+
def find_bridge
86+
Bridge.find_by(id: event_params[:bridge_id])
87+
end
88+
end

app/lib/bridge_api.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# frozen_string_literal: true
2+
3+
require_relative './invalid_payload_key'
4+
require_relative './invalid_environment_variable'
5+
require_relative './sidekiq/large_status_code'
6+
7+
module BridgeApi
8+
end

app/lib/bridge_api/http.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# frozen_string_literal: true
2+
3+
module BridgeApi
4+
module Http
5+
end
6+
end

app/lib/bridge_api/http/builder.rb

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
# frozen_string_literal: true
2+
3+
module BridgeApi
4+
module Http
5+
# Handles building a HTTP Request object. Will parse user defined payloads
6+
# and headers into expected values. Accepts custom parsers for extending.
7+
#
8+
# Example:
9+
#
10+
# ```ruby
11+
# event = Event.find(1)
12+
#
13+
# handler = BridgeApi::Http::RequestHandler.new event
14+
# builder = BridgeApi::Http::Builder.new handler, handler.payload_parser, handler.headers_parser
15+
#
16+
# builder.generate # => returns an Tuple containing Net::Http & Net::Http::{user_request_type} objects
17+
# ```
18+
class Builder
19+
SCHEME = 'https://'
20+
21+
include Interfaces::Builder
22+
23+
# @param [BridgeApi::Http::Handler] request_handler - Used for delegation
24+
# @param [BridgeApi::SyntaxParser::Interfaces::PayloadParser] payload_parser
25+
# @param [BridgeApi::SyntaxParser::Interfaces::HeadersParser] headers_parser
26+
def initialize(request_handler, payload_parser, headers_parser)
27+
@request_handler = request_handler
28+
@payload_parser = payload_parser
29+
@headers_parser = headers_parser
30+
end
31+
32+
# Generate & return `Net::HTTP`(net_http) & `Net::HTTP::{http_method}`(http_request) objects
33+
#
34+
# @return [Tuple(Net::HTTP, Net::HTTP::{http_method})]
35+
def generate
36+
[net_http, http_request]
37+
end
38+
39+
private
40+
41+
delegate :bridge, to: :request_handler
42+
delegate :event, to: :request_handler
43+
44+
attr_reader :request,
45+
:request_handler,
46+
:payload_parser,
47+
:headers_parser
48+
49+
def net_http
50+
http = Net::HTTP.new(uri.host, uri.port)
51+
http.use_ssl = true
52+
53+
http
54+
end
55+
56+
def http_request
57+
@request = net_http_request
58+
parse_headers!
59+
request.body = parsed_payload.to_json
60+
61+
request
62+
end
63+
64+
# Convert user defined outbound_url into a valid URI
65+
#
66+
# @return [String]
67+
def uri
68+
@uri ||= URI(scheme)
69+
end
70+
71+
# Sets the user defined headers into the `request` object
72+
def parse_headers!
73+
headers_parser.parse(headers) do |key, value|
74+
request[key] = value
75+
end
76+
end
77+
78+
# Parse our custom syntax into the expected values
79+
#
80+
# @return [Hash(String, String)]
81+
def parsed_payload
82+
@parsed_payload ||= payload_parser.parse(
83+
event.inbound_payload,
84+
JSON.parse(unparsed_payload)
85+
)
86+
end
87+
88+
# Returns either payload or test_payload depending
89+
# on the event environment
90+
#
91+
# @return [JSON]
92+
def unparsed_payload
93+
@unparsed_payload ||= data['payload']
94+
end
95+
96+
# Ensures the scheme used is using TSL
97+
#
98+
# @return [String]
99+
def scheme
100+
return outbound_url if outbound_url.starts_with?('https')
101+
102+
"#{SCHEME}#{outbound_url.split('//').last}"
103+
end
104+
105+
# Create a HTTP request object based on the user defined
106+
# HTTP method.
107+
#
108+
# @return [Net::HTTP::{http_method}]
109+
def net_http_request
110+
"Net::HTTP::#{http_method}".constantize.new(uri, 'Content-Type' => 'application/json')
111+
end
112+
113+
# @return [String]
114+
def http_method
115+
bridge.http_method.capitalize
116+
end
117+
118+
# @return [Hash(String, JSON)]
119+
def data
120+
bridge.data
121+
end
122+
123+
# @return [String]
124+
def outbound_url
125+
bridge.outbound_url
126+
end
127+
128+
# @return [ActiveRecord::Relation(Header)]
129+
def headers
130+
@headers ||= bridge.headers
131+
end
132+
end
133+
end
134+
end

0 commit comments

Comments
 (0)