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