putsreq 0.0.3 → 0.0.4

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.
Files changed (141) hide show
  1. checksums.yaml +5 -5
  2. data/.babelrc +26 -0
  3. data/.codeclimate.yml +29 -5
  4. data/.env.docker +6 -0
  5. data/.eslintignore +4 -0
  6. data/.eslintrc +22 -0
  7. data/.gitignore +8 -0
  8. data/.postcssrc.yml +3 -0
  9. data/.prettierrc +4 -0
  10. data/.reek +12 -0
  11. data/.rubocop.yml +47 -25
  12. data/.ruby-version +1 -1
  13. data/.simplecov +6 -0
  14. data/.travis.yml +38 -4
  15. data/Dockerfile +44 -0
  16. data/Gemfile +40 -61
  17. data/Gemfile.lock +294 -186
  18. data/Procfile +1 -1
  19. data/README.md +51 -46
  20. data/Rakefile +1 -1
  21. data/{public → app/assets}/images/logo.png +0 -0
  22. data/app/assets/javascripts/application.js +1 -1
  23. data/app/assets/javascripts/buckets.js.coffee +1 -21
  24. data/app/assets/stylesheets/{application.css → application.scss} +2 -1
  25. data/app/assets/stylesheets/buckets.css.scss +0 -2
  26. data/app/controllers/application_controller.rb +11 -9
  27. data/app/controllers/buckets_controller.rb +24 -18
  28. data/app/controllers/home_controller.rb +5 -5
  29. data/app/controllers/requests_controller.rb +13 -2
  30. data/app/helpers/application_helper.rb +6 -2
  31. data/app/interactors/create_or_retrieve_bucket.rb +17 -0
  32. data/app/interactors/create_request.rb +25 -7
  33. data/app/interactors/eval_response_builder.rb +9 -9
  34. data/app/interactors/filter_headers.rb +3 -3
  35. data/app/interactors/forward_request.rb +4 -4
  36. data/app/interactors/record_request.rb +1 -1
  37. data/app/interactors/track.rb +44 -0
  38. data/app/javascript/actionTypes.js +7 -0
  39. data/app/javascript/actions/index.js +64 -0
  40. data/app/javascript/components/Bucket.jsx +91 -0
  41. data/app/javascript/components/Pagination.jsx +67 -0
  42. data/app/javascript/components/RequestCount.jsx +21 -0
  43. data/app/javascript/components/request/Header.jsx +31 -0
  44. data/app/javascript/components/request/Response.jsx +28 -0
  45. data/app/javascript/components/request/index.jsx +47 -0
  46. data/app/javascript/packs/application.js +25 -0
  47. data/app/javascript/packs/hello_react.jsx +26 -0
  48. data/app/javascript/reducers/index.js +23 -0
  49. data/app/javascript/request_poller.js +19 -0
  50. data/app/javascript/store.js +9 -0
  51. data/app/models/bucket.rb +16 -10
  52. data/app/models/request.rb +2 -3
  53. data/app/models/user.rb +2 -2
  54. data/app/serializers/bucket_serializer.rb +11 -0
  55. data/app/serializers/request_serializer.rb +69 -0
  56. data/app/views/buckets/_buttons.html.erb +3 -3
  57. data/app/views/buckets/_form.html.erb +6 -6
  58. data/app/views/buckets/_readonly_buttons.html.erb +1 -1
  59. data/app/views/buckets/show.html.erb +12 -25
  60. data/app/views/home/index.html.erb +22 -16
  61. data/app/views/layouts/application.html.erb +21 -22
  62. data/app/views/layouts/devise.html.erb +16 -18
  63. data/app/views/shared/_flash.html.erb +3 -1
  64. data/bin/byebug +21 -0
  65. data/bin/cc-tddium-post-worker +21 -0
  66. data/bin/codeclimate-test-reporter +21 -0
  67. data/bin/coderay +21 -0
  68. data/bin/dotenv +21 -0
  69. data/bin/htmldiff +21 -0
  70. data/bin/httparty +21 -0
  71. data/bin/httpclient +21 -0
  72. data/bin/ldiff +21 -0
  73. data/bin/mongo_console +21 -0
  74. data/bin/nokogiri +21 -0
  75. data/bin/pry +21 -0
  76. data/bin/putsreq +18 -71
  77. data/bin/rackup +21 -0
  78. data/bin/rails +1 -1
  79. data/bin/ri +21 -0
  80. data/bin/rollbar-rails-runner +21 -0
  81. data/bin/rspec +10 -5
  82. data/bin/safe_yaml +21 -0
  83. data/bin/sass +21 -0
  84. data/bin/sass-convert +21 -0
  85. data/bin/scss +21 -0
  86. data/bin/sdoc +21 -0
  87. data/bin/sdoc-merge +21 -0
  88. data/bin/setup +38 -0
  89. data/bin/sprockets +21 -0
  90. data/bin/thor +21 -0
  91. data/bin/tilt +21 -0
  92. data/bin/unicorn +21 -0
  93. data/bin/unicorn_rails +21 -0
  94. data/bin/update +29 -0
  95. data/bin/webpack +15 -0
  96. data/bin/webpack-dev-server +15 -0
  97. data/bin/yarn +11 -0
  98. data/config/application.rb +6 -4
  99. data/config/boot.rb +1 -1
  100. data/config/environment.rb +1 -1
  101. data/config/environments/development.rb +7 -3
  102. data/config/environments/production.rb +5 -2
  103. data/config/environments/test.rb +3 -1
  104. data/config/initializers/devise.rb +2 -2
  105. data/config/initializers/rack_attack.rb +24 -0
  106. data/config/initializers/rollbar.rb +58 -0
  107. data/config/initializers/setup_email.rb +7 -7
  108. data/config/mongoid.yml +2 -2
  109. data/config/newrelic.yml +45 -0
  110. data/config/puma.rb +15 -0
  111. data/config/routes.rb +2 -0
  112. data/config/webpack/development.js +5 -0
  113. data/config/webpack/environment.js +3 -0
  114. data/config/webpack/production.js +5 -0
  115. data/config/webpack/test.js +5 -0
  116. data/config/webpacker.yml +70 -0
  117. data/docker-compose.yml +25 -0
  118. data/lib/putsreq/cli_helper.rb +114 -0
  119. data/lib/putsreq/version.rb +1 -1
  120. data/package.json +30 -0
  121. data/putsreq.gemspec +6 -7
  122. data/spec/controllers/buckets_controller_spec.rb +54 -46
  123. data/spec/interactors/create_or_retrieve_bucket_spec.rb +25 -0
  124. data/spec/interactors/create_request_spec.rb +11 -8
  125. data/spec/interactors/create_response_spec.rb +1 -1
  126. data/spec/interactors/eval_response_builder_spec.rb +14 -17
  127. data/spec/interactors/forward_request_spec.rb +4 -4
  128. data/spec/lib/putsreq/cli_helper_spec.rb +114 -0
  129. data/spec/models/bucket_spec.rb +3 -3
  130. data/spec/spec_helper.rb +25 -25
  131. data/yarn.lock +7043 -0
  132. metadata +85 -20
  133. data/.hound.yml +0 -14
  134. data/app/interactors/notify_count.rb +0 -15
  135. data/app/models/.keep +0 -0
  136. data/config/initializers/redis.rb +0 -17
  137. data/config/unicorn.rb +0 -16
  138. data/lib/assets/.keep +0 -0
  139. data/public/images/.keep +0 -0
  140. data/vendor/assets/javascripts/.keep +0 -0
  141. data/vendor/assets/stylesheets/.keep +0 -0
@@ -0,0 +1,17 @@
1
+ class CreateOrRetrieveBucket
2
+ include Interactor
3
+
4
+ delegate :token, :owner_token, :user_id, to: :context
5
+
6
+ def call
7
+ if (bucket = Bucket.where(token: token).first)
8
+ return context.bucket = bucket
9
+ end
10
+
11
+ context.bucket = Bucket.create(
12
+ owner_token: owner_token,
13
+ user_id: user_id,
14
+ token: token
15
+ )
16
+ end
17
+ end
@@ -4,12 +4,30 @@ class CreateRequest
4
4
  delegate :bucket, :rack_request, to: :context
5
5
 
6
6
  def call
7
- context.request = bucket.requests.create(body: rack_request.body.read,
8
- content_length: rack_request.content_length,
9
- request_method: rack_request.request_method,
10
- ip: rack_request.ip,
11
- url: rack_request.url,
12
- headers: FilterHeaders.call(headers: rack_request.env).headers,
13
- params: rack_request.request_parameters)
7
+ context.request = bucket.requests.create(
8
+ body: body(rack_request),
9
+ content_length: rack_request.content_length,
10
+ request_method: rack_request.request_method,
11
+ ip: rack_request.ip,
12
+ url: rack_request.url,
13
+ headers: FilterHeaders.call(headers: rack_request.env).headers
14
+ )
15
+
16
+ context.params = params
17
+ rescue
18
+ Rollbar.scope!(request: context.request)
19
+ raise
20
+ end
21
+
22
+ private
23
+
24
+ def params
25
+ rack_request.params.to_h.except('controller', 'action', 'token')
26
+ end
27
+
28
+ def body(rack_request)
29
+ body = rack_request.body.read.encode('UTF-8', invalid: :replace, undef: :replace)
30
+
31
+ body.is_utf8? ? body : nil
14
32
  end
15
33
  end
@@ -1,7 +1,7 @@
1
1
  class EvalResponseBuilder
2
2
  include Interactor
3
3
 
4
- delegate :request, to: :context
4
+ delegate :request, :params, to: :context
5
5
 
6
6
  def call
7
7
  v8_ctx = V8::Context.new timeout: timeout
@@ -12,9 +12,9 @@ class EvalResponseBuilder
12
12
 
13
13
  eval_response(v8_ctx)
14
14
  rescue => e
15
- context.built_response = { 'status' => 500,
15
+ context.built_response = { 'status' => 500,
16
16
  'headers' => { 'Content-Type' => 'text/plain' },
17
- 'body' => e.message }
17
+ 'body' => e.message }
18
18
  end
19
19
 
20
20
  private
@@ -28,19 +28,19 @@ class EvalResponseBuilder
28
28
 
29
29
  # filter allowed parameters
30
30
  context.built_response = JSON.parse(context.built_response).select do |key, value|
31
- %w(status headers body).include?(key) && value.to_s.present?
31
+ %w[status headers body].include?(key) && value.to_s.present?
32
32
  end
33
33
  end
34
34
 
35
35
  def initialize_response_builder_attrs(v8_ctx)
36
- v8_ctx['response'] = { 'status' => 200,
36
+ v8_ctx['response'] = { 'status' => 200,
37
37
  'headers' => {},
38
- 'body' => 'ok' }
38
+ 'body' => 'ok' }
39
39
 
40
40
  v8_ctx['request'] = { 'request_method' => request.request_method,
41
- 'body' => request.body,
42
- 'params' => request.params,
43
- 'headers' => request.headers }
41
+ 'body' => request.body,
42
+ 'params' => params,
43
+ 'headers' => request.headers }
44
44
  end
45
45
 
46
46
  def eval_response_builder(v8_ctx)
@@ -1,8 +1,8 @@
1
1
  class FilterHeaders
2
2
  include Interactor
3
3
 
4
- BLACKLIST_HEADERS = %w(HOST CF X-REQUEST X-FORWARDED CONNECT-TIME TOTAL-ROUTE-TIME VIA).freeze
5
- WHITELIST_HEADERS = %w(HTTP_ CONTENT).freeze
4
+ BLACKLIST_HEADERS = %w[HOST CF X-REQUEST X-FORWARDED CONNECT-TIME TOTAL-ROUTE-TIME VIA].freeze
5
+ WHITELIST_HEADERS = %w[HTTP_ CONTENT].freeze
6
6
 
7
7
  delegate :headers, to: :context
8
8
 
@@ -14,7 +14,7 @@ class FilterHeaders
14
14
 
15
15
  def client_supplied_headers
16
16
  headers.to_h.each_with_object({}) do |(key, value), h|
17
- next unless value.to_s.present?
17
+ next if value.to_s.blank?
18
18
 
19
19
  next unless key.upcase.start_with?(*WHITELIST_HEADERS)
20
20
 
@@ -9,9 +9,9 @@ class ForwardRequest
9
9
  # use the response from the forwarded URL
10
10
  context.built_response = forward_to(built_request, forward_url)
11
11
  rescue => e
12
- context.built_response = { 'status' => 500,
12
+ context.built_response = { 'status' => 500,
13
13
  'headers' => { 'Content-Type' => 'text/plain' },
14
- 'body' => e.message }
14
+ 'body' => e.message }
15
15
  end
16
16
 
17
17
  private
@@ -23,9 +23,9 @@ class ForwardRequest
23
23
 
24
24
  forwarded_response = HTTParty.send(built_request['request_method'].downcase.to_sym, forward_url, options)
25
25
 
26
- { 'status' => forwarded_response.code,
26
+ { 'status' => forwarded_response.code,
27
27
  'headers' => FilterHeaders.call(headers: forwarded_response.headers).headers,
28
- 'body' => forwarded_response.body }
28
+ 'body' => forwarded_response.body }
29
29
  end
30
30
 
31
31
  def body
@@ -1,5 +1,5 @@
1
1
  class RecordRequest
2
2
  include Interactor::Organizer
3
3
 
4
- organize CreateRequest, EvalResponseBuilder, ForwardRequest, CreateResponse, NotifyCount
4
+ organize CreateRequest, EvalResponseBuilder, ForwardRequest, CreateResponse, Track
5
5
  end
@@ -0,0 +1,44 @@
1
+ class Track
2
+ include Interactor
3
+
4
+ delegate :bucket, :rack_request, to: :context
5
+ delegate :token, to: :bucket
6
+ delegate :ip, :user_agent, to: :rack_request
7
+
8
+ def call
9
+ return unless enabled?
10
+
11
+ tracker.event(category: 'bucket', action: 'record', label: token, value: 1) # , non_interactive: true)
12
+ rescue => e
13
+ Rollbar.error(e, token: token)
14
+ end
15
+
16
+ private
17
+
18
+ def global_context
19
+ {
20
+ user_ip: ip,
21
+ ssl: true,
22
+ user_agent: user_agent
23
+ }
24
+ end
25
+
26
+ def client_id
27
+ # since there's no identification in the requests,
28
+ # the only way to aggregate requests per "user" is per ip
29
+ # https://stackoverflow.com/a/31854739/464685
30
+ ip
31
+ end
32
+
33
+ def enabled?
34
+ google_analytics_tracking_id.present?
35
+ end
36
+
37
+ def google_analytics_tracking_id
38
+ ENV['GOOGLE_ANALYTICS_TRACKING_ID']
39
+ end
40
+
41
+ def tracker
42
+ @_tracker ||= Staccato.tracker(google_analytics_tracking_id, client_id, global_context)
43
+ end
44
+ end
@@ -0,0 +1,7 @@
1
+ const bucketsActions = {
2
+ populate: 'POPULATE',
3
+ loading: 'LOADING',
4
+ updateRequestsCount: 'UPDATE_REQUESTS_COUNT'
5
+ }
6
+
7
+ export { bucketsActions }
@@ -0,0 +1,64 @@
1
+ import fetch from 'cross-fetch'
2
+ import { bucketsActions } from '../actionTypes'
3
+
4
+ const getJSONFromPage = id => JSON.parse(document.getElementById(id).innerText)
5
+
6
+ const fetchFromPage = () => {
7
+ const bucket = getJSONFromPage('bucket-json')
8
+ favicon.badge(bucket.requests_count)
9
+
10
+ return {
11
+ type: bucketsActions.populate,
12
+ bucket,
13
+ page: 1
14
+ }
15
+ }
16
+
17
+ const favicon = new Favico({ bgColor: '#6C92C8', animation: 'none' })
18
+
19
+ const updateRequestsCount = count => {
20
+ return (dispatch, getState) => {
21
+ if (count === getState().bucket.requests_count) {
22
+ return
23
+ }
24
+
25
+ favicon.badge(count)
26
+
27
+ let page = getState().bucket.page || 1
28
+
29
+ if (count > getState().bucket.request_count) {
30
+ page = count - getState().bucket.requests_count + page
31
+ }
32
+
33
+ dispatch({
34
+ type: bucketsActions.updateRequestsCount,
35
+ requests_count: count,
36
+ page: page
37
+ })
38
+
39
+ if (count === 1) {
40
+ return fetchPage(1)(dispatch, getState)
41
+ }
42
+ }
43
+ }
44
+
45
+ const handlePageChange = page => {
46
+ return (dispatch, getState) => {
47
+ dispatch({ type: bucketsActions.loading })
48
+
49
+ fetchPage(page)(dispatch, getState)
50
+ }
51
+ }
52
+
53
+ const fetchPage = page => {
54
+ return (dispatch, getState) => {
55
+ fetch(`${getState().bucket.path}.json?page=${page}`)
56
+ .then(
57
+ response => response.json(),
58
+ error => console.log('An error occurred.', error)
59
+ )
60
+ .then(bucket => dispatch({ type: bucketsActions.populate, bucket, page }))
61
+ }
62
+ }
63
+
64
+ export { fetchFromPage, handlePageChange, updateRequestsCount }
@@ -0,0 +1,91 @@
1
+ import React, { Component } from 'react'
2
+ import { connect } from 'react-redux'
3
+ import Request from './request'
4
+ import Pagination from './Pagination'
5
+ import { fetchFromPage, handlePageChange } from '../actions'
6
+ import PropTypes from 'prop-types'
7
+
8
+ class Bucket extends Component {
9
+ componentWillMount() {
10
+ this.props.fetchFromPage()
11
+ }
12
+
13
+ renderFirstRequestLink() {
14
+ if(!this.props.bucket.request) { return }
15
+
16
+ return (
17
+ <em>Request ID: <a href={this.props.bucket.request.path} target="_blank">{this.props.bucket.request.id}</a></em>
18
+ )
19
+ }
20
+
21
+ renderFirstRequest() {
22
+ if(!this.props.bucket.first_request) { return }
23
+
24
+ return (
25
+ <p>
26
+ <em>First request at: {this.props.bucket.first_request.created_at}</em> <br />
27
+ <em>Last request at: {this.props.bucket.last_request.created_at}</em> <br />
28
+
29
+ {this.renderFirstRequestLink()}
30
+ </p>
31
+ )
32
+ }
33
+
34
+ handlePageChange(page) {
35
+ this.props.handlePageChange(page)
36
+ }
37
+
38
+ renderPagination() {
39
+ if(!this.props.bucket.request) { return }
40
+
41
+ return (
42
+ <Pagination
43
+ pageCount={this.props.bucket.requests_count}
44
+ page={this.props.bucket.page}
45
+ onPageChange={this.handlePageChange.bind(this)}
46
+ />
47
+ )
48
+ }
49
+
50
+ renderRequestOrLoading() {
51
+ if(this.props.bucket.loading) {
52
+ return (
53
+ <div><em><strong>Loading...</strong></em></div>
54
+ )
55
+ }
56
+
57
+ return (<Request {...this.props.bucket.request} />)
58
+ }
59
+
60
+ render() {
61
+ if(!this.props.bucket) { return }
62
+
63
+ return (
64
+ <div>
65
+ <div className="row">
66
+ <div className="col-md-6">
67
+ {this.renderFirstRequest()}
68
+ </div>
69
+ <div className="col-md-6"></div>
70
+ </div>
71
+ {this.renderPagination()}
72
+ {this.renderRequestOrLoading()}
73
+ </div>
74
+ )
75
+ }
76
+ }
77
+
78
+ Bucket.propTypes = {
79
+ bucket: PropTypes.object.isRequired
80
+ }
81
+
82
+ const mapStateToProps = (state) => ({
83
+ bucket: state.bucket
84
+ })
85
+
86
+ const mapDispatchToProps = {
87
+ handlePageChange: handlePageChange,
88
+ fetchFromPage: fetchFromPage
89
+ }
90
+
91
+ export default connect(mapStateToProps, mapDispatchToProps)(Bucket)
@@ -0,0 +1,67 @@
1
+ import React, { Component } from 'react'
2
+ import PropTypes from 'prop-types'
3
+
4
+ export default class Pagination extends Component {
5
+ handleFirstPage() {
6
+ this.props.onPageChange(1)
7
+ }
8
+
9
+ handlePreviousPage() {
10
+ const previousPage = this.props.page - 1
11
+ if(previousPage > 0) { this.props.onPageChange(previousPage) }
12
+ }
13
+
14
+ handleNextPage() {
15
+ const nextPage = this.props.page + 1
16
+ if(nextPage <= this.props.pageCount) { this.props.onPageChange(nextPage) }
17
+ }
18
+
19
+ handleLastPage() {
20
+ this.props.onPageChange(this.props.pageCount)
21
+ }
22
+
23
+ firstClassNames() {
24
+ if(this.props.page <= 1) { return 'disabled' }
25
+ }
26
+
27
+ previousClassNames() {
28
+ const previousPage = this.props.page - 1
29
+
30
+ if(previousPage <= 0) { return 'disabled' }
31
+ }
32
+
33
+ nextClassNames() {
34
+ const nextPage = this.props.page + 1
35
+
36
+ if(nextPage > this.props.pageCount) { return 'disabled' }
37
+ }
38
+
39
+ lastClassNames() {
40
+ if(this.props.page >= this.props.pageCount) { return 'disabled' }
41
+ }
42
+
43
+ render() {
44
+ return(
45
+ <ul className="pagination">
46
+ <li className={this.firstClassNames()}>
47
+ <a onClick={this.handleFirstPage.bind(this)}>First</a>
48
+ </li>
49
+ <li className={this.previousClassNames()}>
50
+ <a onClick={this.handlePreviousPage.bind(this)}>Previous</a>
51
+ </li>
52
+ <li className={this.nextClassNames()}>
53
+ <a onClick={this.handleNextPage.bind(this)}>Next</a>
54
+ </li>
55
+ <li className={this.lastClassNames()}>
56
+ <a onClick={this.handleLastPage.bind(this)}>Last</a>
57
+ </li>
58
+ </ul>
59
+ )
60
+ }
61
+ }
62
+
63
+ Pagination.propTypes = {
64
+ page: PropTypes.number.isRequired,
65
+ pageCount: PropTypes.number.isRequired,
66
+ onPageChange: PropTypes.func.isRequired
67
+ }
@@ -0,0 +1,21 @@
1
+ import React, { Component } from 'react'
2
+ import { connect } from 'react-redux'
3
+ import PropTypes from 'prop-types'
4
+
5
+ class RequestCount extends Component {
6
+ render() {
7
+ return (
8
+ <h3>{this.props.requests_count || 0}</h3>
9
+ )
10
+ }
11
+ }
12
+
13
+ RequestCount.propTypes = {
14
+ requests_count: PropTypes.number
15
+ }
16
+
17
+ const mapStateToProps = (state) => ({
18
+ requests_count: state.bucket.requests_count
19
+ })
20
+
21
+ export default connect(mapStateToProps)(RequestCount)