antaeus-sdk 0.2.3
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/LICENSE +22 -0
- data/README.md +119 -0
- data/bin/antaeus-cli +35 -0
- data/lib/antaeus-sdk.rb +58 -0
- data/lib/antaeus-sdk/api_client.rb +105 -0
- data/lib/antaeus-sdk/api_info.rb +47 -0
- data/lib/antaeus-sdk/config.rb +54 -0
- data/lib/antaeus-sdk/exception.rb +5 -0
- data/lib/antaeus-sdk/exceptions/approval_change_failed.rb +6 -0
- data/lib/antaeus-sdk/exceptions/authentication_failure.rb +6 -0
- data/lib/antaeus-sdk/exceptions/checkin_change_failed.rb +6 -0
- data/lib/antaeus-sdk/exceptions/checkout_change_failed.rb +6 -0
- data/lib/antaeus-sdk/exceptions/immutable_instance.rb +6 -0
- data/lib/antaeus-sdk/exceptions/immutable_modification.rb +6 -0
- data/lib/antaeus-sdk/exceptions/invalid_api_client.rb +6 -0
- data/lib/antaeus-sdk/exceptions/invalid_config_data.rb +6 -0
- data/lib/antaeus-sdk/exceptions/invalid_entity.rb +6 -0
- data/lib/antaeus-sdk/exceptions/invalid_input.rb +6 -0
- data/lib/antaeus-sdk/exceptions/invalid_options.rb +6 -0
- data/lib/antaeus-sdk/exceptions/invalid_property.rb +6 -0
- data/lib/antaeus-sdk/exceptions/invalid_where_query.rb +6 -0
- data/lib/antaeus-sdk/exceptions/login_required.rb +6 -0
- data/lib/antaeus-sdk/exceptions/missing_api_client.rb +6 -0
- data/lib/antaeus-sdk/exceptions/missing_entity.rb +6 -0
- data/lib/antaeus-sdk/exceptions/missing_path.rb +6 -0
- data/lib/antaeus-sdk/exceptions/new_instance_with_id.rb +6 -0
- data/lib/antaeus-sdk/guest_api_client.rb +63 -0
- data/lib/antaeus-sdk/helpers/string.rb +24 -0
- data/lib/antaeus-sdk/resource.rb +363 -0
- data/lib/antaeus-sdk/resource_collection.rb +198 -0
- data/lib/antaeus-sdk/resources/appointment.rb +166 -0
- data/lib/antaeus-sdk/resources/group.rb +27 -0
- data/lib/antaeus-sdk/resources/guest.rb +41 -0
- data/lib/antaeus-sdk/resources/hook.rb +17 -0
- data/lib/antaeus-sdk/resources/location.rb +50 -0
- data/lib/antaeus-sdk/resources/remote_application.rb +13 -0
- data/lib/antaeus-sdk/resources/user.rb +28 -0
- data/lib/antaeus-sdk/user_api_client.rb +63 -0
- data/lib/antaeus-sdk/version.rb +7 -0
- metadata +209 -0
@@ -0,0 +1,63 @@
|
|
1
|
+
module Antaeus
|
2
|
+
class GuestAPIClient < APIClient
|
3
|
+
def authenticate(guest, pin)
|
4
|
+
@guest ||= guest
|
5
|
+
begin
|
6
|
+
raw_token_data = RestClient.post(
|
7
|
+
"#{Antaeus.config.base_url}/guests/login",
|
8
|
+
{ email: @guest, pin: pin }.to_json,
|
9
|
+
content_type: :json,
|
10
|
+
accept: :json
|
11
|
+
)
|
12
|
+
token_data = JSON.load(raw_token_data)
|
13
|
+
set_token(@guest, token_data['guest_token'])
|
14
|
+
true
|
15
|
+
rescue RestClient::Exception => e
|
16
|
+
raise Exceptions::AuthenticationFailure, e.response
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Set the token in memory for the given guest
|
21
|
+
def set_token(guest, token)
|
22
|
+
# this should probably be improved to handle race conditions
|
23
|
+
@guest ||= guest
|
24
|
+
@guest_data ||= {}
|
25
|
+
@guest_data[guest] ||= {}
|
26
|
+
@guest_data[guest][:token] = token
|
27
|
+
end
|
28
|
+
|
29
|
+
# Retrieve the token for a given guest
|
30
|
+
def get_token(guest)
|
31
|
+
return nil unless @guest
|
32
|
+
return nil unless @guest == guest
|
33
|
+
return nil unless @guest_data
|
34
|
+
return nil unless @guest_data[guest]
|
35
|
+
@guest_data[guest][:token] ? @guest_data[guest][:token].dup : nil
|
36
|
+
end
|
37
|
+
|
38
|
+
def authenticated?
|
39
|
+
if @guest && @guest_data[@guest] && @guest_data[@guest][:token]
|
40
|
+
true
|
41
|
+
else
|
42
|
+
false
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def connect
|
47
|
+
return true if connected?
|
48
|
+
|
49
|
+
if authenticated?
|
50
|
+
@rest_client = RestClient::Resource.new(
|
51
|
+
Antaeus.config.base_url,
|
52
|
+
content_type: :json,
|
53
|
+
accept: :json,
|
54
|
+
headers: {
|
55
|
+
'X-Guest-Token:': @guest_data[@guest][:token]
|
56
|
+
}
|
57
|
+
)
|
58
|
+
else
|
59
|
+
raise Exceptions::LoginRequired
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# Helpers for manipulating Strings
|
2
|
+
module Antaeus
|
3
|
+
module Helpers
|
4
|
+
# Convert CamelCase to underscored_text
|
5
|
+
# @return [String]
|
6
|
+
def to_underscore(string)
|
7
|
+
string.gsub(/::/, '/')
|
8
|
+
.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
9
|
+
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
|
10
|
+
.tr('-', '_')
|
11
|
+
.downcase
|
12
|
+
end
|
13
|
+
|
14
|
+
# Convert underscored_text to CamelCase
|
15
|
+
# @return [String]
|
16
|
+
def to_camel(string)
|
17
|
+
string.split('_').map(&:capitalize).join
|
18
|
+
end
|
19
|
+
|
20
|
+
def humanize(string)
|
21
|
+
string.to_s.gsub(/_id$/, "").gsub(/_/, " ").capitalize
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,363 @@
|
|
1
|
+
module Antaeus
|
2
|
+
# A generic API resource
|
3
|
+
class Resource
|
4
|
+
attr_accessor :client
|
5
|
+
attr_reader :errors
|
6
|
+
include Comparable
|
7
|
+
|
8
|
+
def self.properties
|
9
|
+
@properties ||= {}
|
10
|
+
end
|
11
|
+
|
12
|
+
# Delayed properties are evaluated when an instance is created
|
13
|
+
# WARNING: Sanity checking of the end-result isn't possible. Use with care
|
14
|
+
def self.delayed_property(options = {}, &block)
|
15
|
+
@properties ||= {}
|
16
|
+
|
17
|
+
@properties[block] = options
|
18
|
+
end
|
19
|
+
|
20
|
+
# Can this type of resource be changed client-side?
|
21
|
+
def self.immutable(status)
|
22
|
+
unless status.is_a?(TrueClass) || status.is_a?(FalseClass)
|
23
|
+
raise Exceptions::InvalidInput
|
24
|
+
end
|
25
|
+
@immutable = status
|
26
|
+
end
|
27
|
+
|
28
|
+
# Check if a resource class is immutable
|
29
|
+
def self.immutable?
|
30
|
+
@immutable ||= false
|
31
|
+
end
|
32
|
+
|
33
|
+
# Define a property for a model
|
34
|
+
# TODO: add more validations on options and names
|
35
|
+
def self.property(name, options = {})
|
36
|
+
@properties ||= {}
|
37
|
+
|
38
|
+
invalid_prop_names = [
|
39
|
+
:>, :<, :'=', :class, :def,
|
40
|
+
:%, :'!', :/, :'.', :'?', :*, :'{}',
|
41
|
+
:'[]'
|
42
|
+
]
|
43
|
+
|
44
|
+
raise(Exception::InvalidProperty) if invalid_prop_names.include?(name.to_sym)
|
45
|
+
@properties[name.to_sym] = options
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.path(kind, uri)
|
49
|
+
@paths ||= {}
|
50
|
+
@paths[kind.to_sym] = uri
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.paths
|
54
|
+
@paths ||= {}
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.path_for(kind)
|
58
|
+
paths[kind.to_sym]
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.gen_property_methods
|
62
|
+
properties.each do |prop, opts|
|
63
|
+
if prop.is_a?(Proc)
|
64
|
+
prop = prop.call.to_s.to_sym
|
65
|
+
end
|
66
|
+
|
67
|
+
# Getter methods
|
68
|
+
define_method(prop) do
|
69
|
+
if @lazy && !@entity.key?(prop.to_s)
|
70
|
+
reload
|
71
|
+
end
|
72
|
+
if opts[:type] && opts[:type] == :time
|
73
|
+
if @entity[prop.to_s] && !@entity[prop.to_s].to_s.empty?
|
74
|
+
Time.parse(@entity[prop.to_s].to_s)
|
75
|
+
else
|
76
|
+
nil
|
77
|
+
end
|
78
|
+
else
|
79
|
+
@entity[prop.to_s]
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# Setter methods (don't make one for obviously read-only properties)
|
84
|
+
unless prop.match /\?$/ || opts[:read_only]
|
85
|
+
define_method("#{prop}=".to_sym) do |value|
|
86
|
+
raise Exceptions::ImmutableModification if immutable?
|
87
|
+
if opts[:type] == :time
|
88
|
+
@entity[prop.to_s] = Time.parse(value.to_s).utc
|
89
|
+
else
|
90
|
+
@entity[prop.to_s] = value
|
91
|
+
end
|
92
|
+
@tainted = true
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def self.all(options = {})
|
99
|
+
validate_options(options)
|
100
|
+
options[:lazy] = true unless options.key?(:lazy)
|
101
|
+
|
102
|
+
# TODO: add validation checks for the required pieces
|
103
|
+
fail(Exceptions::MissingPath) unless path_for(:all)
|
104
|
+
|
105
|
+
root = to_underscore(self.name.split('::').last.en.plural)
|
106
|
+
this_path = options[:lazy] ? path_for(:all) : "#{path_for(:all)}?lazy=false"
|
107
|
+
|
108
|
+
ResourceCollection.new(
|
109
|
+
options[:client].get(this_path)[root].collect do |record|
|
110
|
+
self.new(
|
111
|
+
entity: record,
|
112
|
+
lazy: (options[:lazy] ? true : false),
|
113
|
+
tainted: false,
|
114
|
+
client: options[:client]
|
115
|
+
)
|
116
|
+
end,
|
117
|
+
type: self,
|
118
|
+
client: options[:client]
|
119
|
+
)
|
120
|
+
end
|
121
|
+
|
122
|
+
def self.get(id, options = {})
|
123
|
+
validate_options(options)
|
124
|
+
fail(Exceptions::MissingPath) unless path_for(:all)
|
125
|
+
|
126
|
+
root = to_underscore(self.name.split('::').last)
|
127
|
+
self.new(
|
128
|
+
entity: options[:client].get("#{path_for(:all)}/#{id}")[root],
|
129
|
+
lazy: false,
|
130
|
+
tainted: false,
|
131
|
+
client: options[:client]
|
132
|
+
)
|
133
|
+
end
|
134
|
+
|
135
|
+
def self.where(attribute, value, options = {})
|
136
|
+
validate_options(options)
|
137
|
+
options[:comparison] ||= '=='
|
138
|
+
all(lazy: (options[:lazy] ? true : false), client: options[:client]).where(attribute, value, comparison: options[:comparison])
|
139
|
+
end
|
140
|
+
|
141
|
+
def destroy
|
142
|
+
fail Exceptions::ImmutableInstance if immutable?
|
143
|
+
unless new?
|
144
|
+
@client.delete("#{path_for(:all)}/#{id}")
|
145
|
+
@lazy = false
|
146
|
+
@tainted = true
|
147
|
+
@entity.delete('id')
|
148
|
+
end
|
149
|
+
true
|
150
|
+
end
|
151
|
+
|
152
|
+
def fresh?
|
153
|
+
!tainted?
|
154
|
+
end
|
155
|
+
|
156
|
+
# ActiveRecord ActiveModel::Name compatibility method
|
157
|
+
def self.human
|
158
|
+
humanize(i18n_key)
|
159
|
+
end
|
160
|
+
|
161
|
+
# ActiveRecord ActiveModel::Name compatibility method
|
162
|
+
def self.i18n_key
|
163
|
+
to_underscore(self.name.split('::').last)
|
164
|
+
end
|
165
|
+
|
166
|
+
def id
|
167
|
+
@entity['id']
|
168
|
+
end
|
169
|
+
|
170
|
+
def immutable?
|
171
|
+
self.class.immutable?
|
172
|
+
end
|
173
|
+
|
174
|
+
def initialize(options = {})
|
175
|
+
raise Exceptions::InvalidOptions unless options.is_a?(Hash)
|
176
|
+
raise Exceptions::MissingAPIClient unless options[:client]
|
177
|
+
raise Exceptions::InvalidAPIClient unless options[:client].is_a?(APIClient)
|
178
|
+
|
179
|
+
if options[:entity]
|
180
|
+
raise Exceptions::MissingEntity unless options[:entity]
|
181
|
+
raise Exceptions::InvalidEntity unless options[:entity].is_a?(Hash)
|
182
|
+
@entity = options[:entity]
|
183
|
+
else
|
184
|
+
@entity = {}
|
185
|
+
end
|
186
|
+
# Allows lazy-loading if we're told this is a lazy instance
|
187
|
+
# This means only the minimal attributes were fetched.
|
188
|
+
# This shouldn't be set by end-users.
|
189
|
+
@lazy = options.key?(:lazy) ? options[:lazy] : false
|
190
|
+
# This allows local, user-created instances to be differentiated from fetched
|
191
|
+
# instances from the backend API. This shouldn't be set by end-users.
|
192
|
+
@tainted = options.key?(:tainted) ? options[:tainted] : true
|
193
|
+
# This is the API Client used to get data for this resource
|
194
|
+
@client = options[:client]
|
195
|
+
@errors = {}
|
196
|
+
|
197
|
+
if immutable? && @tainted
|
198
|
+
raise Exceptions::ImmutableInstance
|
199
|
+
end
|
200
|
+
|
201
|
+
# The 'id' field should not be set manually
|
202
|
+
if @entity.key?('id')
|
203
|
+
raise Exceptions::NewInstanceWithID unless !@tainted
|
204
|
+
end
|
205
|
+
|
206
|
+
self.class.class_eval do
|
207
|
+
gen_property_methods
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
# ActiveRecord ActiveModel::Name compatibility method
|
212
|
+
def model_name
|
213
|
+
self.class
|
214
|
+
end
|
215
|
+
|
216
|
+
def new?
|
217
|
+
!@entity.key?('id')
|
218
|
+
end
|
219
|
+
|
220
|
+
# ActiveRecord ActiveModel::Name compatibility method
|
221
|
+
def self.param_key
|
222
|
+
singular_route_key.to_sym
|
223
|
+
end
|
224
|
+
|
225
|
+
def paths
|
226
|
+
self.class.paths
|
227
|
+
end
|
228
|
+
|
229
|
+
def path_for(kind)
|
230
|
+
self.class.path_for(kind)
|
231
|
+
end
|
232
|
+
|
233
|
+
# ActiveRecord ActiveModel::Model compatibility method
|
234
|
+
def persisted?
|
235
|
+
!new?
|
236
|
+
end
|
237
|
+
|
238
|
+
def reload
|
239
|
+
root = to_underscore(self.class.name.split('::').last)
|
240
|
+
|
241
|
+
if new?
|
242
|
+
# Can't reload a new resource
|
243
|
+
false
|
244
|
+
else
|
245
|
+
@entity = @client.get("#{path_for(:all)}/#{id}")[root]
|
246
|
+
@lazy = false
|
247
|
+
@tainted = false
|
248
|
+
true
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
# ActiveRecord ActiveModel::Name compatibility method
|
253
|
+
def self.route_key
|
254
|
+
singular_route_key.en.plural
|
255
|
+
end
|
256
|
+
|
257
|
+
# ActiveRecord ActiveModel::Name compatibility method
|
258
|
+
def self.singular_route_key
|
259
|
+
to_underscore(self.name.split('::').last)
|
260
|
+
end
|
261
|
+
|
262
|
+
def save
|
263
|
+
root = to_underscore(self.class.name.split('::').last)
|
264
|
+
|
265
|
+
if new?
|
266
|
+
@entity = @client.post("#{path_for(:all)}", @entity)[root]
|
267
|
+
@lazy = false
|
268
|
+
else
|
269
|
+
@client.put("#{path_for(:all)}/#{id}", @entity)
|
270
|
+
end
|
271
|
+
@tainted = false
|
272
|
+
true
|
273
|
+
end
|
274
|
+
|
275
|
+
def self.search(query, options = {})
|
276
|
+
validate_options(options)
|
277
|
+
is_lazy = options.key?(:lazy) ? options[:lazy] : false
|
278
|
+
request_uri = "#{path_for(:all)}/search?q=#{query}"
|
279
|
+
request_uri << '&lazy=false' if !is_lazy
|
280
|
+
root = to_underscore(self.name.split('::').last.en.plural)
|
281
|
+
|
282
|
+
ResourceCollection.new(
|
283
|
+
options[:client].get(request_uri)[root].collect do |record|
|
284
|
+
self.new(
|
285
|
+
entity: record,
|
286
|
+
lazy: is_lazy,
|
287
|
+
tainted: false,
|
288
|
+
client: options[:client]
|
289
|
+
)
|
290
|
+
end,
|
291
|
+
type: self,
|
292
|
+
client: options[:client]
|
293
|
+
)
|
294
|
+
end
|
295
|
+
|
296
|
+
def tainted?
|
297
|
+
@tainted ? true : false
|
298
|
+
end
|
299
|
+
|
300
|
+
# ActiveRecord ActiveModel::Conversion compatibility method
|
301
|
+
def to_key
|
302
|
+
new? ? [] : [id]
|
303
|
+
end
|
304
|
+
|
305
|
+
# ActiveRecord ActiveModel::Conversion compatibility method
|
306
|
+
def to_model
|
307
|
+
self
|
308
|
+
end
|
309
|
+
|
310
|
+
# ActiveRecord ActiveModel::Conversion compatibility method
|
311
|
+
def to_param
|
312
|
+
new? ? nil : id.to_s
|
313
|
+
end
|
314
|
+
|
315
|
+
def <=>(other)
|
316
|
+
if id < other.id
|
317
|
+
-1
|
318
|
+
elsif id > other.id
|
319
|
+
1
|
320
|
+
elsif id == other.id
|
321
|
+
0
|
322
|
+
else
|
323
|
+
raise Exceptions::InvalidInput
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
327
|
+
# ActiveRecord ActiveModel compatibility method
|
328
|
+
def update(params)
|
329
|
+
new_params = {}
|
330
|
+
# need to convert multi-part datetime params
|
331
|
+
params.each do |key, value|
|
332
|
+
if key.match /([^(]+)\(1i/
|
333
|
+
actual_key = key.match(/([^(]+)\(/)[1]
|
334
|
+
new_params[actual_key] = DateTime.new(
|
335
|
+
params["#{actual_key}(1i)"].to_i,
|
336
|
+
params["#{actual_key}(2i)"].to_i,
|
337
|
+
params["#{actual_key}(3i)"].to_i,
|
338
|
+
params["#{actual_key}(4i)"].to_i,
|
339
|
+
params["#{actual_key}(5i)"].to_i
|
340
|
+
)
|
341
|
+
elsif key.match /([^(]+)\(/
|
342
|
+
# skip this... already got it
|
343
|
+
else
|
344
|
+
new_params[key] = value
|
345
|
+
end
|
346
|
+
end
|
347
|
+
|
348
|
+
new_params.each do |key, value|
|
349
|
+
raise Exceptions::InvalidProperty unless self.respond_to?("#{key}=".to_sym)
|
350
|
+
send("#{key}=".to_sym, value)
|
351
|
+
end
|
352
|
+
save
|
353
|
+
end
|
354
|
+
|
355
|
+
private
|
356
|
+
|
357
|
+
def self.validate_options(options)
|
358
|
+
raise Exceptions::InvalidOptions unless options.is_a?(Hash)
|
359
|
+
raise Exceptions::MissingAPIClient unless options[:client]
|
360
|
+
raise Exceptions::InvalidAPIClient unless options[:client].is_a?(APIClient)
|
361
|
+
end
|
362
|
+
end
|
363
|
+
end
|