apes 1.0.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.
- checksums.yaml +7 -0
- data/.gitignore +7 -0
- data/.rubocop.yml +82 -0
- data/.travis-gemfile +15 -0
- data/.travis.yml +15 -0
- data/.yardopts +1 -0
- data/CHANGELOG.md +3 -0
- data/Gemfile +22 -0
- data/README.md +177 -0
- data/Rakefile +44 -0
- data/apes.gemspec +34 -0
- data/doc/Apes.html +130 -0
- data/doc/Apes/Concerns.html +127 -0
- data/doc/Apes/Concerns/Errors.html +1089 -0
- data/doc/Apes/Concerns/Pagination.html +636 -0
- data/doc/Apes/Concerns/Request.html +766 -0
- data/doc/Apes/Concerns/Response.html +940 -0
- data/doc/Apes/Controller.html +1100 -0
- data/doc/Apes/Errors.html +125 -0
- data/doc/Apes/Errors/AuthenticationError.html +133 -0
- data/doc/Apes/Errors/BadRequestError.html +157 -0
- data/doc/Apes/Errors/BaseError.html +320 -0
- data/doc/Apes/Errors/InvalidDataError.html +157 -0
- data/doc/Apes/Errors/MissingDataError.html +157 -0
- data/doc/Apes/Model.html +378 -0
- data/doc/Apes/PaginationCursor.html +2138 -0
- data/doc/Apes/RuntimeConfiguration.html +909 -0
- data/doc/Apes/Serializers.html +125 -0
- data/doc/Apes/Serializers/JSON.html +389 -0
- data/doc/Apes/Serializers/JWT.html +452 -0
- data/doc/Apes/Serializers/List.html +347 -0
- data/doc/Apes/UrlsParser.html +1432 -0
- data/doc/Apes/Validators.html +125 -0
- data/doc/Apes/Validators/BaseValidator.html +278 -0
- data/doc/Apes/Validators/BooleanValidator.html +494 -0
- data/doc/Apes/Validators/EmailValidator.html +350 -0
- data/doc/Apes/Validators/PhoneValidator.html +375 -0
- data/doc/Apes/Validators/ReferenceValidator.html +372 -0
- data/doc/Apes/Validators/TimestampValidator.html +640 -0
- data/doc/Apes/Validators/UuidValidator.html +372 -0
- data/doc/Apes/Validators/ZipCodeValidator.html +372 -0
- data/doc/Apes/Version.html +189 -0
- data/doc/ApplicationController.html +547 -0
- data/doc/Concerns.html +128 -0
- data/doc/Concerns/ErrorHandling.html +826 -0
- data/doc/Concerns/PaginationHandling.html +463 -0
- data/doc/Concerns/RequestHandling.html +512 -0
- data/doc/Concerns/ResponseHandling.html +579 -0
- data/doc/Errors.html +126 -0
- data/doc/Errors/AuthenticationError.html +123 -0
- data/doc/Errors/BadRequestError.html +147 -0
- data/doc/Errors/BaseError.html +289 -0
- data/doc/Errors/InvalidDataError.html +147 -0
- data/doc/Errors/MissingDataError.html +147 -0
- data/doc/Model.html +315 -0
- data/doc/PaginationCursor.html +764 -0
- data/doc/Serializers.html +126 -0
- data/doc/Serializers/JSON.html +253 -0
- data/doc/Serializers/JWT.html +253 -0
- data/doc/Serializers/List.html +245 -0
- data/doc/Validators.html +126 -0
- data/doc/Validators/BaseValidator.html +209 -0
- data/doc/Validators/BooleanValidator.html +391 -0
- data/doc/Validators/EmailValidator.html +298 -0
- data/doc/Validators/PhoneValidator.html +313 -0
- data/doc/Validators/ReferenceValidator.html +284 -0
- data/doc/Validators/TimestampValidator.html +476 -0
- data/doc/Validators/UuidValidator.html +310 -0
- data/doc/Validators/ZipCodeValidator.html +310 -0
- data/doc/_index.html +435 -0
- data/doc/class_list.html +58 -0
- data/doc/css/common.css +1 -0
- data/doc/css/full_list.css +57 -0
- data/doc/css/style.css +339 -0
- data/doc/file.README.html +252 -0
- data/doc/file_list.html +60 -0
- data/doc/frames.html +26 -0
- data/doc/index.html +252 -0
- data/doc/js/app.js +219 -0
- data/doc/js/full_list.js +181 -0
- data/doc/js/jquery.js +4 -0
- data/doc/method_list.html +615 -0
- data/doc/top-level-namespace.html +112 -0
- data/lib/apes.rb +40 -0
- data/lib/apes/concerns/errors.rb +111 -0
- data/lib/apes/concerns/pagination.rb +81 -0
- data/lib/apes/concerns/request.rb +237 -0
- data/lib/apes/concerns/response.rb +74 -0
- data/lib/apes/controller.rb +77 -0
- data/lib/apes/errors.rb +38 -0
- data/lib/apes/model.rb +94 -0
- data/lib/apes/pagination_cursor.rb +152 -0
- data/lib/apes/runtime_configuration.rb +80 -0
- data/lib/apes/serializers.rb +88 -0
- data/lib/apes/urls_parser.rb +233 -0
- data/lib/apes/validators.rb +234 -0
- data/lib/apes/version.rb +24 -0
- data/spec/apes/concerns/errors_spec.rb +141 -0
- data/spec/apes/concerns/pagination_spec.rb +114 -0
- data/spec/apes/concerns/request_spec.rb +244 -0
- data/spec/apes/concerns/response_spec.rb +79 -0
- data/spec/apes/controller_spec.rb +54 -0
- data/spec/apes/errors_spec.rb +14 -0
- data/spec/apes/models_spec.rb +148 -0
- data/spec/apes/pagination_cursor_spec.rb +113 -0
- data/spec/apes/runtime_configuration_spec.rb +100 -0
- data/spec/apes/serializers_spec.rb +70 -0
- data/spec/apes/urls_parser_spec.rb +150 -0
- data/spec/apes/validators_spec.rb +237 -0
- data/spec/spec_helper.rb +30 -0
- data/views/_included.json.jbuilder +9 -0
- data/views/_pagination.json.jbuilder +9 -0
- data/views/collection.json.jbuilder +4 -0
- data/views/errors/400.json.jbuilder +9 -0
- data/views/errors/403.json.jbuilder +7 -0
- data/views/errors/404.json.jbuilder +6 -0
- data/views/errors/422.json.jbuilder +19 -0
- data/views/errors/500.json.jbuilder +12 -0
- data/views/errors/501.json.jbuilder +7 -0
- data/views/layouts/general.json.jbuilder +36 -0
- data/views/object.json.jbuilder +4 -0
- metadata +262 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
#
|
|
2
|
+
# This file is part of the apes gem. Copyright (C) 2016 and above Shogun <shogun@cowtech.it>.
|
|
3
|
+
# Licensed under the MIT license, which can be found at http://www.opensource.org/licenses/mit-license.php.
|
|
4
|
+
#
|
|
5
|
+
|
|
6
|
+
module Apes
|
|
7
|
+
module Concerns
|
|
8
|
+
# JSON API response handling module.
|
|
9
|
+
module Response
|
|
10
|
+
attr_accessor :included
|
|
11
|
+
|
|
12
|
+
# Returns the template to use to render a object.
|
|
13
|
+
#
|
|
14
|
+
# @param object [Object] The object to render.
|
|
15
|
+
# @return [String] The template to use.
|
|
16
|
+
def response_template_for(object)
|
|
17
|
+
return @object_template if @object_template
|
|
18
|
+
object = object.first if object.respond_to?(:first)
|
|
19
|
+
object.class.name.underscore.gsub("/", "_")
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Returns the metadata for the current response.
|
|
23
|
+
#
|
|
24
|
+
# @param default [Object] Fallback data if nothing is found.
|
|
25
|
+
# @return [HashWithIndifferentAccess|Object|Nil] The metadata for the current response.
|
|
26
|
+
def response_meta(default = nil)
|
|
27
|
+
@meta || default || HashWithIndifferentAccess.new
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Returns the data for the current response.
|
|
31
|
+
#
|
|
32
|
+
# @param default [Object] Fallback data if nothing is found.
|
|
33
|
+
# @return [HashWithIndifferentAccess|Object|Nil] The data for the current response.
|
|
34
|
+
def response_data(default = nil)
|
|
35
|
+
@data || default || HashWithIndifferentAccess.new
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Returns the linked objects for the current response.
|
|
39
|
+
#
|
|
40
|
+
# @param default [Object] Fallback data if nothing is found.
|
|
41
|
+
# @return [HashWithIndifferentAccess|Object|Nil] The linked objects for the current response.
|
|
42
|
+
def response_links(default = nil)
|
|
43
|
+
@links || default || HashWithIndifferentAccess.new
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Returns the included (side-loaded) objects for the current response.
|
|
47
|
+
#
|
|
48
|
+
# @param default [Object] Fallback data if nothing is found.
|
|
49
|
+
# @return [HashWithIndifferentAccess|Object|Nil] The included objects for the current response.
|
|
50
|
+
def response_included(default = nil)
|
|
51
|
+
controller.included || default || HashWithIndifferentAccess.new
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Adds an object to the included (side-load) set.
|
|
55
|
+
#
|
|
56
|
+
# @param object [Object] The object to include.
|
|
57
|
+
# @param template [String] The template to use for rendering.
|
|
58
|
+
# @return [HashWithIndifferentAccess] A hash of objects to include. Keys are a template:id formatted strings, values are `[object, template]` pairs.
|
|
59
|
+
def response_include(object, template = nil)
|
|
60
|
+
controller.included ||= HashWithIndifferentAccess.new
|
|
61
|
+
controller.included[sprintf("%s:%s", response_template_for(object), object.to_param)] = [object, template]
|
|
62
|
+
controller.included
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Serializes a timestamp.
|
|
66
|
+
#
|
|
67
|
+
# @param timestamp [DateTime] The timestamp to serialize.
|
|
68
|
+
# @return [String] The serialized timestamp.
|
|
69
|
+
def response_timestamp(timestamp)
|
|
70
|
+
timestamp.safe_send(:strftime, "%FT%T.%L%z")
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
#
|
|
2
|
+
# This file is part of the apes gem. Copyright (C) 2016 and above Shogun <shogun@cowtech.it>.
|
|
3
|
+
# Licensed under the MIT license, which can be found at http://www.opensource.org/licenses/mit-license.php.
|
|
4
|
+
#
|
|
5
|
+
|
|
6
|
+
module Apes
|
|
7
|
+
# A ready to use controller for JSON API applications.
|
|
8
|
+
#
|
|
9
|
+
# @attribute [r] current_account
|
|
10
|
+
# @return [Object] The current account making the request
|
|
11
|
+
# @attribute [r] cursor
|
|
12
|
+
# @return [Apes::PaginationCursor] The pagination cursor for this request.
|
|
13
|
+
# @attribute [r] request_cursor
|
|
14
|
+
# @return [Apes::PaginationCursor] The original pagination cursor sent from the client.
|
|
15
|
+
class Controller < ActionController::API
|
|
16
|
+
include ActionController::ImplicitRender
|
|
17
|
+
include ActionView::Layouts
|
|
18
|
+
include Apes::Concerns::Request
|
|
19
|
+
include Apes::Concerns::Response
|
|
20
|
+
include Apes::Concerns::Pagination
|
|
21
|
+
include Apes::Concerns::Errors
|
|
22
|
+
|
|
23
|
+
layout "general"
|
|
24
|
+
before_filter :request_handle_cors
|
|
25
|
+
before_filter :request_validate
|
|
26
|
+
|
|
27
|
+
attr_reader :current_account, :cursor, :request_cursor
|
|
28
|
+
|
|
29
|
+
# Exception handling
|
|
30
|
+
rescue_from Exception, with: :error_handle_exception
|
|
31
|
+
# This allows to avoid to declare all the views
|
|
32
|
+
rescue_from ActionView::MissingTemplate, with: :render_default_views
|
|
33
|
+
|
|
34
|
+
# Returns the default URL options for this request.
|
|
35
|
+
# It ensures that the host is always included and that is set properly in development mode.
|
|
36
|
+
#
|
|
37
|
+
# @return [Hash] Default URL options for the request.
|
|
38
|
+
def default_url_options
|
|
39
|
+
rv = {only_path: false}
|
|
40
|
+
rv = {host: request_source_host} if Apes::RuntimeConfiguration.development?
|
|
41
|
+
rv
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Tiny handle to handle CORS OPTIONS requests. It just renders nothing as headers are handle in Apes::Concerns::Response module.
|
|
45
|
+
#
|
|
46
|
+
# To enable this route, add the following to the routes.rb:
|
|
47
|
+
#
|
|
48
|
+
# # This is to enable AJAX cross domain
|
|
49
|
+
# match '*path', to: 'application#handle_cors', via: :options
|
|
50
|
+
def handle_cors
|
|
51
|
+
render(nothing: true, status: :no_content)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Default handler to render errors.
|
|
55
|
+
#
|
|
56
|
+
# @param status [Symbol|Fixnum] The HTTP error code to return.
|
|
57
|
+
# @param errors [Array] The list of occurred errors.
|
|
58
|
+
def render_error(status, errors)
|
|
59
|
+
@errors = errors
|
|
60
|
+
status_code = status.is_a?(Fixnum) ? status : Rack::Utils::SYMBOL_TO_STATUS_CODE.fetch(status.to_sym, 500)
|
|
61
|
+
render("errors/#{status_code}", status: status)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
private
|
|
65
|
+
|
|
66
|
+
# :nodoc:
|
|
67
|
+
def render_default_views(exception)
|
|
68
|
+
if defined?(@objects)
|
|
69
|
+
render "/collection"
|
|
70
|
+
elsif defined?(@object)
|
|
71
|
+
render "/object"
|
|
72
|
+
else
|
|
73
|
+
error_handle_exception(exception)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
data/lib/apes/errors.rb
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
#
|
|
2
|
+
# This file is part of the apes gem. Copyright (C) 2016 and above Shogun <shogun@cowtech.it>.
|
|
3
|
+
# Licensed under the MIT license, which can be found at http://www.opensource.org/licenses/mit-license.php.
|
|
4
|
+
#
|
|
5
|
+
|
|
6
|
+
module Apes
|
|
7
|
+
# Error used by the framework.
|
|
8
|
+
module Errors
|
|
9
|
+
# The base error.
|
|
10
|
+
class BaseError < RuntimeError
|
|
11
|
+
attr_reader :details
|
|
12
|
+
|
|
13
|
+
# Creates a new error.
|
|
14
|
+
#
|
|
15
|
+
# @param details [Object] The details of this error.
|
|
16
|
+
def initialize(details = nil)
|
|
17
|
+
super("")
|
|
18
|
+
@details = details
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Error raised when the request is not compliant with JSON API specification.
|
|
23
|
+
class BadRequestError < BaseError
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Error raised when the sent data is not valid.
|
|
27
|
+
class InvalidDataError < BaseError
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Error raised when the sent data is not missing.
|
|
31
|
+
class MissingDataError < BaseError
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Error raised when provided authentication is invalid.
|
|
35
|
+
class AuthenticationError < RuntimeError
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
data/lib/apes/model.rb
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
#
|
|
2
|
+
# This file is part of the apes gem. Copyright (C) 2016 and above Shogun <shogun@cowtech.it>.
|
|
3
|
+
# Licensed under the MIT license, which can be found at http://www.opensource.org/licenses/mit-license.php.
|
|
4
|
+
#
|
|
5
|
+
|
|
6
|
+
module Apes
|
|
7
|
+
# Some utility extensions to ActiveModel.
|
|
8
|
+
module Model
|
|
9
|
+
extend ActiveSupport::Concern
|
|
10
|
+
|
|
11
|
+
class_methods do
|
|
12
|
+
# Find a object by using the UUID, a handle or model specific definitions (defined using SECONDARY_KEY or SECONDARY_QUERY constants).
|
|
13
|
+
# Raise exception when nothing is found.
|
|
14
|
+
#
|
|
15
|
+
# @param id [Object] The value to find.
|
|
16
|
+
# @return [Object] The first found model.
|
|
17
|
+
def find_with_any!(id)
|
|
18
|
+
if id =~ Validators::UuidValidator::VALID_REGEX
|
|
19
|
+
find(id)
|
|
20
|
+
elsif defined?(self::SECONDARY_KEY)
|
|
21
|
+
find_by!(self::SECONDARY_KEY => id)
|
|
22
|
+
elsif defined?(self::SECONDARY_QUERY)
|
|
23
|
+
find_by!(self::SECONDARY_QUERY, {id: id})
|
|
24
|
+
else
|
|
25
|
+
find_by!(handle: id)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Find a object by using the UUID, a handle or model specific definitions (defined using SECONDARY_KEY or SECONDARY_QUERY constants).
|
|
30
|
+
#
|
|
31
|
+
# @param id [Object] The value to find.
|
|
32
|
+
# @return [Object] The first found model.
|
|
33
|
+
def find_with_any(id)
|
|
34
|
+
find_with_any!(id)
|
|
35
|
+
rescue ActiveRecord::RecordNotFound
|
|
36
|
+
nil
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Performs a search on the model.
|
|
40
|
+
#
|
|
41
|
+
# @param params [Hash] The list of params for the query.
|
|
42
|
+
# @param query [ActiveRecord::Relation|NilClass] A model query to further scope, if any.
|
|
43
|
+
# @param fields [Array] The model fields where to perform search on.
|
|
44
|
+
# @param start_only [Boolean] Whether only match that starts with the searched value rather than just containing it.
|
|
45
|
+
# @param parameter [Symbol|NilClass] The field in `params` which contains the value to search. Will fallback to `params[:filter][:query]` (using `.dig`).
|
|
46
|
+
# @param placeholder [Symbol] The placeholder to use in prepared statement. Useful to avoid collisions. Default is `query`.
|
|
47
|
+
# @param method [Symbol] The operator to use for searching. Everything different from `or` will fallback to `and`.
|
|
48
|
+
# @param case_sensitive [Boolean] Whether to perform case sensitive search. Default is `false`.
|
|
49
|
+
# @return [ActiveRecord::Relation] A query relation object.
|
|
50
|
+
def search(params: {}, query: nil, fields: ["name"], start_only: false, parameter: nil, placeholder: :query, method: :or, case_sensitive: false)
|
|
51
|
+
query ||= where({})
|
|
52
|
+
value = parameter ? params[parameter] : params.dig(:filter, :query)
|
|
53
|
+
return query if value.blank?
|
|
54
|
+
|
|
55
|
+
value = "#{value}%"
|
|
56
|
+
value = "%#{value}" unless start_only
|
|
57
|
+
|
|
58
|
+
method = method.to_s == "or" ? " OR " : " AND "
|
|
59
|
+
operator = case_sensitive ? "LIKE" : "ILIKE"
|
|
60
|
+
|
|
61
|
+
sql = fields.map { |f| "#{f} #{operator} :#{placeholder}" }.join(method)
|
|
62
|
+
query.where(sql, {placeholder => value})
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# A list of manually managed errors for the model.
|
|
67
|
+
#
|
|
68
|
+
# @return [ActiveModel::Errors] A list of manually managed errors for the model.
|
|
69
|
+
def additional_errors
|
|
70
|
+
@additional_errors ||= ActiveModel::Errors.new(self)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Perform validations on the model and makes sure manually added errors are included.
|
|
74
|
+
def run_validations!
|
|
75
|
+
errors.messages.merge!(additional_errors.messages)
|
|
76
|
+
super
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# A list of automatically and manually added errors for the model.
|
|
80
|
+
#
|
|
81
|
+
# @return [ActiveModel::Errors] A list of automatically and manually added errors for the model.
|
|
82
|
+
def all_validation_errors
|
|
83
|
+
additional_errors.each do |field, error|
|
|
84
|
+
errors.add(field, error)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
errors.each do |field|
|
|
88
|
+
errors[field].uniq!
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
errors
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
#
|
|
2
|
+
# This file is part of the apes gem. Copyright (C) 2016 and above Shogun <shogun@cowtech.it>.
|
|
3
|
+
# Licensed under the MIT license, which can be found at http://www.opensource.org/licenses/mit-license.php.
|
|
4
|
+
#
|
|
5
|
+
|
|
6
|
+
module Apes
|
|
7
|
+
# A cursor that can be sent to the client, received unmodified and retrieved later to paginate results.
|
|
8
|
+
#
|
|
9
|
+
# @attribute value
|
|
10
|
+
# @return [String] The value obtain from previous pagination. It can either be the value of the first or last element in previous iteration.
|
|
11
|
+
# @attribute use_offset
|
|
12
|
+
# @return [Boolean] Whether to use offset based pagination rather than collection fields values.
|
|
13
|
+
# @attribute direction
|
|
14
|
+
# @return [IO|String] Which page to get in this iteration.
|
|
15
|
+
# @attribute size
|
|
16
|
+
# @return [IO|String] The size of the pagination page.
|
|
17
|
+
class PaginationCursor
|
|
18
|
+
# The default size of a pagination page.
|
|
19
|
+
DEFAULT_SIZE = 25
|
|
20
|
+
|
|
21
|
+
# Format to serialize timestamp when using them for pagination.
|
|
22
|
+
TIMESTAMP_FORMAT = "%FT%T.%6N%z".freeze
|
|
23
|
+
|
|
24
|
+
attr_accessor :value, :use_offset, :direction, :size
|
|
25
|
+
|
|
26
|
+
# Creates a new cursor.
|
|
27
|
+
#
|
|
28
|
+
# @param params [Hash] The request parameters.
|
|
29
|
+
# @param field [Symbol] The parameters field where to lookup for the serialized cursor.
|
|
30
|
+
# @param count_field [Symbol] The parameters field where to lookup for the overriding cursor size.
|
|
31
|
+
# @return [Apes::PaginationCursor] A new cursor instance.
|
|
32
|
+
def initialize(params = {}, field = :page, count_field = :count)
|
|
33
|
+
begin
|
|
34
|
+
payload = JWT.decode(params[field], jwt_secret, true, {algorithm: "HS256", verify_aud: true, aud: "pagination"}).dig(0, "sub")
|
|
35
|
+
|
|
36
|
+
extract_payload(payload)
|
|
37
|
+
rescue
|
|
38
|
+
default_payload
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Sanitization
|
|
42
|
+
sanitize(count_field, params)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Get the operator (`>` or `<`) for the query according to the direction and the provided ordering.
|
|
46
|
+
#
|
|
47
|
+
# @param order [Symbol] The order to use.
|
|
48
|
+
# @return [String] The operator to use for the query.
|
|
49
|
+
def operator(order)
|
|
50
|
+
if direction == "next"
|
|
51
|
+
order == :asc ? ">" : "<" # Descending order means newer results first
|
|
52
|
+
else
|
|
53
|
+
order == :asc ? "<" : ">" # Descending order means newer results first
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Verifies whether a specific page might exist for the given collection.
|
|
58
|
+
#
|
|
59
|
+
# @param page [String] The page to check. It can be `first`, `next`, `prev` or `previous`.
|
|
60
|
+
# @param collection [Enumerable] The collection to analyze.
|
|
61
|
+
# @return [Boolean] Returns `true` if the page might exist for the collection, `false` otherwise.
|
|
62
|
+
def might_exist?(page, collection)
|
|
63
|
+
case page.to_s
|
|
64
|
+
when "first" then true
|
|
65
|
+
when "next" then collection.present?
|
|
66
|
+
else value.present? && collection.present? # Previous
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Serializes the cursor to send it to the client.
|
|
71
|
+
#
|
|
72
|
+
# @param collection [Enumerable] The collection to analyze.
|
|
73
|
+
# @param page [String] The page to return. It can be `first`, `next`, `prev` or `previous`.
|
|
74
|
+
# @param field [Symbol] When not using offset based pagination, the field to consider for generation.
|
|
75
|
+
# @param size [Fixnum] The number of results to advance when using offset based pagination.
|
|
76
|
+
# @param use_offset [Boolean] Whether to use offset based pagination.
|
|
77
|
+
# @return [String] The serialized cursor.
|
|
78
|
+
def save(collection, page, field: :id, size: nil, use_offset: nil)
|
|
79
|
+
size ||= self.size
|
|
80
|
+
use_offset = self.use_offset if use_offset.nil?
|
|
81
|
+
direction, value = use_offset ? update_with_offset(page, size) : update_with_field(page, collection, field)
|
|
82
|
+
|
|
83
|
+
value = value.strftime(TIMESTAMP_FORMAT) if value.respond_to?(:strftime)
|
|
84
|
+
|
|
85
|
+
JWT.encode({aud: "pagination", sub: {value: value, use_offset: use_offset, direction: direction, size: size}}, jwt_secret, "HS256")
|
|
86
|
+
end
|
|
87
|
+
alias_method :serialize, :save
|
|
88
|
+
|
|
89
|
+
private
|
|
90
|
+
|
|
91
|
+
# :nodoc:
|
|
92
|
+
def default_payload
|
|
93
|
+
@value = nil
|
|
94
|
+
@direction = "next"
|
|
95
|
+
@size = 0
|
|
96
|
+
@use_offset = false
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# :nodoc:
|
|
100
|
+
def extract_payload(payload)
|
|
101
|
+
@value = payload["value"]
|
|
102
|
+
@direction = payload["direction"]
|
|
103
|
+
@size = payload["size"]
|
|
104
|
+
@use_offset = payload["use_offset"]
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# :nodoc:
|
|
108
|
+
def sanitize(count_field, params)
|
|
109
|
+
@direction = "next" unless ["prev", "previous"].include?(@direction)
|
|
110
|
+
@size = params[count_field].to_integer if params[count_field].present?
|
|
111
|
+
@size = DEFAULT_SIZE if @size < 1
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# :nodoc:
|
|
115
|
+
def update_with_field(type, collection, field)
|
|
116
|
+
case type.ensure_string
|
|
117
|
+
when "next"
|
|
118
|
+
direction = "next"
|
|
119
|
+
value = collection.last&.send(field)
|
|
120
|
+
when "prev", "previous"
|
|
121
|
+
direction = "previous"
|
|
122
|
+
value = collection.first&.send(field)
|
|
123
|
+
else # first
|
|
124
|
+
direction = "next"
|
|
125
|
+
value = nil
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
[direction, value]
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# :nodoc:
|
|
132
|
+
def update_with_offset(type, size)
|
|
133
|
+
case type.ensure_string
|
|
134
|
+
when "next"
|
|
135
|
+
direction = "next"
|
|
136
|
+
value = self.value + size
|
|
137
|
+
when "prev", "previous"
|
|
138
|
+
direction = "previous"
|
|
139
|
+
value = [0, self.value - size].max
|
|
140
|
+
else # first
|
|
141
|
+
direction = "next"
|
|
142
|
+
value = nil
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
[direction, value]
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def jwt_secret
|
|
149
|
+
Apes::RuntimeConfiguration.jwt_token
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
end
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
#
|
|
2
|
+
# This file is part of the apes gem. Copyright (C) 2016 and above Shogun <shogun@cowtech.it>.
|
|
3
|
+
# Licensed under the MIT license, which can be found at http://www.opensource.org/licenses/mit-license.php.
|
|
4
|
+
#
|
|
5
|
+
|
|
6
|
+
module Apes
|
|
7
|
+
# Internal class to handle runtime configuration.
|
|
8
|
+
class RuntimeConfiguration
|
|
9
|
+
class << self
|
|
10
|
+
# Returns the root directory of apes.
|
|
11
|
+
# @return [String]
|
|
12
|
+
def root
|
|
13
|
+
Pathname.new(Gem.loaded_specs["apes"].full_gem_path).to_s
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Returns the current Rails root directory.
|
|
17
|
+
#
|
|
18
|
+
# @param default [String] The fallback if Rails configuration is invalid.
|
|
19
|
+
# @return [String] The current Rails root directory.
|
|
20
|
+
def rails_root(default = nil)
|
|
21
|
+
fetch_with_fallback(default) { Rails.root.to_s }
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Returns the current RubyGems root directory.
|
|
25
|
+
#
|
|
26
|
+
# @param default [String] The fallback if RubyGems configuration is invalid.
|
|
27
|
+
# @return [String] The current RubyGems root directory.
|
|
28
|
+
def gems_root(default = nil)
|
|
29
|
+
fetch_with_fallback(default) { Pathname.new(Gem.loaded_specs["lazier"].full_gem_path).parent.to_s }
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Returns the current Rails environment.
|
|
33
|
+
#
|
|
34
|
+
# @param default [String] The fallback environment if Rails configuration is invalid.
|
|
35
|
+
# @return [String] The the current Rails environment.
|
|
36
|
+
def environment(default = "development")
|
|
37
|
+
fetch_with_fallback(default) { Rails.env }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Check if Rails is in development environment.
|
|
41
|
+
#
|
|
42
|
+
# @return [Boolean] `true` if Rails is in `development` environment, `false` otherwise.
|
|
43
|
+
def development?
|
|
44
|
+
environment == "development"
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Returns the JWT token used by Apes. This should be defined in the Rails secrets.yml file.
|
|
48
|
+
#
|
|
49
|
+
# @param default [String] The fallback if no valid secret is found in Rails secrets file.
|
|
50
|
+
# @return [String] The JWT token used by Apes.
|
|
51
|
+
def jwt_token(default = "secret")
|
|
52
|
+
fetch_with_fallback(default) { Rails.application.secrets.jwt }
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Returns the CORS source used by Apes. This should be defined in the Rails secrets.yml file.
|
|
56
|
+
#
|
|
57
|
+
# @param default [String] The fallback if no valid CORS source is found in Rails secrets file.
|
|
58
|
+
# @return [String] The CORS source used by Apes.
|
|
59
|
+
def cors_source(default = "localhost")
|
|
60
|
+
fetch_with_fallback(default) { Rails.application.secrets.cors_source }
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Returns a map where keys are tags and values are strftime compliant formats.
|
|
64
|
+
#
|
|
65
|
+
# @param default [String] The fallback if no valid configuration is found in Rails.
|
|
66
|
+
# @return [Hash] A object describing valid timestamps formats.
|
|
67
|
+
def timestamp_formats(default = {})
|
|
68
|
+
fetch_with_fallback(default) { Rails.application.config.timestamp_formats }
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
private
|
|
72
|
+
|
|
73
|
+
def fetch_with_fallback(default)
|
|
74
|
+
yield
|
|
75
|
+
rescue
|
|
76
|
+
default
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|