antaeus-sdk 0.2.3

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