apress-api 1.22.0

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