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.
Files changed (41) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +22 -0
  3. data/README.md +119 -0
  4. data/bin/antaeus-cli +35 -0
  5. data/lib/antaeus-sdk.rb +58 -0
  6. data/lib/antaeus-sdk/api_client.rb +105 -0
  7. data/lib/antaeus-sdk/api_info.rb +47 -0
  8. data/lib/antaeus-sdk/config.rb +54 -0
  9. data/lib/antaeus-sdk/exception.rb +5 -0
  10. data/lib/antaeus-sdk/exceptions/approval_change_failed.rb +6 -0
  11. data/lib/antaeus-sdk/exceptions/authentication_failure.rb +6 -0
  12. data/lib/antaeus-sdk/exceptions/checkin_change_failed.rb +6 -0
  13. data/lib/antaeus-sdk/exceptions/checkout_change_failed.rb +6 -0
  14. data/lib/antaeus-sdk/exceptions/immutable_instance.rb +6 -0
  15. data/lib/antaeus-sdk/exceptions/immutable_modification.rb +6 -0
  16. data/lib/antaeus-sdk/exceptions/invalid_api_client.rb +6 -0
  17. data/lib/antaeus-sdk/exceptions/invalid_config_data.rb +6 -0
  18. data/lib/antaeus-sdk/exceptions/invalid_entity.rb +6 -0
  19. data/lib/antaeus-sdk/exceptions/invalid_input.rb +6 -0
  20. data/lib/antaeus-sdk/exceptions/invalid_options.rb +6 -0
  21. data/lib/antaeus-sdk/exceptions/invalid_property.rb +6 -0
  22. data/lib/antaeus-sdk/exceptions/invalid_where_query.rb +6 -0
  23. data/lib/antaeus-sdk/exceptions/login_required.rb +6 -0
  24. data/lib/antaeus-sdk/exceptions/missing_api_client.rb +6 -0
  25. data/lib/antaeus-sdk/exceptions/missing_entity.rb +6 -0
  26. data/lib/antaeus-sdk/exceptions/missing_path.rb +6 -0
  27. data/lib/antaeus-sdk/exceptions/new_instance_with_id.rb +6 -0
  28. data/lib/antaeus-sdk/guest_api_client.rb +63 -0
  29. data/lib/antaeus-sdk/helpers/string.rb +24 -0
  30. data/lib/antaeus-sdk/resource.rb +363 -0
  31. data/lib/antaeus-sdk/resource_collection.rb +198 -0
  32. data/lib/antaeus-sdk/resources/appointment.rb +166 -0
  33. data/lib/antaeus-sdk/resources/group.rb +27 -0
  34. data/lib/antaeus-sdk/resources/guest.rb +41 -0
  35. data/lib/antaeus-sdk/resources/hook.rb +17 -0
  36. data/lib/antaeus-sdk/resources/location.rb +50 -0
  37. data/lib/antaeus-sdk/resources/remote_application.rb +13 -0
  38. data/lib/antaeus-sdk/resources/user.rb +28 -0
  39. data/lib/antaeus-sdk/user_api_client.rb +63 -0
  40. data/lib/antaeus-sdk/version.rb +7 -0
  41. metadata +209 -0
@@ -0,0 +1,5 @@
1
+ module Antaeus
2
+ # Generic Exception definition
3
+ class Exception < StandardError
4
+ end
5
+ end
@@ -0,0 +1,6 @@
1
+ module Antaeus
2
+ module Exceptions
3
+ class ApprovalChangeFailed < Exception
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module Antaeus
2
+ module Exceptions
3
+ class AuthenticationFailure < Exception
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module Antaeus
2
+ module Exceptions
3
+ class CheckinChangeFailed < Exception
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module Antaeus
2
+ module Exceptions
3
+ class CheckoutChangeFailed < Exception
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module Antaeus
2
+ module Exceptions
3
+ class ImmutableInstance < Exception
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module Antaeus
2
+ module Exceptions
3
+ class ImmutableModification < Exception
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module Antaeus
2
+ module Exceptions
3
+ class InvalidAPIClient < Exception
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module Antaeus
2
+ module Exceptions
3
+ class InvalidConfigData < Exception
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module Antaeus
2
+ module Exceptions
3
+ class InvalidEntity < Exception
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module Antaeus
2
+ module Exceptions
3
+ class InvalidInput < Exception
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module Antaeus
2
+ module Exceptions
3
+ class InvalidOptions < Exception
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module Antaeus
2
+ module Exceptions
3
+ class InvalidProperty < Exception
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module Antaeus
2
+ module Exceptions
3
+ class InvalidWhereQuery < Exception
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module Antaeus
2
+ module Exceptions
3
+ class LoginRequired < Exception
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module Antaeus
2
+ module Exceptions
3
+ class MissingAPIClient < Exception
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module Antaeus
2
+ module Exceptions
3
+ class MissingEntity < Exception
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module Antaeus
2
+ module Exceptions
3
+ class MissingPath < Exception
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module Antaeus
2
+ module Exceptions
3
+ class NewInstanceWithID < Exception
4
+ end
5
+ end
6
+ end
@@ -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