putsreq 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
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)