apress-api 1.22.0

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 (94) hide show
  1. checksums.yaml +7 -0
  2. data/.drone.yml +30 -0
  3. data/.gitignore +15 -0
  4. data/.rspec +4 -0
  5. data/Appraisals +31 -0
  6. data/CHANGELOG.md +227 -0
  7. data/Gemfile +8 -0
  8. data/LICENSE.txt +22 -0
  9. data/README.md +31 -0
  10. data/Rakefile +2 -0
  11. data/app/controllers/apress/api/deprecated_versions_controller.rb +15 -0
  12. data/app/controllers/apress/api/v1/callbacks_controller.rb +30 -0
  13. data/app/controllers/apress/api/v1/tokens_controller.rb +24 -0
  14. data/app/docs/schema/api/v1/types/apress/api/link.rb +29 -0
  15. data/app/docs/schema/api/v1/types/apress/api/links.rb +24 -0
  16. data/app/docs/swagger/v1/controllers/apress/api/tokens_controller.rb +64 -0
  17. data/app/docs/swagger/v1/default_responses/bad_request.rb +16 -0
  18. data/app/docs/swagger/v1/default_responses/not_found.rb +16 -0
  19. data/app/docs/swagger/v1/default_responses/unauthenticated.rb +16 -0
  20. data/app/docs/swagger/v1/default_responses/unauthorized.rb +16 -0
  21. data/app/docs/swagger/v1/default_responses/unprocessable.rb +16 -0
  22. data/app/docs/swagger/v1/default_responses/updates_locked.rb +13 -0
  23. data/app/docs/swagger/v1/models/apress/api/client.rb +53 -0
  24. data/app/docs/swagger/v1/models/apress/api/simple_error.rb +27 -0
  25. data/app/docs/swagger/v1/models/apress/api/unproccesable_error.rb +16 -0
  26. data/app/docs/swagger/v1/models/apress/api/unprocessable_error.rb +37 -0
  27. data/app/docs/swagger/v1/root.rb +28 -0
  28. data/app/interactors/apress/api/callbacks/base_callback.rb +42 -0
  29. data/app/interactors/apress/api/delayed_fire_callback.rb +25 -0
  30. data/app/jobs/apress/api/event_handler_enqueueing_job.rb +19 -0
  31. data/app/jobs/apress/api/fire_callback_job.rb +25 -0
  32. data/app/models/apress/api/client.rb +60 -0
  33. data/app/policies/apress/api/callback_policy.rb +15 -0
  34. data/app/services/apress/api/auth_service.rb +37 -0
  35. data/app/views/apress/api/shared/_exception.json.jbuilder +4 -0
  36. data/app/views/apress/api/shared/error.json.jbuilder +5 -0
  37. data/app/views/apress/api/shared/parameter_missing_errors.json.jbuilder +9 -0
  38. data/app/views/apress/api/shared/unprocessable_errors.json.jbuilder +9 -0
  39. data/app/views/apress/api/v1/clients/_client.json.jbuilder +2 -0
  40. data/app/views/apress/api/v1/tokens/create.json.jbuilder +4 -0
  41. data/apress-api.gemspec +46 -0
  42. data/config/routes.rb +17 -0
  43. data/db/migrate/20150716000000_create_api_clients.rb +38 -0
  44. data/dip.yml +48 -0
  45. data/docker-compose.development.yml +18 -0
  46. data/docker-compose.drone.yml +7 -0
  47. data/docker-compose.yml +20 -0
  48. data/lib/apress-api.rb +1 -0
  49. data/lib/apress/api.rb +25 -0
  50. data/lib/apress/api/api_controller/authentification.rb +33 -0
  51. data/lib/apress/api/api_controller/base.rb +63 -0
  52. data/lib/apress/api/api_controller/compatibility.rb +26 -0
  53. data/lib/apress/api/api_controller/pagination.rb +60 -0
  54. data/lib/apress/api/api_controller/pagination_helper.rb +55 -0
  55. data/lib/apress/api/api_controller/rescue.rb +89 -0
  56. data/lib/apress/api/api_controller/responds.rb +25 -0
  57. data/lib/apress/api/callbacks/config.rb +56 -0
  58. data/lib/apress/api/callbacks/fire_callback_error.rb +12 -0
  59. data/lib/apress/api/callbacks/integration.rb +28 -0
  60. data/lib/apress/api/callbacks/repeat_callback_error.rb +12 -0
  61. data/lib/apress/api/engine.rb +33 -0
  62. data/lib/apress/api/extensions/jbuilder/jbuilder_template.rb +41 -0
  63. data/lib/apress/api/rspec.rb +48 -0
  64. data/lib/apress/api/rspec/utils.rb +17 -0
  65. data/lib/apress/api/testing/json_matcher.rb +9 -0
  66. data/lib/apress/api/version.rb +5 -0
  67. data/lib/tasks/docs.rake +12 -0
  68. data/spec/controllers/api_controller/authentification_spec.rb +79 -0
  69. data/spec/controllers/api_controller/pagination_spec.rb +199 -0
  70. data/spec/controllers/api_controller/rescue_spec.rb +167 -0
  71. data/spec/controllers/deprecated_versions_controller_spec.rb +10 -0
  72. data/spec/controllers/v1/callbacks_controller_spec.rb +50 -0
  73. data/spec/controllers/v1/tokens_controller_spec.rb +53 -0
  74. data/spec/factories/client_factory.rb +4 -0
  75. data/spec/helpers/paginating_cache_spec.rb +72 -0
  76. data/spec/interactors/apress/api/delayed_fire_callback_spec.rb +43 -0
  77. data/spec/internal/app/integrations/error_client/fire_callback.rb +14 -0
  78. data/spec/internal/app/integrations/service_client/fire_callback.rb +7 -0
  79. data/spec/internal/app/jobs/handler_job.rb +5 -0
  80. data/spec/internal/app/jobs/second_handler_job.rb +5 -0
  81. data/spec/internal/app/models/dummy_model.rb +15 -0
  82. data/spec/internal/config/database.yml +5 -0
  83. data/spec/internal/config/environments/test.rb +5 -0
  84. data/spec/internal/config/initializers/api.rb +10 -0
  85. data/spec/internal/config/routes.rb +3 -0
  86. data/spec/internal/db/schema.rb +5 -0
  87. data/spec/internal/log/.gitignore +1 -0
  88. data/spec/jobs/apress/api/event_handler_equeueing_job_spec.rb +31 -0
  89. data/spec/jobs/apress/api/fire_callback_job_spec.rb +34 -0
  90. data/spec/lib/apress/api/callbacks/integration_spec.rb +24 -0
  91. data/spec/models/client_spec.rb +25 -0
  92. data/spec/services/auth_service_spec.rb +64 -0
  93. data/spec/spec_helper.rb +34 -0
  94. metadata +518 -0
@@ -0,0 +1,33 @@
1
+ module Apress
2
+ module Api
3
+ module ApiController
4
+ module Authentification
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ attr_reader :current_api_client
9
+
10
+ if (Rails::VERSION::MAJOR == 4 && Rails::VERSION::MINOR == 2) || Rails::VERSION::MAJOR > 4
11
+ before_action :find_session
12
+ before_action :authenticate
13
+ else
14
+ before_filter :find_session
15
+ before_filter :authenticate
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ def find_session
22
+ auth_service = AuthService.new(request)
23
+ return unless auth_service.call
24
+ @current_api_client = auth_service.client
25
+ end
26
+
27
+ def authenticate
28
+ forbidden unless @current_api_client
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,63 @@
1
+ module Apress
2
+ module Api
3
+ module ApiController
4
+ class Base < ActionController::Metal
5
+ abstract!
6
+
7
+ if (Rails::VERSION::MAJOR == 4 && Rails::VERSION::MINOR > 0) || Rails::VERSION::MAJOR > 4
8
+ include AbstractController::Rendering
9
+ include ActionView::Rendering
10
+ end
11
+ include ActionController::HideActions if Rails::VERSION::MAJOR < 5
12
+ include ActionController::UrlFor
13
+ include ActionController::Redirecting
14
+ include ActionController::Rendering
15
+ include ActionController::Renderers::All
16
+ include ActionController::ConditionalGet
17
+ include ActionController::RackDelegation if Rails::VERSION::MAJOR < 5
18
+ include ActionController::ForceSSL
19
+ include AbstractController::Callbacks
20
+ include ActionController::Rescue
21
+ include ActionController::Instrumentation
22
+ include ActionController::StrongParameters if Rails::VERSION::MAJOR >= 4
23
+ include ActionController::MimeResponds
24
+ include ActionController::ImplicitRender
25
+ include ActionController::Helpers
26
+ include ActionController::Caching
27
+ include AbstractController::AssetPaths
28
+
29
+ # https://github.com/rails/strong_parameters/pull/199
30
+ if Rails::VERSION::MAJOR == 3
31
+ require "strong_parameters"
32
+ require "strong_parameters/version"
33
+ end
34
+
35
+ if defined?(StrongParameters::VERSION) &&
36
+ Gem::Version.new(StrongParameters::VERSION) <= Gem::Version.new("0.2.3")
37
+ include ActionController::StrongParameters
38
+ end
39
+
40
+ # :nocov:
41
+ if defined?(::NewRelic)
42
+ require "new_relic/agent/instrumentation/rails#{Rails::VERSION::MAJOR}/action_controller"
43
+
44
+ include ::NewRelic::Agent::Instrumentation::ControllerInstrumentation
45
+
46
+ if Rails::VERSION::MAJOR == 3
47
+ include ::NewRelic::Agent::Instrumentation::Rails3::ActionController
48
+ end
49
+ end
50
+ # :nocov:
51
+
52
+ extend Compatibility
53
+ include Rescue
54
+ include Responds
55
+ include Authentification
56
+ include Pagination
57
+
58
+ ActiveSupport.run_load_hooks(:action_controller, self)
59
+ ActiveSupport.run_load_hooks(:"apress/api/api_controller", self)
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,26 @@
1
+ module Apress
2
+ module Api
3
+ module ApiController
4
+ module Compatibility
5
+ def assets_dir=(*); end
6
+
7
+ def javascripts_dir=(*); end
8
+
9
+ def stylesheets_dir=(*); end
10
+
11
+ def page_cache_directory=(*); end
12
+
13
+ def relative_url_root=(*); end
14
+
15
+ def helpers_path=(*); end
16
+
17
+ def allow_forgery_protection=(*); end
18
+
19
+ def helper(*); end
20
+
21
+ def include_all_helpers=(*); end
22
+ # :nocov:
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,60 @@
1
+ require 'addressable/uri'
2
+ require 'apress/api/api_controller/pagination_helper'
3
+ module Apress
4
+ module Api
5
+ module ApiController
6
+ module Pagination
7
+ class InvalidPaginationError < StandardError; end
8
+ class LinkHeaderAppendNotImplemented < StandardError; end
9
+
10
+ extend ActiveSupport::Concern
11
+
12
+ included do
13
+ attr_reader :per_page, :page
14
+ alias_method :current_page, :page
15
+ rescue_from InvalidPaginationError, with: :bad_request
16
+ end
17
+
18
+ DEFAULT_PER_PAGE = 30
19
+ MAX_PER_PAGE = 100
20
+
21
+ # Public: sets page and per_page variables
22
+ #
23
+ # options - Hash, configuration
24
+ # :per_page - Hash, per_page value configuration
25
+ # :max - Integer, max per_page value
26
+ # :defuault - Integer, default value for per_page
27
+ # Examples
28
+ # prepare_pagination(per_page: {max: 110, default: 40})
29
+ #
30
+ # or with defaults max - 100 and default - 30
31
+ #
32
+ # prepare_pagination
33
+ #
34
+ # Returns nothing
35
+ def prepare_pagination(options = {})
36
+ per_page = options.fetch(:per_page, {})
37
+ max_per_page = per_page.fetch(:max, MAX_PER_PAGE)
38
+ default_per_page = per_page.fetch(:default, DEFAULT_PER_PAGE)
39
+
40
+ @page = params.fetch(:page, 1).to_i
41
+ raise InvalidPaginationError if @page <= 0
42
+
43
+ @per_page = params.fetch(:per_page, default_per_page).to_i
44
+ raise InvalidPaginationError unless (1..max_per_page).include?(@per_page)
45
+ end
46
+
47
+ # Public: sets pagination headers
48
+ #
49
+ # collection - WillPaginate::Collection
50
+ #
51
+ # Returns nothing
52
+ def pagination_headers(collection)
53
+ raise LinkHeaderAppendNotImplemented if headers['Link'].present?
54
+
55
+ headers.merge!(::Apress::Api::ApiController::PaginationHelper.headers(collection, request.url))
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,55 @@
1
+ module Apress
2
+ module Api
3
+ module ApiController
4
+ module PaginationHelper
5
+ def self.headers(collection, url)
6
+ result = {}
7
+
8
+ result['X-Total-Count'] = collection.total_entries.to_s
9
+ result['X-Total-Pages'] = collection.total_pages.to_s
10
+ result['X-Per-Page'] = collection.per_page.to_s
11
+ result['X-Page'] = collection.current_page.to_s
12
+
13
+ numbers = page_numbers(collection)
14
+ links = header_link_values(url, numbers)
15
+
16
+ result['Link'] = links.join(', ') if links.present?
17
+
18
+ result
19
+ end
20
+
21
+ def self.header_link_values(url, page_numbers)
22
+ url = ::Addressable::URI.parse(url)
23
+ url.query_values ||= {}
24
+
25
+ page_numbers.map do |rel, number|
26
+ url.query_values = url.query_values.merge('page' => number)
27
+
28
+ %(<#{url}>; rel="#{rel}")
29
+ end
30
+ rescue ::Addressable::URI::InvalidURIError
31
+ []
32
+ end
33
+
34
+ def self.page_numbers(collection)
35
+ result = {}
36
+ return result unless collection.total_pages > 1
37
+
38
+ if collection.current_page > 1
39
+ result[:first] = 1
40
+ result[:prev] = collection.current_page - 1
41
+ end
42
+
43
+ if collection.current_page < collection.total_pages
44
+ result[:last] = collection.total_pages
45
+ result[:next] = collection.current_page + 1
46
+ end
47
+
48
+ result
49
+ end
50
+
51
+ private_class_method :header_link_values, :page_numbers
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,89 @@
1
+ module Apress
2
+ module Api
3
+ module ApiController
4
+ module Rescue
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ rescue_from "Exception", with: :server_error if Rails.env.production?
9
+
10
+ rescue_from "Pundit::NotAuthorizedError", with: :forbidden
11
+
12
+ rescue_from "ActiveRecord::RecordNotFound",
13
+ "ActionView::MissingTemplate",
14
+ "ActionController::UnknownFormat",
15
+ "ActiveRecord::SubclassNotFound",
16
+ with: :not_found unless Rails.env.development?
17
+
18
+ rescue_from 'ActiveRecord::RecordInvalid', with: :unprocessable
19
+
20
+ rescue_from 'ActionController::ParameterMissing',
21
+ with: :parameter_missing
22
+
23
+ helper_method :show_errors?
24
+ end
25
+
26
+ private
27
+
28
+ def server_error(exception)
29
+ render_error(500, exception)
30
+ end
31
+
32
+ def not_found(exception = nil)
33
+ render_error(404, exception)
34
+ end
35
+
36
+ def forbidden(exception = nil)
37
+ render_error(403, exception)
38
+ end
39
+
40
+ def bad_request(exception = nil)
41
+ render_error(400, exception)
42
+ end
43
+
44
+ def unprocessable(exception_or_errors)
45
+ @status = 422
46
+
47
+ @errors =
48
+ if exception_or_errors.respond_to?(:record)
49
+ exception_or_errors.record.errors
50
+ elsif exception_or_errors.is_a?(StandardError)
51
+ {message: exception_or_errors.message}
52
+ else
53
+ exception_or_errors
54
+ end
55
+ @errors = Array.wrap(@errors)
56
+ render 'apress/api/shared/unprocessable_errors', status: @status
57
+ end
58
+
59
+ def parameter_missing(exception)
60
+ @status = 400
61
+ @errors = [{exception.param => 'missing'}]
62
+ render 'apress/api/shared/parameter_missing_errors', status: @status
63
+ end
64
+
65
+ def render_error(status, exception = nil)
66
+ @status = status
67
+ @exception = exception
68
+
69
+ log_error(exception) if exception
70
+
71
+ render "apress/api/shared/error", status: status
72
+ end
73
+
74
+ def log_error(exception)
75
+ Rails.logger.error(exception.message)
76
+ Rails.logger.error(exception.backtrace.join("/n")) if exception.backtrace.present?
77
+ end
78
+
79
+ def show_errors?
80
+ soft_environment?
81
+ end
82
+
83
+ def soft_environment?
84
+ Rails.env.staging? || !Rails.env.production?
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,25 @@
1
+ module Apress
2
+ module Api
3
+ module ApiController
4
+ module Responds
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ if (Rails::VERSION::MAJOR == 4 && Rails::VERSION::MINOR == 2) || Rails::VERSION::MAJOR > 4
9
+ before_action :set_json_format
10
+ else
11
+ before_filter :set_json_format
12
+ end
13
+ end
14
+
15
+ private
16
+
17
+ # :nocov:
18
+ def set_json_format
19
+ request.format = :json
20
+ end
21
+ # :nocov:
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,56 @@
1
+ require 'singleton'
2
+
3
+ module Apress
4
+ module Api
5
+ module Callbacks
6
+ class Config
7
+ include Singleton
8
+
9
+ class << self
10
+ delegate :add_service, :services, to: :instance
11
+ delegate :allowed_client?, :add_client, to: :instance
12
+ delegate :add_handler, :handlers, to: :instance
13
+ delegate :valid_event?, to: :instance
14
+ end
15
+
16
+ def add_service(event:, service:)
17
+ events[event] << service
18
+ end
19
+
20
+ def add_handler(service:, event:, handler:)
21
+ (handlers_config[service][event] ||= []) << handler
22
+ end
23
+
24
+ def add_client(access_id)
25
+ clients << access_id
26
+ end
27
+
28
+ def allowed_client?(client)
29
+ clients.include?(client.access_id)
30
+ end
31
+
32
+ def services(event)
33
+ events[event]
34
+ end
35
+
36
+ def handlers(service:, event:)
37
+ handlers_config.fetch(service).fetch(event)
38
+ end
39
+
40
+ private
41
+
42
+ def handlers_config
43
+ @handlers_config ||= ::Hash.new { |hash, key| hash[key] = {} }
44
+ end
45
+
46
+ def events
47
+ @events ||= ::Hash.new { |hash, key| hash[key] = [] }
48
+ end
49
+
50
+ def clients
51
+ @clients ||= Set.new
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,12 @@
1
+ module Apress
2
+ module Api
3
+ module Callbacks
4
+ class FireCallbackError < StandardError
5
+ def initialize(message, backtrace)
6
+ super(message)
7
+ set_backtrace(backtrace)
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,28 @@
1
+ module Apress
2
+ module Api
3
+ module Callbacks
4
+ module Integration
5
+ extend ActiveSupport::Concern
6
+
7
+ module ClassMethods
8
+ def notify_services(event:, params: {}, at:)
9
+ at = Array.wrap(at)
10
+ callback = at.shift
11
+
12
+ public_send(callback, *at) do
13
+ hash_params =
14
+ if params.respond_to?(:call)
15
+ params.call(self)
16
+ else
17
+ params.each_with_object({}) do |param, hash|
18
+ hash[param] = public_send(param)
19
+ end
20
+ end
21
+ ::Apress::Api::DelayedFireCallback.call!(event: event, params: hash_params)
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end