sinja 1.0.0.pre2 → 1.1.0.pre1

Sign up to get free protection for your applications and to get access to all the features.
@@ -11,6 +11,12 @@ module Sinja
11
11
  c.not_found_exceptions << ::Sequel::NoMatchingRow
12
12
  c.validation_exceptions << ::Sequel::ValidationFailed
13
13
  c.validation_formatter = ->(e) { e.errors.keys.zip(e.errors.full_messages) }
14
+
15
+ c.page_using = {
16
+ :number=>1,
17
+ :size=>10,
18
+ :record_count=>nil
19
+ } if ::Sequel::Database::EXTENSIONS.key?(:pagination)
14
20
  end
15
21
 
16
22
  def validate!
@@ -21,6 +27,43 @@ module Sinja
21
27
  ::Sequel::DATABASES.first
22
28
  end
23
29
 
30
+ def filter(collection, **fields)
31
+ collection.where(fields)
32
+ end
33
+
34
+ def sort(collection, **fields)
35
+ collection.order(*fields.map { |k, v| ::Sequel.send(v, k) })
36
+ end
37
+
38
+ def page(collection, **opts)
39
+ opts = settings._sinja.page_using.merge(opts)
40
+ collection = collection.dataset \
41
+ unless collection.respond_to?(:paginate)
42
+ collection = collection.paginate \
43
+ opts[:number].to_i,
44
+ opts[:size].to_i,
45
+ (opts[:record_count].to_i if opts[:record_count])
46
+
47
+ # Attributes common to all pagination links
48
+ base = {
49
+ :size=>collection.page_size,
50
+ :record_count=>collection.pagination_record_count
51
+ }
52
+ pagination = {
53
+ :first=>base.merge(:number=>1),
54
+ :self=>base.merge(:number=>collection.current_page),
55
+ :last=>base.merge(:number=>collection.page_count)
56
+ }
57
+ pagination[:next] = base.merge(:number=>collection.next_page) if collection.next_page
58
+ pagination[:prev] = base.merge(:number=>collection.prev_page) if collection.prev_page
59
+
60
+ return collection, pagination
61
+ end if ::Sequel::Database::EXTENSIONS.key?(:pagination)
62
+
63
+ def finalize(collection)
64
+ collection.all
65
+ end
66
+
24
67
  def transaction(&block)
25
68
  database.transaction(&block)
26
69
  end
@@ -39,17 +39,16 @@ module Sinja
39
39
  end
40
40
 
41
41
  def include_exclude!(options)
42
- client, default, excluded =
42
+ included, default, excluded =
43
43
  params[:include],
44
44
  options.delete(:include) || [],
45
45
  options.delete(:exclude) || []
46
46
 
47
- included = Array === client ? client : client.split(',')
48
47
  if included.empty?
49
48
  included = Array === default ? default : default.split(',')
50
- end
51
49
 
52
- return included if included.empty?
50
+ return included if included.empty?
51
+ end
53
52
 
54
53
  excluded = Array === excluded ? excluded : excluded.split(',')
55
54
  unless excluded.empty?
@@ -64,7 +63,7 @@ module Sinja
64
63
  return included if included.empty?
65
64
  end
66
65
 
67
- return included unless settings._resource_roles
66
+ return included unless settings._resource_config
68
67
 
69
68
  # Walk the tree and try to exclude based on fetch and pluck permissions
70
69
  included.keep_if do |termstr|
@@ -72,18 +71,19 @@ module Sinja
72
71
  *terms, last_term = termstr.split('.')
73
72
 
74
73
  # Start cursor at root of current resource
75
- roles = settings._resource_roles
74
+ config = settings._resource_config
76
75
  terms.each do |term|
77
76
  # Move cursor through each term, avoiding the default proc,
78
77
  # halting if no roles found, i.e. client asked to include
79
78
  # something that Sinja doesn't know about
80
79
  throw :keep?, true \
81
- unless roles = settings._sinja.resource_roles.fetch(term.pluralize.to_sym, nil)
80
+ unless config = settings._sinja.resource_config.fetch(term.pluralize.to_sym, nil)
82
81
  end
83
82
 
84
- roles =
85
- roles.dig(:has_many, last_term.pluralize.to_sym, :fetch) ||
86
- roles.dig(:has_one, last_term.singularize.to_sym, :pluck)
83
+ roles = (
84
+ config.dig(:has_many, last_term.pluralize.to_sym, :fetch) ||
85
+ config.dig(:has_one, last_term.singularize.to_sym, :pluck)
86
+ )[:roles]
87
87
 
88
88
  throw :keep?, roles && (roles.empty? || roles === memoized_role)
89
89
  end
@@ -95,9 +95,9 @@ module Sinja
95
95
  options[:skip_collection_check] = defined?(::Sequel) && ::Sequel::Model === model
96
96
  options[:include] = include_exclude!(options)
97
97
  options[:fields] ||= params[:fields] unless params[:fields].empty?
98
+ options = settings._sinja.serializer_opts.merge(options)
98
99
 
99
- ::JSONAPI::Serializer.serialize model,
100
- settings._sinja.serializer_opts.merge(options)
100
+ ::JSONAPI::Serializer.serialize(model, options)
101
101
  end
102
102
 
103
103
  def serialize_model?(model=nil, options={})
@@ -114,9 +114,29 @@ module Sinja
114
114
  options[:is_collection] = true
115
115
  options[:include] = include_exclude!(options)
116
116
  options[:fields] ||= params[:fields] unless params[:fields].empty?
117
+ options = settings._sinja.serializer_opts.merge(options)
117
118
 
118
- ::JSONAPI::Serializer.serialize [*models],
119
- settings._sinja.serializer_opts.merge(options)
119
+ if options.key?(:links) && pagination = options[:links].delete(:pagination)
120
+ options[:links][:self] = request.url unless pagination.key?(:self)
121
+
122
+ query = Rack::Utils.build_nested_query \
123
+ env['rack.request.query_hash'].dup.tap { |h| h.delete('page') }
124
+ self_link, join_char =
125
+ if query.empty?
126
+ [request.path, ??]
127
+ else
128
+ ["#{request.path}?#{query}", ?&]
129
+ end
130
+
131
+ %i[self first prev next last].each do |key|
132
+ next unless pagination.key?(key)
133
+ query = Rack::Utils.build_nested_query \
134
+ :page=>pagination[key]
135
+ options[:links][key] = "#{self_link}#{join_char}#{query}"
136
+ end
137
+ end
138
+
139
+ ::JSONAPI::Serializer.serialize([*models], options)
120
140
  end
121
141
 
122
142
  def serialize_models?(models=[], options={})
@@ -129,16 +149,15 @@ module Sinja
129
149
  end
130
150
  end
131
151
 
132
- def serialize_linkage(model, rel_path, options={})
152
+ def serialize_linkage(model, rel, options={})
133
153
  options[:is_collection] = false
134
154
  options[:skip_collection_check] = defined?(::Sequel) && ::Sequel::Model === model
135
- options[:include] = rel_path.to_s
136
-
137
- content = ::JSONAPI::Serializer.serialize model,
138
- settings._sinja.serializer_opts.merge(options)
155
+ options[:include] = rel.to_s
156
+ options = settings._sinja.serializer_opts.merge(options)
139
157
 
140
158
  # TODO: This is extremely wasteful. Refactor JAS to expose the linkage serializer?
141
- content['data']['relationships'][rel_path.to_s].tap do |linkage|
159
+ content = ::JSONAPI::Serializer.serialize(model, options)
160
+ content['data']['relationships'][rel.to_s].tap do |linkage|
142
161
  %w[meta jsonapi].each do |key|
143
162
  linkage[key] = content[key] if content.key?(key)
144
163
  end
@@ -2,10 +2,11 @@
2
2
  module Sinja
3
3
  module RelationshipRoutes
4
4
  module HasMany
5
- ACTIONS = %i[fetch clear merge subtract].freeze
6
-
7
5
  def self.registered(app)
8
- app.def_action_helpers(ACTIONS, app)
6
+ app.def_action_helper(app, :fetch, %i[roles filter_by sort_by])
7
+ app.def_action_helper(app, :clear, %i[roles sideload_on])
8
+ app.def_action_helper(app, :merge, %i[roles sideload_on])
9
+ app.def_action_helper(app, :subtract, :roles)
9
10
 
10
11
  app.head '' do
11
12
  unless relationship_link?
@@ -21,8 +22,11 @@ module Sinja
21
22
  serialize_linkage
22
23
  end
23
24
 
24
- app.get '', :actions=>:fetch do
25
- serialize_models(*fetch)
25
+ app.get '', :qparams=>%i[include fields filter sort page], :actions=>:fetch do
26
+ collection, opts = fetch
27
+ collection, links = filter_sort_page(collection, :fetch)
28
+ (opts[:links] ||= {}).merge!(links)
29
+ serialize_models(collection, opts)
26
30
  end
27
31
 
28
32
  app.patch '', :nullif=>proc(&:empty?), :actions=>:clear do
@@ -2,10 +2,10 @@
2
2
  module Sinja
3
3
  module RelationshipRoutes
4
4
  module HasOne
5
- ACTIONS = %i[pluck prune graft].freeze
6
-
7
5
  def self.registered(app)
8
- app.def_action_helpers(ACTIONS, app)
6
+ app.def_action_helper(app, :pluck, :roles)
7
+ app.def_action_helper(app, :prune, :roles)
8
+ app.def_action_helper(app, :graft, %i[roles sideload_on])
9
9
 
10
10
  app.head '' do
11
11
  unless relationship_link?
@@ -21,7 +21,7 @@ module Sinja
21
21
  serialize_linkage
22
22
  end
23
23
 
24
- app.get '', :actions=>:pluck do
24
+ app.get '', :qparams=>%i[include fields], :actions=>:pluck do
25
25
  serialize_model(*pluck)
26
26
  end
27
27
 
@@ -12,15 +12,17 @@ require 'sinja/resource_routes'
12
12
 
13
13
  module Sinja
14
14
  module Resource
15
- SIDELOAD_ACTIONS = Set.new(%i[graft merge clear]).freeze
16
-
17
- def def_action_helper(action, context)
18
- abort "JSONAPI action helpers can't be HTTP verbs!" \
15
+ def def_action_helper(context, action, allow_opts=[])
16
+ abort "Action helper names can't overlap with Sinatra DSL" \
19
17
  if Sinatra::Base.respond_to?(action)
20
18
 
21
19
  context.define_singleton_method(action) do |**opts, &block|
22
- resource_roles.merge!(action=>opts[:roles]) if opts.key?(:roles)
23
- resource_sideload.merge!(action=>opts[:sideload_on]) if opts.key?(:sideload_on)
20
+ abort "Unexpected option(s) for `#{action}' action helper" \
21
+ unless (opts.keys - [*allow_opts]).empty?
22
+
23
+ resource_config[action].each do |k, v|
24
+ v.replace([*opts[k]]) if opts.key?(k)
25
+ end
24
26
 
25
27
  return unless block ||=
26
28
  case !method_defined?(action) && action
@@ -32,14 +34,16 @@ module Sinja
32
34
  required_arity = {
33
35
  :create=>2,
34
36
  :index=>-1,
35
- :fetch=>-1
37
+ :fetch=>-1,
38
+ :show_many=>-1
36
39
  }.freeze[action] || 1
37
40
 
38
41
  define_method(action) do |*args|
39
- raise ArgumentError, "Unexpected block signature for `#{action}' action helper" \
42
+ raise ArgumentError, "Unexpected argument(s) for `#{action}' action helper" \
40
43
  unless args.length == block.arity
41
44
 
42
- public_send("before_#{action}", *args) if respond_to?("before_#{action}")
45
+ public_send("before_#{action}", *args) \
46
+ if respond_to?("before_#{action}")
43
47
 
44
48
  case result = instance_exec(*args, &block)
45
49
  when Array
@@ -67,10 +71,6 @@ module Sinja
67
71
  end
68
72
  end
69
73
 
70
- def def_action_helpers(actions, context=nil)
71
- [*actions].each { |action| def_action_helper(action, context) }
72
- end
73
-
74
74
  def self.registered(app)
75
75
  app.helpers Helpers::Relationships do
76
76
  attr_accessor :resource
@@ -81,25 +81,23 @@ module Sinja
81
81
 
82
82
  %i[has_one has_many].each do |rel_type|
83
83
  define_method(rel_type) do |rel, &block|
84
- rel_path = rel.to_s
84
+ rel = rel.to_s
85
85
  .send(rel_type == :has_one ? :singularize : :pluralize)
86
86
  .dasherize
87
87
  .to_sym
88
88
 
89
- _resource_roles[rel_type][rel.to_sym] # trigger default proc
89
+ config = _resource_config[rel_type][rel] # trigger default proc
90
90
 
91
- namespace %r{/[^/]+(?<r>/relationships)?/#{rel_path}} do
92
- define_singleton_method(:resource_roles) do
93
- _resource_roles[rel_type][rel.to_sym]
94
- end
91
+ namespace %r{/[^/]+(?<r>/relationships)?/#{rel}} do
92
+ define_singleton_method(:resource_config) { config }
95
93
 
96
94
  helpers Helpers::Nested do
97
95
  define_method(:can?) do |*args|
98
- super(*args, rel_type, rel.to_sym)
96
+ super(*args, rel_type, rel)
99
97
  end
100
98
 
101
99
  define_method(:serialize_linkage) do |*args|
102
- super(resource, rel_path, *args)
100
+ super(resource, rel, *args)
103
101
  end
104
102
  end
105
103
 
@@ -1,39 +1,52 @@
1
1
  # frozen_string_literal: true
2
2
  module Sinja
3
3
  module ResourceRoutes
4
- ACTIONS = %i[index show create update destroy].freeze
5
-
6
4
  def self.registered(app)
7
- app.def_action_helpers(ACTIONS, app)
5
+ app.def_action_helper(app, :show, :roles)
6
+ app.def_action_helper(app, :show_many)
7
+ app.def_action_helper(app, :index, %i[roles filter_by sort_by])
8
+ app.def_action_helper(app, :create, :roles)
9
+ app.def_action_helper(app, :update, :roles)
10
+ app.def_action_helper(app, :destroy, :roles)
8
11
 
9
- app.head '', :pfilters=>:id do
12
+ app.head '', :qcapture=>{ :filter=>:id } do
10
13
  allow :get=>:show
11
14
  end
12
15
 
13
- app.get '', :pfilters=>:id, :actions=>:show do
14
- ids = params['filter'].delete('id')
15
- ids = ids.split(',') if ids.respond_to?(:split)
16
+ app.get '', :qcapture=>{ :filter=>:id }, :qparams=>%i[include fields], :actions=>:show do
17
+ ids = @qcaptures.first # TODO: Get this as a block parameter?
18
+ ids = ids.split(',') if String === ids
19
+ ids = [*ids].tap(&:uniq!)
16
20
 
17
- opts = {}
18
- resources = [*ids].tap(&:uniq!).map! do |id|
19
- tmp, opts = show(id)
20
- raise NotFoundError, "Resource '#{id}' not found" unless tmp
21
- tmp
22
- end
21
+ if respond_to?(:show_many)
22
+ resources, opts = show_many(ids)
23
+ raise NotFoundError, "Resource(s) not found" \
24
+ unless ids.length == resources.length
25
+ serialize_models(resources, opts)
26
+ else
27
+ opts = {}
28
+ resources = ids.map! do |id|
29
+ tmp, opts = show(id)
30
+ raise NotFoundError, "Resource '#{id}' not found" unless tmp
31
+ tmp
32
+ end
23
33
 
24
- # TODO: Serialize collection with opts from last model found?
25
- serialize_models(resources, opts)
34
+ serialize_models(resources, opts)
35
+ end
26
36
  end
27
37
 
28
38
  app.head '' do
29
39
  allow :get=>:index, :post=>:create
30
40
  end
31
41
 
32
- app.get '', :actions=>:index do
33
- serialize_models(*index)
42
+ app.get '', :qparams=>%i[include fields filter sort page], :actions=>:index do
43
+ collection, opts = index
44
+ collection, links = filter_sort_page(collection, :index)
45
+ (opts[:links] ||= {}).merge!(links)
46
+ serialize_models(collection, opts)
34
47
  end
35
48
 
36
- app.post '', :actions=>:create do
49
+ app.post '', :qparams=>:include, :actions=>:create do
37
50
  sanity_check!
38
51
 
39
52
  opts = {}
@@ -72,13 +85,13 @@ module Sinja
72
85
  allow :get=>:show, :patch=>:update, :delete=>:destroy
73
86
  end
74
87
 
75
- app.get '/:id', :actions=>:show do |id|
88
+ app.get '/:id', :qparams=>%i[include fields], :actions=>:show do |id|
76
89
  tmp, opts = show(id)
77
90
  raise NotFoundError, "Resource '#{id}' not found" unless tmp
78
91
  serialize_model(tmp, opts)
79
92
  end
80
93
 
81
- app.patch '/:id', :actions=>:update do |id|
94
+ app.patch '/:id', :qparams=>:include, :actions=>:update do |id|
82
95
  sanity_check!(id)
83
96
  tmp, opts = transaction do
84
97
  update(attributes).tap do
data/lib/sinja/version.rb CHANGED
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module Sinja
3
- VERSION = '1.0.0.pre2'
3
+ VERSION = '1.1.0.pre1'
4
4
  end
data/lib/sinja.rb CHANGED
@@ -4,6 +4,7 @@ require 'mustermann'
4
4
  require 'sinatra/base'
5
5
  require 'sinatra/namespace'
6
6
 
7
+ require 'set'
7
8
  require 'sinja/config'
8
9
  require 'sinja/errors'
9
10
  require 'sinja/helpers/serializers'
@@ -34,33 +35,17 @@ module Sinja
34
35
  .to_sym
35
36
 
36
37
  # trigger default procs
37
- _sinja.resource_roles[resource_name]
38
- _sinja.resource_sideload[resource_name]
38
+ config = _sinja.resource_config[resource_name]
39
39
 
40
40
  namespace "/#{resource_name}" do
41
- define_singleton_method(:_resource_roles) do
42
- _sinja.resource_roles[resource_name]
43
- end
44
-
45
- define_singleton_method(:resource_roles) do
46
- _resource_roles[:resource]
47
- end
48
-
49
- define_singleton_method(:resource_sideload) do
50
- _sinja.resource_sideload[resource_name]
51
- end
41
+ define_singleton_method(:_resource_config) { config }
42
+ define_singleton_method(:resource_config) { config[:resource] }
52
43
 
53
44
  helpers do
54
- define_method(:can?) do |*args|
55
- super(resource_name, *args)
56
- end
57
-
58
- define_method(:sanity_check!) do |*args|
59
- super(resource_name, *args)
60
- end
61
-
62
- define_method(:sideload?) do |*args|
63
- super(resource_name, *args)
45
+ %i[can? sanity_check! sideload?].each do |meth|
46
+ define_method(meth) do |*args|
47
+ super(resource_name, *args)
48
+ end
64
49
  end
65
50
  end
66
51
 
@@ -102,7 +87,7 @@ module Sinja
102
87
 
103
88
  app.disable :protection, :show_exceptions, :static
104
89
  app.set :_sinja, Sinja::Config.new
105
- app.set :_resource_roles, nil # dummy value overridden in each resource
90
+ app.set :_resource_config, nil # dummy value overridden in each resource
106
91
 
107
92
  app.set :actions do |*actions|
108
93
  condition do
@@ -112,18 +97,61 @@ module Sinja
112
97
  raise MethodNotAllowedError, 'Action or method not implemented or supported' \
113
98
  unless respond_to?(action)
114
99
  end
100
+
115
101
  true
116
102
  end
117
103
  end
118
104
 
119
- app.set :pfilters do |*pfilters|
105
+ app.set :qcapture do |*index|
120
106
  condition do
121
- pfilters.all? do |pfilter|
122
- params.key?('filter') && params['filter'].key?(pfilter.to_s)
107
+ @qcaptures ||= []
108
+ index.to_h.all? do |key, subkeys|
109
+ params.key?(key.to_s) && [*subkeys].all? do |subkey|
110
+ @qcaptures << params[key.to_s].delete(subkey.to_s) \
111
+ if params[key.to_s].key?(subkey.to_s)
112
+ end
123
113
  end
124
114
  end
125
115
  end
126
116
 
117
+ app.set :qparams do |*allow_params|
118
+ allow_params = allow_params.to_set
119
+
120
+ abort "Unexpected query parameter(s) in route definiton" \
121
+ unless allow_params.subset?(settings._sinja.query_params.keys.to_set)
122
+
123
+ condition do
124
+ params.each do |key, value|
125
+ key = key.to_sym
126
+
127
+ next if settings._sinja.query_params[key].nil?
128
+
129
+ raise BadRequestError, "`#{key}' query parameter not allowed" \
130
+ unless allow_params.include?(key) || value.empty?
131
+
132
+ if Array === settings._sinja.query_params[key] && String === value
133
+ value = (params[key.to_s] = value.split(','))
134
+ elsif !(settings._sinja.query_params[key].class === value)
135
+ raise BadRequestError, "`#{key}' query parameter malformed"
136
+ end
137
+ end
138
+
139
+ settings._sinja.query_params.each do |key, value|
140
+ next if value.nil?
141
+
142
+ key = key.to_s
143
+
144
+ if respond_to?("normalize_#{key}_params")
145
+ params[key] = send("normalize_#{key}_params")
146
+ else
147
+ params[key] ||= value
148
+ end
149
+ end
150
+
151
+ true
152
+ end
153
+ end
154
+
127
155
  app.set :nullif do |nullish|
128
156
  condition { nullish.(data) }
129
157
  end
@@ -151,10 +179,13 @@ module Sinja
151
179
  end
152
180
 
153
181
  def can?(resource_name, action, rel_type=nil, rel=nil)
154
- lookup = settings._sinja.resource_roles[resource_name]
155
- # TODO: This is... problematic.
156
- roles = (lookup[rel_type][rel][action] if rel_type && rel) || lookup[:resource][action]
157
- roles.nil? || roles.empty? || roles === memoized_role
182
+ config = settings._sinja.resource_config[resource_name]
183
+ config = rel_type && rel ? config[rel_type][rel] : config[:resource]
184
+ # JRuby issues with nil default_proc (fixed in 9.1.7.0?)
185
+ # https://github.com/jruby/jruby/issues/4302
186
+ #roles = config&.dig(action, :roles)
187
+ roles = config&.key?(action) && config[action][:roles]
188
+ !roles || roles.empty? || roles === memoized_role
158
189
  end
159
190
 
160
191
  def content?
@@ -166,8 +197,72 @@ module Sinja
166
197
  @data[request.path] ||= begin
167
198
  deserialize_request_body.fetch(:data)
168
199
  rescue NoMethodError, KeyError
169
- raise BadRequestError, 'Malformed JSON:API request payload'
200
+ raise BadRequestError, 'Malformed {json:api} request payload'
201
+ end
202
+ end
203
+
204
+ def normalize_filter_params
205
+ return {} unless params[:filter]&.any?
206
+
207
+ halt 400, "Unsupported `filter' query parameter(s)" \
208
+ unless respond_to?(:filter)
209
+
210
+ params[:filter].map do |k, v|
211
+ [dedasherize(k).to_sym, v]
212
+ end.to_h
213
+ end
214
+
215
+ def normalize_sort_params
216
+ return [] unless params[:sort]&.any?
217
+
218
+ halt 400, "Unsupported `sort' query parameter(s)" \
219
+ unless respond_to?(:sort)
220
+
221
+ params[:sort].map do |k|
222
+ dir = k.sub!(/^-/, '') ? :desc : :asc
223
+ [dedasherize(k).to_sym, dir]
224
+ end.to_h
225
+ end
226
+
227
+ def normalize_page_params
228
+ return {} unless params[:page]&.any?
229
+
230
+ halt 400, "Unsupported `page' query parameter(s)" \
231
+ unless respond_to?(:page)
232
+
233
+ h = params[:page].map do |k, v|
234
+ [dedasherize(k).to_sym, v]
235
+ end.to_h
236
+
237
+ return h if h.keys.to_set.subset?(settings._sinja.page_using.keys.to_set)
238
+
239
+ halt 400, "Invalid `page' query parameter(s)"
240
+ end
241
+
242
+ def filter_sort_page(collection, action)
243
+ unless params[:filter].empty?
244
+ halt 400, "Invalid `filter' query parameter(s)" \
245
+ unless settings.resource_config[action][:filter_by].empty? ||
246
+ params[:filter].keys.to_set.subset?(settings.resource_config[action][:filter_by])
247
+
248
+ collection = filter(collection, params[:filter])
249
+ end
250
+
251
+ unless params[:sort].empty?
252
+ halt 400, "Invalid `sort' query parameter(s)" \
253
+ unless settings.resource_config[action][:sort_by].empty? ||
254
+ params[:sort].keys.to_set.subset?(settings.resource_config[action][:sort_by])
255
+
256
+ collection = sort(collection, params[:sort])
170
257
  end
258
+
259
+ unless params[:page].empty?
260
+ collection, pagination = page(collection, params[:page])
261
+ end
262
+
263
+ collection = finalize(collection) if respond_to?(:finalize)
264
+
265
+ return collection, :pagination=>pagination
171
266
  end
172
267
 
173
268
  def halt(code, body=nil)
@@ -184,22 +279,10 @@ module Sinja
184
279
  @role ||= role
185
280
  end
186
281
 
187
- def normalize_params!
188
- # TODO: halt 400 if other params, or params not implemented?
189
- {
190
- :fields=>{}, # passthru
191
- :include=>[], # passthru
192
- :filter=>{},
193
- :page=>{},
194
- :sort=>''
195
- }.each { |k, v| params[k] ||= v }
196
- end
197
-
198
282
  def sideload?(resource_name, child)
199
283
  return unless sideloaded?
200
- parent = env.fetch('sinja.passthru', 'unknown').to_sym
201
- settings._sinja.resource_sideload[resource_name][child]&.
202
- include?(parent) && can?(parent)
284
+ parent = env['sinja.passthru'].to_sym
285
+ settings.resource_config[child][:sideload_on]&.include?(parent) && can?(parent)
203
286
  end
204
287
 
205
288
  def sideloaded?
@@ -235,8 +318,6 @@ module Sinja
235
318
  )
236
319
  end
237
320
 
238
- normalize_params!
239
-
240
321
  content_type :api_json
241
322
  end
242
323
 
@@ -265,4 +346,12 @@ module Sinja
265
346
  serialize_errors(&settings._sinja.error_logger)
266
347
  end
267
348
  end
349
+
350
+ def self.extended(base)
351
+ def base.route(*, **opts)
352
+ opts[:qparams] ||= []
353
+
354
+ super
355
+ end
356
+ end
268
357
  end
data/sinja.gemspec CHANGED
@@ -9,7 +9,7 @@ Gem::Specification.new do |spec|
9
9
  spec.authors = ['Mike Pastore']
10
10
  spec.email = ['mike@oobak.org']
11
11
 
12
- spec.summary = 'RESTful, JSON:API-compliant web services in Sinatra'
12
+ spec.summary = 'RESTful, {json:api}-compliant web services in Sinatra'
13
13
  spec.homepage = 'https://github.com/mwpastore/sinja'
14
14
  spec.license = 'MIT'
15
15