apress-api 1.22.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.drone.yml +30 -0
- data/.gitignore +15 -0
- data/.rspec +4 -0
- data/Appraisals +31 -0
- data/CHANGELOG.md +227 -0
- data/Gemfile +8 -0
- data/LICENSE.txt +22 -0
- data/README.md +31 -0
- data/Rakefile +2 -0
- data/app/controllers/apress/api/deprecated_versions_controller.rb +15 -0
- data/app/controllers/apress/api/v1/callbacks_controller.rb +30 -0
- data/app/controllers/apress/api/v1/tokens_controller.rb +24 -0
- data/app/docs/schema/api/v1/types/apress/api/link.rb +29 -0
- data/app/docs/schema/api/v1/types/apress/api/links.rb +24 -0
- data/app/docs/swagger/v1/controllers/apress/api/tokens_controller.rb +64 -0
- data/app/docs/swagger/v1/default_responses/bad_request.rb +16 -0
- data/app/docs/swagger/v1/default_responses/not_found.rb +16 -0
- data/app/docs/swagger/v1/default_responses/unauthenticated.rb +16 -0
- data/app/docs/swagger/v1/default_responses/unauthorized.rb +16 -0
- data/app/docs/swagger/v1/default_responses/unprocessable.rb +16 -0
- data/app/docs/swagger/v1/default_responses/updates_locked.rb +13 -0
- data/app/docs/swagger/v1/models/apress/api/client.rb +53 -0
- data/app/docs/swagger/v1/models/apress/api/simple_error.rb +27 -0
- data/app/docs/swagger/v1/models/apress/api/unproccesable_error.rb +16 -0
- data/app/docs/swagger/v1/models/apress/api/unprocessable_error.rb +37 -0
- data/app/docs/swagger/v1/root.rb +28 -0
- data/app/interactors/apress/api/callbacks/base_callback.rb +42 -0
- data/app/interactors/apress/api/delayed_fire_callback.rb +25 -0
- data/app/jobs/apress/api/event_handler_enqueueing_job.rb +19 -0
- data/app/jobs/apress/api/fire_callback_job.rb +25 -0
- data/app/models/apress/api/client.rb +60 -0
- data/app/policies/apress/api/callback_policy.rb +15 -0
- data/app/services/apress/api/auth_service.rb +37 -0
- data/app/views/apress/api/shared/_exception.json.jbuilder +4 -0
- data/app/views/apress/api/shared/error.json.jbuilder +5 -0
- data/app/views/apress/api/shared/parameter_missing_errors.json.jbuilder +9 -0
- data/app/views/apress/api/shared/unprocessable_errors.json.jbuilder +9 -0
- data/app/views/apress/api/v1/clients/_client.json.jbuilder +2 -0
- data/app/views/apress/api/v1/tokens/create.json.jbuilder +4 -0
- data/apress-api.gemspec +46 -0
- data/config/routes.rb +17 -0
- data/db/migrate/20150716000000_create_api_clients.rb +38 -0
- data/dip.yml +48 -0
- data/docker-compose.development.yml +18 -0
- data/docker-compose.drone.yml +7 -0
- data/docker-compose.yml +20 -0
- data/lib/apress-api.rb +1 -0
- data/lib/apress/api.rb +25 -0
- data/lib/apress/api/api_controller/authentification.rb +33 -0
- data/lib/apress/api/api_controller/base.rb +63 -0
- data/lib/apress/api/api_controller/compatibility.rb +26 -0
- data/lib/apress/api/api_controller/pagination.rb +60 -0
- data/lib/apress/api/api_controller/pagination_helper.rb +55 -0
- data/lib/apress/api/api_controller/rescue.rb +89 -0
- data/lib/apress/api/api_controller/responds.rb +25 -0
- data/lib/apress/api/callbacks/config.rb +56 -0
- data/lib/apress/api/callbacks/fire_callback_error.rb +12 -0
- data/lib/apress/api/callbacks/integration.rb +28 -0
- data/lib/apress/api/callbacks/repeat_callback_error.rb +12 -0
- data/lib/apress/api/engine.rb +33 -0
- data/lib/apress/api/extensions/jbuilder/jbuilder_template.rb +41 -0
- data/lib/apress/api/rspec.rb +48 -0
- data/lib/apress/api/rspec/utils.rb +17 -0
- data/lib/apress/api/testing/json_matcher.rb +9 -0
- data/lib/apress/api/version.rb +5 -0
- data/lib/tasks/docs.rake +12 -0
- data/spec/controllers/api_controller/authentification_spec.rb +79 -0
- data/spec/controllers/api_controller/pagination_spec.rb +199 -0
- data/spec/controllers/api_controller/rescue_spec.rb +167 -0
- data/spec/controllers/deprecated_versions_controller_spec.rb +10 -0
- data/spec/controllers/v1/callbacks_controller_spec.rb +50 -0
- data/spec/controllers/v1/tokens_controller_spec.rb +53 -0
- data/spec/factories/client_factory.rb +4 -0
- data/spec/helpers/paginating_cache_spec.rb +72 -0
- data/spec/interactors/apress/api/delayed_fire_callback_spec.rb +43 -0
- data/spec/internal/app/integrations/error_client/fire_callback.rb +14 -0
- data/spec/internal/app/integrations/service_client/fire_callback.rb +7 -0
- data/spec/internal/app/jobs/handler_job.rb +5 -0
- data/spec/internal/app/jobs/second_handler_job.rb +5 -0
- data/spec/internal/app/models/dummy_model.rb +15 -0
- data/spec/internal/config/database.yml +5 -0
- data/spec/internal/config/environments/test.rb +5 -0
- data/spec/internal/config/initializers/api.rb +10 -0
- data/spec/internal/config/routes.rb +3 -0
- data/spec/internal/db/schema.rb +5 -0
- data/spec/internal/log/.gitignore +1 -0
- data/spec/jobs/apress/api/event_handler_equeueing_job_spec.rb +31 -0
- data/spec/jobs/apress/api/fire_callback_job_spec.rb +34 -0
- data/spec/lib/apress/api/callbacks/integration_spec.rb +24 -0
- data/spec/models/client_spec.rb +25 -0
- data/spec/services/auth_service_spec.rb +64 -0
- data/spec/spec_helper.rb +34 -0
- 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,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
|