haveapi 0.3.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/lib/haveapi/action.rb +521 -0
- data/lib/haveapi/actions/default.rb +55 -0
- data/lib/haveapi/actions/paginable.rb +12 -0
- data/lib/haveapi/api.rb +66 -0
- data/lib/haveapi/authentication/base.rb +37 -0
- data/lib/haveapi/authentication/basic/provider.rb +37 -0
- data/lib/haveapi/authentication/chain.rb +110 -0
- data/lib/haveapi/authentication/token/provider.rb +166 -0
- data/lib/haveapi/authentication/token/resources.rb +107 -0
- data/lib/haveapi/authorization.rb +108 -0
- data/lib/haveapi/common.rb +38 -0
- data/lib/haveapi/context.rb +78 -0
- data/lib/haveapi/example.rb +36 -0
- data/lib/haveapi/extensions/action_exceptions.rb +25 -0
- data/lib/haveapi/extensions/base.rb +9 -0
- data/lib/haveapi/extensions/resource_prefetch.rb +7 -0
- data/lib/haveapi/hooks.rb +190 -0
- data/lib/haveapi/metadata.rb +56 -0
- data/lib/haveapi/model_adapter.rb +119 -0
- data/lib/haveapi/model_adapters/active_record.rb +352 -0
- data/lib/haveapi/model_adapters/hash.rb +27 -0
- data/lib/haveapi/output_formatter.rb +57 -0
- data/lib/haveapi/output_formatters/base.rb +29 -0
- data/lib/haveapi/output_formatters/json.rb +9 -0
- data/lib/haveapi/params/param.rb +114 -0
- data/lib/haveapi/params/resource.rb +109 -0
- data/lib/haveapi/params.rb +314 -0
- data/lib/haveapi/public/css/bootstrap-theme.min.css +7 -0
- data/lib/haveapi/public/css/bootstrap.min.css +7 -0
- data/lib/haveapi/public/js/bootstrap.min.js +6 -0
- data/lib/haveapi/public/js/jquery-1.11.1.min.js +4 -0
- data/lib/haveapi/resource.rb +120 -0
- data/lib/haveapi/route.rb +22 -0
- data/lib/haveapi/server.rb +440 -0
- data/lib/haveapi/spec/helpers.rb +103 -0
- data/lib/haveapi/types.rb +24 -0
- data/lib/haveapi/version.rb +3 -0
- data/lib/haveapi/views/doc_layout.erb +27 -0
- data/lib/haveapi/views/doc_sidebars/create-client.erb +20 -0
- data/lib/haveapi/views/doc_sidebars/protocol.erb +42 -0
- data/lib/haveapi/views/index.erb +12 -0
- data/lib/haveapi/views/main_layout.erb +50 -0
- data/lib/haveapi/views/version_page.erb +195 -0
- data/lib/haveapi/views/version_sidebar.erb +42 -0
- data/lib/haveapi.rb +22 -0
- metadata +242 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 3eed7a7a97050e343134cb1d9cfd614099a613c6
|
4
|
+
data.tar.gz: 09d4bc3e055b2d385b8e6bdf49140425852b4a6f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ed8241e6a1e8d65b41fb56671fe421730eaab16a748d8373d4331c06a5b14990e2e4a7b535919524bf655ab8a0ca0f6fccdb4c83932d9ec78ece6caffb7e9397
|
7
|
+
data.tar.gz: 992ef4d404cdd0b07564b6bd9310c59280544a026382ec1a84b753586a8cab2482a293f72b3ea15b55f4dcd487181747b7abf14f2f04b880b057ea7b8bef7272
|
@@ -0,0 +1,521 @@
|
|
1
|
+
module HaveAPI
|
2
|
+
class Action < Common
|
3
|
+
obj_type :action
|
4
|
+
has_attr :version
|
5
|
+
has_attr :desc
|
6
|
+
has_attr :route
|
7
|
+
has_attr :resolve, ->(klass){ klass.respond_to?(:id) ? klass.id : nil }
|
8
|
+
has_attr :http_method, :get
|
9
|
+
has_attr :auth, true
|
10
|
+
has_attr :aliases, []
|
11
|
+
|
12
|
+
include Hookable
|
13
|
+
|
14
|
+
has_hook :exec_exception
|
15
|
+
|
16
|
+
attr_reader :message, :errors, :version
|
17
|
+
attr_accessor :flags
|
18
|
+
|
19
|
+
class << self
|
20
|
+
attr_reader :resource, :authorization, :examples
|
21
|
+
|
22
|
+
def inherited(subclass)
|
23
|
+
# puts "Action.inherited called #{subclass} from #{to_s}"
|
24
|
+
|
25
|
+
subclass.instance_variable_set(:@obj_type, obj_type)
|
26
|
+
|
27
|
+
if subclass.name
|
28
|
+
# not an anonymouse class
|
29
|
+
delayed_inherited(subclass)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def delayed_inherited(subclass)
|
34
|
+
resource = Kernel.const_get(subclass.to_s.deconstantize)
|
35
|
+
|
36
|
+
inherit_attrs(subclass)
|
37
|
+
inherit_attrs_from_resource(subclass, resource, [:auth])
|
38
|
+
|
39
|
+
i = @input.clone
|
40
|
+
i.action = subclass
|
41
|
+
|
42
|
+
o = @output.clone
|
43
|
+
o.action = subclass
|
44
|
+
|
45
|
+
m = {}
|
46
|
+
|
47
|
+
@meta.each do |k,v|
|
48
|
+
m[k] = v && v.clone
|
49
|
+
next unless v
|
50
|
+
m[k].action = subclass
|
51
|
+
end
|
52
|
+
|
53
|
+
subclass.instance_variable_set(:@input, i)
|
54
|
+
subclass.instance_variable_set(:@output, o)
|
55
|
+
subclass.instance_variable_set(:@meta, m)
|
56
|
+
|
57
|
+
begin
|
58
|
+
subclass.instance_variable_set(:@resource, resource)
|
59
|
+
subclass.instance_variable_set(:@model, resource.model)
|
60
|
+
rescue NoMethodError
|
61
|
+
return
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def initialize
|
66
|
+
return if @initialized
|
67
|
+
|
68
|
+
input.exec
|
69
|
+
model_adapter(input.layout).load_validators(model, input) if model
|
70
|
+
|
71
|
+
output.exec
|
72
|
+
|
73
|
+
model_adapter(input.layout).used_by(:input, self)
|
74
|
+
model_adapter(output.layout).used_by(:output, self)
|
75
|
+
|
76
|
+
if @meta
|
77
|
+
@meta.each_value do |m|
|
78
|
+
next unless m
|
79
|
+
m.input && m.input.exec
|
80
|
+
m.output && m.output.exec
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
@initialized = true
|
85
|
+
end
|
86
|
+
|
87
|
+
def model_adapter(layout)
|
88
|
+
ModelAdapter.for(layout, resource.model)
|
89
|
+
end
|
90
|
+
|
91
|
+
def input(layout = nil, namespace: nil, &block)
|
92
|
+
if block
|
93
|
+
@input ||= Params.new(:input, self)
|
94
|
+
@input.layout = layout
|
95
|
+
@input.namespace = namespace
|
96
|
+
@input.add_block(block)
|
97
|
+
else
|
98
|
+
@input
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def output(layout = nil, namespace: nil, &block)
|
103
|
+
if block
|
104
|
+
@output ||= Params.new(:output, self)
|
105
|
+
@output.layout = layout
|
106
|
+
@output.namespace = namespace
|
107
|
+
@output.add_block(block)
|
108
|
+
else
|
109
|
+
@output
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def meta(type = :object, &block)
|
114
|
+
if block
|
115
|
+
@meta ||= {object: nil, global: nil}
|
116
|
+
@meta[type] ||= Metadata::ActionMetadata.new
|
117
|
+
@meta[type].action = self
|
118
|
+
@meta[type].instance_exec(&block)
|
119
|
+
else
|
120
|
+
@meta[type]
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def authorize(&block)
|
125
|
+
@authorization = Authorization.new(&block)
|
126
|
+
end
|
127
|
+
|
128
|
+
def example(title = '', &block)
|
129
|
+
@examples ||= []
|
130
|
+
e = Example.new(title)
|
131
|
+
e.instance_eval(&block)
|
132
|
+
@examples << e
|
133
|
+
end
|
134
|
+
|
135
|
+
def build_route(prefix)
|
136
|
+
route = @route || to_s.demodulize.underscore
|
137
|
+
|
138
|
+
if !route.is_a?(String) && route.respond_to?(:call)
|
139
|
+
route = route.call(self.resource)
|
140
|
+
end
|
141
|
+
|
142
|
+
prefix + route % {resource: self.resource.to_s.demodulize.underscore}
|
143
|
+
end
|
144
|
+
|
145
|
+
def describe(context)
|
146
|
+
authorization = (@authorization && @authorization.clone) || Authorization.new
|
147
|
+
|
148
|
+
return false if (context.endpoint || context.current_user) && !authorization.authorized?(context.current_user)
|
149
|
+
|
150
|
+
route_method = context.action.http_method.to_s.upcase
|
151
|
+
context.authorization = authorization
|
152
|
+
|
153
|
+
if context.endpoint
|
154
|
+
context.action_instance = context.action.from_context(context)
|
155
|
+
|
156
|
+
ret = catch(:return) do
|
157
|
+
context.action_prepare = context.action_instance.prepare
|
158
|
+
end
|
159
|
+
|
160
|
+
return false if ret == false
|
161
|
+
end
|
162
|
+
|
163
|
+
{
|
164
|
+
auth: @auth,
|
165
|
+
description: @desc,
|
166
|
+
aliases: @aliases,
|
167
|
+
input: @input ? @input.describe(context) : {parameters: {}},
|
168
|
+
output: @output ? @output.describe(context) : {parameters: {}},
|
169
|
+
meta: @meta ? @meta.merge(@meta) { |_, v| v && v.describe(context) } : nil,
|
170
|
+
examples: @examples ? @examples.map { |e| e.describe } : [],
|
171
|
+
url: context.resolved_url,
|
172
|
+
method: route_method,
|
173
|
+
help: "#{context.url}?method=#{route_method}"
|
174
|
+
}
|
175
|
+
end
|
176
|
+
|
177
|
+
# Inherit attributes from resource action is defined in.
|
178
|
+
def inherit_attrs_from_resource(action, r, attrs)
|
179
|
+
begin
|
180
|
+
return unless r.obj_type == :resource
|
181
|
+
|
182
|
+
rescue NoMethodError
|
183
|
+
return
|
184
|
+
end
|
185
|
+
|
186
|
+
attrs.each do |attr|
|
187
|
+
action.method(attr).call(r.method(attr).call)
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
def from_context(c)
|
192
|
+
ret = new(nil, c.version, c.params, nil, c)
|
193
|
+
ret.instance_exec do
|
194
|
+
@safe_params = @params.dup
|
195
|
+
@authorization = c.authorization
|
196
|
+
@current_user = c.current_user
|
197
|
+
end
|
198
|
+
|
199
|
+
ret
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
def initialize(request, version, params, body, context)
|
204
|
+
@request = request
|
205
|
+
@version = version
|
206
|
+
@params = params
|
207
|
+
@params.update(body) if body
|
208
|
+
@context = context
|
209
|
+
@context.action = self.class
|
210
|
+
@context.action_instance = self
|
211
|
+
@metadata = {}
|
212
|
+
@reply_meta = {object: {}, global: {}}
|
213
|
+
@flags = {}
|
214
|
+
|
215
|
+
class_auth = self.class.authorization
|
216
|
+
|
217
|
+
if class_auth
|
218
|
+
@authorization = class_auth.clone
|
219
|
+
else
|
220
|
+
@authorization = Authorization.new {}
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
def validate!
|
225
|
+
begin
|
226
|
+
@params = validate
|
227
|
+
rescue ValidationError => e
|
228
|
+
error(e.message, e.to_hash)
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
def authorized?(user)
|
233
|
+
@current_user = user
|
234
|
+
@authorization.authorized?(user)
|
235
|
+
end
|
236
|
+
|
237
|
+
def current_user
|
238
|
+
@current_user
|
239
|
+
end
|
240
|
+
|
241
|
+
def params
|
242
|
+
@safe_params
|
243
|
+
end
|
244
|
+
|
245
|
+
def input
|
246
|
+
@safe_params[ self.class.input.namespace ] if self.class.input
|
247
|
+
end
|
248
|
+
|
249
|
+
def request
|
250
|
+
@request
|
251
|
+
end
|
252
|
+
|
253
|
+
def meta
|
254
|
+
@metadata
|
255
|
+
end
|
256
|
+
|
257
|
+
def set_meta(hash)
|
258
|
+
@reply_meta[:global].update(hash)
|
259
|
+
end
|
260
|
+
|
261
|
+
# Prepare object, set instance variables from URL parameters.
|
262
|
+
# This method should return queried object. If the method is
|
263
|
+
# not implemented or returns nil, action description will not
|
264
|
+
# contain link to an associated resource.
|
265
|
+
# --
|
266
|
+
# FIXME: is this correct behaviour?
|
267
|
+
# ++
|
268
|
+
def prepare
|
269
|
+
|
270
|
+
end
|
271
|
+
|
272
|
+
def pre_exec
|
273
|
+
|
274
|
+
end
|
275
|
+
|
276
|
+
# This method must be reimplemented in every action.
|
277
|
+
# It must not be invoked directly, only via safe_exec, which restricts output.
|
278
|
+
def exec
|
279
|
+
['not implemented']
|
280
|
+
end
|
281
|
+
|
282
|
+
# Calls exec while catching all exceptions and restricting output only
|
283
|
+
# to what user can see.
|
284
|
+
# Return array +[status, data|error, errors]+
|
285
|
+
def safe_exec
|
286
|
+
ret = catch(:return) do
|
287
|
+
begin
|
288
|
+
validate!
|
289
|
+
prepare
|
290
|
+
pre_exec
|
291
|
+
exec
|
292
|
+
rescue Exception => e
|
293
|
+
tmp = call_class_hooks_as_for(Action, :exec_exception, args: [self, e])
|
294
|
+
|
295
|
+
if tmp.empty?
|
296
|
+
p e.message
|
297
|
+
puts e.backtrace
|
298
|
+
error('Server error occurred')
|
299
|
+
end
|
300
|
+
|
301
|
+
error(tmp[:message]) unless tmp[:status]
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
305
|
+
safe_output(ret)
|
306
|
+
end
|
307
|
+
|
308
|
+
def v?(v)
|
309
|
+
@version == v
|
310
|
+
end
|
311
|
+
|
312
|
+
def safe_output(ret)
|
313
|
+
if ret
|
314
|
+
output = self.class.output
|
315
|
+
|
316
|
+
if output
|
317
|
+
safe_ret = nil
|
318
|
+
adapter = self.class.model_adapter(output.layout)
|
319
|
+
out_params = self.class.output.params
|
320
|
+
|
321
|
+
case output.layout
|
322
|
+
when :object
|
323
|
+
out = adapter.output(@context, ret)
|
324
|
+
safe_ret = @authorization.filter_output(
|
325
|
+
out_params,
|
326
|
+
out,
|
327
|
+
true
|
328
|
+
)
|
329
|
+
@reply_meta[:global].update(out.meta)
|
330
|
+
|
331
|
+
when :object_list
|
332
|
+
safe_ret = []
|
333
|
+
|
334
|
+
ret.each do |obj|
|
335
|
+
out = adapter.output(@context, obj)
|
336
|
+
|
337
|
+
safe_ret << @authorization.filter_output(
|
338
|
+
out_params,
|
339
|
+
out,
|
340
|
+
true
|
341
|
+
)
|
342
|
+
safe_ret.last.update({Metadata.namespace => out.meta}) unless meta[:no]
|
343
|
+
end
|
344
|
+
|
345
|
+
when :hash
|
346
|
+
safe_ret = @authorization.filter_output(
|
347
|
+
out_params,
|
348
|
+
adapter.output(@context, ret),
|
349
|
+
true
|
350
|
+
)
|
351
|
+
|
352
|
+
when :hash_list
|
353
|
+
safe_ret = ret
|
354
|
+
safe_ret.map! do |hash|
|
355
|
+
@authorization.filter_output(
|
356
|
+
out_params,
|
357
|
+
adapter.output(@context, hash),
|
358
|
+
true
|
359
|
+
)
|
360
|
+
end
|
361
|
+
|
362
|
+
else
|
363
|
+
safe_ret = ret
|
364
|
+
end
|
365
|
+
|
366
|
+
ns = {output.namespace => safe_ret}
|
367
|
+
ns[Metadata.namespace] = @reply_meta[:global] unless meta[:no]
|
368
|
+
|
369
|
+
[true, ns]
|
370
|
+
|
371
|
+
else
|
372
|
+
[true, {}]
|
373
|
+
end
|
374
|
+
|
375
|
+
else
|
376
|
+
[false, @message, @errors]
|
377
|
+
end
|
378
|
+
end
|
379
|
+
|
380
|
+
input {}
|
381
|
+
output {}
|
382
|
+
meta(:global) do
|
383
|
+
input do
|
384
|
+
bool :no, label: 'Disable metadata'
|
385
|
+
end
|
386
|
+
end
|
387
|
+
|
388
|
+
protected
|
389
|
+
def with_restricted(*args)
|
390
|
+
if args.empty?
|
391
|
+
@authorization.restrictions
|
392
|
+
else
|
393
|
+
args.first.update(@authorization.restrictions)
|
394
|
+
end
|
395
|
+
end
|
396
|
+
|
397
|
+
# Convert parameter names to corresponding DB names.
|
398
|
+
# By default, input parameters are used for the translation.
|
399
|
+
def to_db_names(hash, src=:input)
|
400
|
+
return {} unless hash
|
401
|
+
|
402
|
+
params = self.class.method(src).call.params
|
403
|
+
ret = {}
|
404
|
+
|
405
|
+
hash.each do |k, v|
|
406
|
+
k = k.to_sym
|
407
|
+
hit = false
|
408
|
+
|
409
|
+
params.each do |p|
|
410
|
+
if k == p.name
|
411
|
+
ret[p.db_name] = v
|
412
|
+
hit = true
|
413
|
+
break
|
414
|
+
end
|
415
|
+
end
|
416
|
+
|
417
|
+
ret[k] = v unless hit
|
418
|
+
end
|
419
|
+
|
420
|
+
ret
|
421
|
+
end
|
422
|
+
|
423
|
+
# Convert DB names to corresponding parameter names.
|
424
|
+
# By default, output parameters are used for the translation.
|
425
|
+
def to_param_names(hash, src=:output)
|
426
|
+
return {} unless hash
|
427
|
+
|
428
|
+
params = self.class.method(src).call.params
|
429
|
+
ret = {}
|
430
|
+
|
431
|
+
hash.each do |k, v|
|
432
|
+
k = k.to_sym
|
433
|
+
hit = false
|
434
|
+
|
435
|
+
params.each do |p|
|
436
|
+
if k == p.db_name
|
437
|
+
ret[p.name] = v
|
438
|
+
hit = true
|
439
|
+
break
|
440
|
+
end
|
441
|
+
end
|
442
|
+
|
443
|
+
ret[k] = v unless hit
|
444
|
+
end
|
445
|
+
|
446
|
+
ret
|
447
|
+
end
|
448
|
+
|
449
|
+
def ok(ret={})
|
450
|
+
throw(:return, ret)
|
451
|
+
end
|
452
|
+
|
453
|
+
def error(msg, errs={})
|
454
|
+
@message = msg
|
455
|
+
@errors = errs
|
456
|
+
throw(:return, false)
|
457
|
+
end
|
458
|
+
|
459
|
+
private
|
460
|
+
def validate
|
461
|
+
# Validate standard input
|
462
|
+
@safe_params = @params.dup
|
463
|
+
input = self.class.input
|
464
|
+
|
465
|
+
if input
|
466
|
+
# First check layout
|
467
|
+
input.check_layout(@safe_params)
|
468
|
+
|
469
|
+
# Then filter allowed params
|
470
|
+
case input.layout
|
471
|
+
when :object_list, :hash_list
|
472
|
+
@safe_params[input.namespace].map! do |obj|
|
473
|
+
@authorization.filter_input(
|
474
|
+
self.class.input.params,
|
475
|
+
self.class.model_adapter(self.class.input.layout).input(obj))
|
476
|
+
end
|
477
|
+
|
478
|
+
else
|
479
|
+
@safe_params[input.namespace] = @authorization.filter_input(
|
480
|
+
self.class.input.params,
|
481
|
+
self.class.model_adapter(self.class.input.layout).input(@safe_params[input.namespace]))
|
482
|
+
end
|
483
|
+
|
484
|
+
# Remove duplicit key
|
485
|
+
@safe_params.delete(input.namespace.to_s)
|
486
|
+
|
487
|
+
# Now check required params, convert types and set defaults
|
488
|
+
input.validate(@safe_params)
|
489
|
+
end
|
490
|
+
|
491
|
+
# Validate metadata input
|
492
|
+
auth = Authorization.new { allow }
|
493
|
+
@metadata = {}
|
494
|
+
|
495
|
+
return if input && %i(object_list hash_list).include?(input.layout)
|
496
|
+
|
497
|
+
[:object, :global].each do |v|
|
498
|
+
meta = self.class.meta(v)
|
499
|
+
next unless meta
|
500
|
+
|
501
|
+
raw_meta = nil
|
502
|
+
|
503
|
+
[Metadata.namespace, Metadata.namespace.to_s].each do |ns|
|
504
|
+
params = v == :object ? (@params[input.namespace] && @params[input.namespace][ns]) : @params[ns]
|
505
|
+
next unless params
|
506
|
+
|
507
|
+
raw_meta = auth.filter_input(
|
508
|
+
meta.input.params,
|
509
|
+
self.class.model_adapter(meta.input.layout).input(params)
|
510
|
+
)
|
511
|
+
|
512
|
+
break if raw_meta
|
513
|
+
end
|
514
|
+
|
515
|
+
next unless raw_meta
|
516
|
+
|
517
|
+
@metadata.update(meta.input.validate(raw_meta))
|
518
|
+
end
|
519
|
+
end
|
520
|
+
end
|
521
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module HaveAPI
|
2
|
+
module Actions
|
3
|
+
module Default
|
4
|
+
class Index < Action
|
5
|
+
route ''
|
6
|
+
http_method :get
|
7
|
+
aliases %i(list)
|
8
|
+
|
9
|
+
meta(:global) do
|
10
|
+
input do
|
11
|
+
bool :count, label: 'Return the count of all items', default: false
|
12
|
+
end
|
13
|
+
|
14
|
+
output do
|
15
|
+
integer :total_count, label: 'Total count of all items'
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
include HaveAPI::Actions::Paginable
|
20
|
+
|
21
|
+
def pre_exec
|
22
|
+
set_meta(total_count: count) if meta[:count]
|
23
|
+
end
|
24
|
+
|
25
|
+
# Return the total count of items.
|
26
|
+
def count
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class Create < Action
|
32
|
+
route ''
|
33
|
+
http_method :post
|
34
|
+
aliases %i(new)
|
35
|
+
end
|
36
|
+
|
37
|
+
class Show < Action
|
38
|
+
route ->(r){ r.singular ? '' : ':%{resource}_id' }
|
39
|
+
http_method :get
|
40
|
+
aliases %i(find)
|
41
|
+
end
|
42
|
+
|
43
|
+
class Update < Action
|
44
|
+
route ->(r){ r.singular ? '' : ':%{resource}_id' }
|
45
|
+
http_method :put
|
46
|
+
end
|
47
|
+
|
48
|
+
class Delete < Action
|
49
|
+
route ->(r){ r.singular ? '' : ':%{resource}_id' }
|
50
|
+
http_method :delete
|
51
|
+
aliases %i(destroy)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module HaveAPI::Actions
|
2
|
+
module Paginable
|
3
|
+
def self.included(action)
|
4
|
+
action.input do
|
5
|
+
integer :offset, label: 'Offset', desc: 'The offset of the first object',
|
6
|
+
default: 0
|
7
|
+
integer :limit, label: 'Limit', desc: 'The number of objects to retrieve',
|
8
|
+
default: 25
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
data/lib/haveapi/api.rb
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
module HaveAPI
|
2
|
+
# Return a list of all resources or yield them if block is given.
|
3
|
+
def self.resources(module_name) # yields: resource
|
4
|
+
ret = []
|
5
|
+
|
6
|
+
module_name.constants.select do |c|
|
7
|
+
obj = module_name.const_get(c)
|
8
|
+
|
9
|
+
if obj.obj_type == :resource
|
10
|
+
if block_given?
|
11
|
+
yield obj
|
12
|
+
else
|
13
|
+
ret << obj
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
ret
|
19
|
+
end
|
20
|
+
|
21
|
+
# Iterate through all resources and return those for which yielded block
|
22
|
+
# returned true.
|
23
|
+
def self.filter_resources(module_name)
|
24
|
+
ret = []
|
25
|
+
|
26
|
+
resources(module_name) do |r|
|
27
|
+
ret << r if yield(r)
|
28
|
+
end
|
29
|
+
|
30
|
+
ret
|
31
|
+
end
|
32
|
+
|
33
|
+
# Return list of resources for version +v+.
|
34
|
+
def self.get_version_resources(module_name, v)
|
35
|
+
filter_resources(module_name) do |r|
|
36
|
+
r.version.is_a?(Array) ? r.version.include?(v) : (r.version == v || r.version == :all)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Return a list of all API versions.
|
41
|
+
def self.get_versions(module_name)
|
42
|
+
ret = []
|
43
|
+
|
44
|
+
resources(module_name) do |r|
|
45
|
+
ret << r.version unless ret.include?(r.version)
|
46
|
+
end
|
47
|
+
|
48
|
+
ret
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.set_module_name(name)
|
52
|
+
@module_name = name
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.module_name
|
56
|
+
@module_name
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.set_default_authenticate(chain)
|
60
|
+
@default_auth = chain
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.default_authenticate
|
64
|
+
@default_auth
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module HaveAPI
|
2
|
+
module Authentication
|
3
|
+
# Base class for authentication providers.
|
4
|
+
class Base
|
5
|
+
attr_accessor :name, :resources
|
6
|
+
|
7
|
+
def initialize(server, v)
|
8
|
+
@server = server
|
9
|
+
@version = v
|
10
|
+
setup
|
11
|
+
end
|
12
|
+
|
13
|
+
# Reimplement this method in your authentication provider.
|
14
|
+
# +request+ is passed directly from Sinatra.
|
15
|
+
def authenticate(request)
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
# Reimplement to describe provider.
|
20
|
+
def describe
|
21
|
+
{}
|
22
|
+
end
|
23
|
+
|
24
|
+
protected
|
25
|
+
# Called during API mount.
|
26
|
+
def setup
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
# Immediately return from authentication chain.
|
31
|
+
# User is not allowed to authenticate.
|
32
|
+
def deny
|
33
|
+
throw(:return)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|