xoopit-cloud_query 0.1.5 → 0.2.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.
- data/README.markdown +10 -10
- data/lib/cloud_query/client.rb +370 -0
- data/lib/cloud_query/crypto.rb +28 -0
- data/lib/cloud_query/field.rb +22 -0
- data/lib/cloud_query/request.rb +64 -0
- data/lib/cloud_query/schema.rb +18 -0
- data/lib/cloud_query/x64.rb +42 -0
- data/lib/cloud_query.rb +3 -1
- data/spec/cloudquery_spec.rb +3 -3
- data/spec/lib/cloud_query/field_spec.rb +43 -0
- data/spec/lib/cloud_query/schema_spec.rb +34 -0
- data/spec/lib/cloud_query/x64_spec.rb +14 -0
- data/tasks/distribution.rb +38 -0
- data/tasks/testing.rb +14 -0
- metadata +40 -13
- data/Rakefile +0 -80
- data/VERSION.yml +0 -4
- data/spec/example_schema.xml +0 -26
- data/spec/spec_helper.rb +0 -11
data/README.markdown
CHANGED
@@ -1,7 +1,7 @@
|
|
1
|
-
|
1
|
+
CloudQuery
|
2
2
|
==========
|
3
3
|
|
4
|
-
Client for Xoopit's
|
4
|
+
Client for Xoopit's CloudQuery API
|
5
5
|
|
6
6
|
Install
|
7
7
|
-------
|
@@ -11,21 +11,21 @@ care of it. If not, `sudo gem install json rack taf2-curb` will do it.
|
|
11
11
|
|
12
12
|
Be sure you've run `gem sources -a http://gems.github.com` once on your system. Then:
|
13
13
|
|
14
|
-
sudo gem install xoopit-cloudquery
|
14
|
+
sudo gem install xoopit-cloudquery-ruby
|
15
15
|
|
16
16
|
Simple contacts application example
|
17
17
|
-----------------------------------
|
18
18
|
|
19
19
|
> require 'rubygems'
|
20
20
|
=> true
|
21
|
-
> require '
|
21
|
+
> require 'cloud_query'
|
22
22
|
=> true
|
23
|
-
> include
|
23
|
+
> include CloudQuery
|
24
24
|
=> Object
|
25
25
|
> secret = Client.get_secret(<account_name>, <password>)
|
26
26
|
=> "your secret appears here"
|
27
27
|
> c = Client.new(:account => '<account_name>', :secret => secret)
|
28
|
-
=> #<
|
28
|
+
=> #<CloudQuery::Client:0x10b1b24 @secure=true, @secret="your secret appears here", @account="<account_name>", @document_id_method=nil>
|
29
29
|
> c.add_indexes('superheroes')
|
30
30
|
=> {"result"=>["kMzzzybpqpY"], "size"=>1, "STATUS"=>200}
|
31
31
|
> c.add_schema(File.open('simple.contact.xml'))
|
@@ -35,10 +35,10 @@ Simple contacts application example
|
|
35
35
|
'simple.contact.email' => ['steve.rogers@example.com','captain.america@marvel.com'],
|
36
36
|
'simple.contact.telephone' => ['555-555-5555','123-456-6789'],
|
37
37
|
'simple.contact.address' => ['Lower East Side, NY NY'],
|
38
|
-
'simple.contact.birthday' =>
|
38
|
+
'simple.contact.birthday' => Time.parse('July 4, 1917').to_i_milliseconds,
|
39
39
|
'simple.contact.note' => 'Captain America!',
|
40
40
|
}
|
41
|
-
=> {"simple.contact.birthday"
|
41
|
+
=> {"simple.contact.birthday"=>-1656604800000, "simple.contact.address"=>["Lower East Side, NY NY"], "simple.contact.telephone"=>["555-555-5555", "123-456-6789"], "simple.contact.note"=>"Captain America!", "simple.contact.email"=>["steve.rogers@example.com", "captain.america@marvel.com"], "simple.contact.name"=>"Steve Rogers"}
|
42
42
|
> c.add_documents('superheroes', doc, 'simple.contact')
|
43
43
|
=> {"result"=>["nDLCNLPo3oHtxANzG4YBn5kMzzzybpqpY"], "size"=>1, "STATUS"=>201}
|
44
44
|
> docs = [
|
@@ -47,7 +47,7 @@ Simple contacts application example
|
|
47
47
|
'simple.contact.email' => ['clark.kent@example.com','superman@dc.com'],
|
48
48
|
'simple.contact.telephone' => ['555-123-1234','555-456-6789'],
|
49
49
|
'simple.contact.address' => ['344 Clinton St., Apt. #3B, Metropolis', 'The Fortess of Solitude, North Pole'],
|
50
|
-
'simple.contact.birthday' =>
|
50
|
+
'simple.contact.birthday' => Time.parse('June 18, 1938').to_i_milliseconds,
|
51
51
|
'simple.contact.note' => 'Superhuman strength, speed, stamina, durability, senses, intelligence, regeneration, and longevity; super breath, heat vision, x-ray vision and flight. Member of the justice league.'
|
52
52
|
},
|
53
53
|
{
|
@@ -55,7 +55,7 @@ Simple contacts application example
|
|
55
55
|
'simple.contact.email' => ['bruce.wayne@example.com','batman@dc.com'],
|
56
56
|
'simple.contact.telephone' => ['555-123-6666','555-456-6666'],
|
57
57
|
'simple.contact.address' => ['1007 Mountain Drive, Gotham', 'The Batcave, Gotham'],
|
58
|
-
'simple.contact.birthday' =>
|
58
|
+
'simple.contact.birthday' => Time.parse('February 19, 1939').to_i_milliseconds,
|
59
59
|
'simple.contact.note' => 'Sidekick is Robin. Has problems with the Joker. Member of e justice league.'
|
60
60
|
}
|
61
61
|
]
|
@@ -0,0 +1,370 @@
|
|
1
|
+
module CloudQuery
|
2
|
+
class Client
|
3
|
+
attr_reader :account
|
4
|
+
attr_writer :secret
|
5
|
+
|
6
|
+
# Create a new instance of the client
|
7
|
+
#
|
8
|
+
# It's highly recommended to set options <tt>:account</tt>
|
9
|
+
# and <tt>:secret</tt>. Creating a client without an account
|
10
|
+
# and secret isn't very useful.
|
11
|
+
#
|
12
|
+
# ==== Acceptable options:
|
13
|
+
# :account => <account name> (default => nil)
|
14
|
+
# :secret => <API secret> (default => nil)
|
15
|
+
# :document_id_method => <method name> (default => nil)
|
16
|
+
# :secure => Boolean (use HTTPS, default => true)
|
17
|
+
#
|
18
|
+
# If +document_id_method+ is set, it will be called on each
|
19
|
+
# document as a part of +add_documents+ and +update_documents+
|
20
|
+
# which should inject an <tt>'#.#'</tt> key-value pair as a
|
21
|
+
# simple way to tie app PKs to doc ids.
|
22
|
+
def initialize(options={})
|
23
|
+
# unless options[:account] && options[:secret]
|
24
|
+
# raise "Client requires :account => <account name> and :secret => <secret>"
|
25
|
+
# end
|
26
|
+
|
27
|
+
@account = options[:account]
|
28
|
+
@secret = options[:secret]
|
29
|
+
|
30
|
+
@secure = options[:secure] != false # must pass false for insecure
|
31
|
+
|
32
|
+
@document_id_method = options[:document_id_method]
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
## Account management
|
37
|
+
|
38
|
+
# Retrieve the API secret for an +account+, using the +password+ (uses HTTPS)
|
39
|
+
def self.get_secret(account, password)
|
40
|
+
auth = Request.new(:path => "#{PATH}/auth")
|
41
|
+
curl = Curl::Easy.new(auth.url) do |c|
|
42
|
+
c.enable_cookies = true
|
43
|
+
c.cookiejar = COOKIE_JAR
|
44
|
+
end
|
45
|
+
params = Rack::Utils.build_query({"name" => account, "password" => password})
|
46
|
+
curl.http_post(params)
|
47
|
+
|
48
|
+
if (curl.response_code/100) == 2
|
49
|
+
curl.url = Request.new(:path => "#{PATH}/#{API_PATHS[:account]}/#{account}").url
|
50
|
+
curl.http_get
|
51
|
+
response = JSON.parse(curl.body_str)
|
52
|
+
response['result']['secret']
|
53
|
+
else
|
54
|
+
STDERR.puts "Error: #{curl.response_code} #{Rack::Utils::HTTP_STATUS_CODES[curl.response_code]}"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# change password
|
59
|
+
# optionally change the secret
|
60
|
+
def self.change_password(account, old_password, new_password, new_secret=nil)
|
61
|
+
secret = get_secret(account, old_password)
|
62
|
+
c = Client.new(:account => account, :secret => secret, :secure => true)
|
63
|
+
a = c.get_account()['result']
|
64
|
+
a['password'] = new_password
|
65
|
+
a.delete('secret')
|
66
|
+
if new_secret != nil
|
67
|
+
a['secret'] = new_secret
|
68
|
+
end
|
69
|
+
c.update_account(a)
|
70
|
+
return (new_secret != nil ? new_secret : secret);
|
71
|
+
end
|
72
|
+
|
73
|
+
# Get the account document
|
74
|
+
def get_account
|
75
|
+
send_request get(account_path)
|
76
|
+
end
|
77
|
+
|
78
|
+
# Update the account document.
|
79
|
+
#
|
80
|
+
# Use this method to change the API secret:
|
81
|
+
# update_account({'secret' => 'your-new-secret'})
|
82
|
+
def update_account(account_doc={})
|
83
|
+
body = JSON.generate(account_doc)
|
84
|
+
send_request put(account_path, body)
|
85
|
+
end
|
86
|
+
|
87
|
+
# Delete the account.
|
88
|
+
#
|
89
|
+
# ==== BEWARE: THIS WILL ACTUALLY DELETE YOUR ACCOUNT.
|
90
|
+
def delete_account!
|
91
|
+
send_request delete(account_path)
|
92
|
+
end
|
93
|
+
|
94
|
+
|
95
|
+
## Schema management
|
96
|
+
|
97
|
+
# Add a schema to the account. +xml+ can be a +String+
|
98
|
+
# or +File+-like (responds to <tt>:read</tt>)
|
99
|
+
def add_schema(xml)
|
100
|
+
body = xml.respond_to?(:read) ? xml.read : xml
|
101
|
+
request = post(build_path(API_PATHS[:schema]), body)
|
102
|
+
send_request(request, CONTENT_TYPES[:xml])
|
103
|
+
end
|
104
|
+
|
105
|
+
# Delete a schema from the account, by name
|
106
|
+
# if cascade is true, all documents with the specified schema will be deleted
|
107
|
+
# (use with care)
|
108
|
+
def delete_schema(schema_name, cascade=nil)
|
109
|
+
c = cascade ? {:cascade => 'true'} : nil
|
110
|
+
send_request delete(build_path(
|
111
|
+
API_PATHS[:schema],
|
112
|
+
Rack::Utils.escape("$.name:\"#{schema_name}\"")
|
113
|
+
), c)
|
114
|
+
end
|
115
|
+
|
116
|
+
# Get the schemas for the account.
|
117
|
+
#
|
118
|
+
# NOTE: returned format is not the same as accepted for input
|
119
|
+
def get_schemas
|
120
|
+
send_request get(build_path(API_PATHS[:schema]))
|
121
|
+
end
|
122
|
+
|
123
|
+
|
124
|
+
## Index management
|
125
|
+
|
126
|
+
# Add one or more indexes to the account, by name or id
|
127
|
+
def add_indexes(*indexes)
|
128
|
+
body = JSON.generate(indexes.flatten)
|
129
|
+
send_request post(build_path(API_PATHS[:indexes]), body)
|
130
|
+
end
|
131
|
+
|
132
|
+
# Delete one or more indexes from the account, by name or id
|
133
|
+
# <tt>indexes = '*'</tt> will delete all indexes
|
134
|
+
def delete_indexes(*indexes)
|
135
|
+
indexes = url_pipe_join(indexes)
|
136
|
+
send_request delete(build_path(API_PATHS[:indexes], indexes))
|
137
|
+
end
|
138
|
+
|
139
|
+
# Get the indexes from the account. Returns a list of ids
|
140
|
+
def get_indexes
|
141
|
+
send_request get(build_path(API_PATHS[:indexes]))
|
142
|
+
end
|
143
|
+
|
144
|
+
|
145
|
+
## Document management
|
146
|
+
|
147
|
+
# Add documents to the specified +index+
|
148
|
+
#
|
149
|
+
# <tt>index = name</tt> or +id+, <tt>docs = {}</tt> or +Array+ of <tt>{}</tt>.
|
150
|
+
#
|
151
|
+
# Documents with key <tt>'#.#'</tt> and an existing value will be updated.
|
152
|
+
#
|
153
|
+
# If +schemas+ is not +nil+, ensures existence of the
|
154
|
+
# specified schemas on each document.
|
155
|
+
def add_documents(index, docs, schemas=[], fieldmode=nil)
|
156
|
+
fm = fieldmode != nil ? {:fieldmode => fieldmode} : nil
|
157
|
+
request = post(
|
158
|
+
build_path(API_PATHS[:documents], index, url_pipe_join(schemas), fm),
|
159
|
+
JSON.generate(identify_documents(docs))
|
160
|
+
)
|
161
|
+
send_request request
|
162
|
+
end
|
163
|
+
|
164
|
+
# Update documents in the specified +index+
|
165
|
+
|
166
|
+
# <tt>index = name</tt> or +id+, <tt>docs = {}</tt> or +Array+ of <tt>{}</tt>.
|
167
|
+
#
|
168
|
+
# Documents lacking the key <tt>'#.#'</tt> will be created.
|
169
|
+
#
|
170
|
+
# If +schemas+ is not +nil+, ensures existence of the
|
171
|
+
# specified schemas on each document.
|
172
|
+
def update_documents(index, docs, schemas=[], fieldmode=nil)
|
173
|
+
fm = fieldmode != nil ? {:fieldmode => fieldmode} : nil
|
174
|
+
request = put(
|
175
|
+
build_path(API_PATHS[:documents], index, url_pipe_join(schemas), fm),
|
176
|
+
JSON.generate(identify_documents(docs))
|
177
|
+
)
|
178
|
+
send_request request
|
179
|
+
end
|
180
|
+
|
181
|
+
# Modify documents in the +index+ matching +query+
|
182
|
+
#
|
183
|
+
# <tt>modifications = {...data...}</tt> to update all matching
|
184
|
+
# documents.
|
185
|
+
#
|
186
|
+
# If +schemas+ is not +nil+, ensures existence of the
|
187
|
+
# specified schemas on each document.
|
188
|
+
def modify_documents(index, query, modifications, schemas=[], fieldmode=nil)
|
189
|
+
fm = fieldmode != nil ? "?fieldmode=#{fieldmode}" : ""
|
190
|
+
request = put(
|
191
|
+
build_path(API_PATHS[:documents], index, url_pipe_join(schemas), Rack::Utils.escape(query), fm),
|
192
|
+
JSON.generate(modifications)
|
193
|
+
)
|
194
|
+
send_request request
|
195
|
+
end
|
196
|
+
|
197
|
+
# Delete documents in the +index+ matching +query+
|
198
|
+
#
|
199
|
+
# query => defaults to '*'
|
200
|
+
# index => may be an id, index name, or Array of ids or names.
|
201
|
+
#
|
202
|
+
# Operates on all indexes if +index+ = +nil+ or <tt>'*'</tt>
|
203
|
+
#
|
204
|
+
# ==== BEWARE: If +query+ = +nil+ this will delete ALL documents in +index+.
|
205
|
+
#
|
206
|
+
# ==== Acceptable options:
|
207
|
+
# :sort => a string ("[+|-]schema.field"), or a list thereof (default => index-order)
|
208
|
+
# :offset => integer offset into the result set (default => 0)
|
209
|
+
# :limit => integer limit on number of documents returned per index (default => <no limit>)
|
210
|
+
#
|
211
|
+
# If +schemas+ is not +nil+, ensures existence of the
|
212
|
+
# specified schemas on each document.
|
213
|
+
def delete_documents(index, query, options={}, schemas=[])
|
214
|
+
if options[:sort]
|
215
|
+
options[:sort] = Array(options[:sort]).flatten.join(',')
|
216
|
+
end
|
217
|
+
request = delete(
|
218
|
+
build_path(API_PATHS[:documents],
|
219
|
+
url_pipe_join(index),
|
220
|
+
url_pipe_join(schemas),
|
221
|
+
Rack::Utils.escape(query)
|
222
|
+
),
|
223
|
+
options
|
224
|
+
)
|
225
|
+
send_request request
|
226
|
+
end
|
227
|
+
|
228
|
+
# Get documents matching +query+
|
229
|
+
#
|
230
|
+
# query => defaults to '*'
|
231
|
+
# index => may be an id, index name, or Array of ids or names.
|
232
|
+
#
|
233
|
+
# Operates on all indexes if +index+ = +nil+ or <tt>'*'</tt>
|
234
|
+
#
|
235
|
+
# ==== Acceptable options:
|
236
|
+
# :fields => a field name, a prefix match (e.g. 'trans*'), or Array of fields (default => '*')
|
237
|
+
# :sort => a string ("[+|-]schema.field"), or a list thereof (default => index-order)
|
238
|
+
# :offset => integer offset into the result set (default => 0)
|
239
|
+
# :limit => integer limit on number of documents returned per index (default => <no limit>)
|
240
|
+
# :fieldmode => 'short' or 'long'/nil, if 'short' then field names will be returned in their
|
241
|
+
# short form (no schema name prefix), this can cause naming collisions depending
|
242
|
+
# on use schema definition and data etc.
|
243
|
+
#
|
244
|
+
# If +schemas+ is not +nil+, ensures existence of the
|
245
|
+
# specified schemas on each document.
|
246
|
+
def get_documents(index, query, options={}, schemas=[])
|
247
|
+
if fields = options.delete(:fields)
|
248
|
+
fields = url_pipe_join(fields)
|
249
|
+
end
|
250
|
+
|
251
|
+
if options[:sort]
|
252
|
+
options[:sort] = Array(options[:sort]).flatten.join(',')
|
253
|
+
end
|
254
|
+
|
255
|
+
request = get(
|
256
|
+
build_path(API_PATHS[:documents],
|
257
|
+
url_pipe_join(index),
|
258
|
+
url_pipe_join(schemas),
|
259
|
+
url_pipe_join(query),
|
260
|
+
fields
|
261
|
+
),
|
262
|
+
options
|
263
|
+
)
|
264
|
+
send_request request
|
265
|
+
end
|
266
|
+
|
267
|
+
# Count documents matching +query+
|
268
|
+
#
|
269
|
+
# query => defaults to '*'
|
270
|
+
# index => may be an id, index name, or Array of ids or names.
|
271
|
+
#
|
272
|
+
# Operates on all indexes if +index+ = +nil+ or <tt>'*'</tt>
|
273
|
+
#
|
274
|
+
# If +schemas+ is not +nil+, ensures existence of the
|
275
|
+
# specified schemas on each document.
|
276
|
+
def count_documents(index, query, schemas)
|
277
|
+
get_documents(index, query, {:fields => '@count'}, schemas)
|
278
|
+
end
|
279
|
+
|
280
|
+
private
|
281
|
+
def build_path(*path_elements)
|
282
|
+
path_elements.flatten.compact.unshift(PATH).join('/')
|
283
|
+
end
|
284
|
+
|
285
|
+
def account_path
|
286
|
+
build_path(API_PATHS[:account], @account)
|
287
|
+
end
|
288
|
+
|
289
|
+
def build_request(options={})
|
290
|
+
Request.new default_request_params.merge(options)
|
291
|
+
end
|
292
|
+
|
293
|
+
def get(path, params={})
|
294
|
+
build_request(:method => 'GET', :path => path, :params => params)
|
295
|
+
end
|
296
|
+
|
297
|
+
def delete(path, params={})
|
298
|
+
build_request(:method => 'DELETE', :path => path, :params => params)
|
299
|
+
end
|
300
|
+
|
301
|
+
def post(path, doc, params={})
|
302
|
+
build_request(:method => 'POST', :path => path, :body => doc, :params => params)
|
303
|
+
end
|
304
|
+
|
305
|
+
def put(path, doc, params={})
|
306
|
+
build_request(:method => 'PUT', :path => path, :body => doc, :params => params)
|
307
|
+
end
|
308
|
+
|
309
|
+
def default_request_params
|
310
|
+
{
|
311
|
+
:account => @account,
|
312
|
+
:secret => @secret,
|
313
|
+
:scheme => @secure ? 'https' : 'http',
|
314
|
+
}
|
315
|
+
end
|
316
|
+
|
317
|
+
def send_request(request, content_type=nil)
|
318
|
+
response = execute_request(request.method, request.url, request.headers, request.body, content_type)
|
319
|
+
status_code = response.first
|
320
|
+
if (200..299).include?(status_code)
|
321
|
+
begin
|
322
|
+
result = JSON.parse(response.last)
|
323
|
+
rescue JSON::ParserError => e
|
324
|
+
result = {"REASON" => e.message}
|
325
|
+
end
|
326
|
+
else
|
327
|
+
result = {"REASON" => "Error: #{status_code} #{Rack::Utils::HTTP_STATUS_CODES[status_code]}"}
|
328
|
+
end
|
329
|
+
result.merge!({'STATUS' => status_code})
|
330
|
+
end
|
331
|
+
|
332
|
+
def execute_request(method, url, headers, body, content_type=nil)
|
333
|
+
content_type ||= CONTENT_TYPES[:json]
|
334
|
+
curl = Curl::Easy.new(url) do |c|
|
335
|
+
c.headers = headers
|
336
|
+
c.headers['Content-Type'] = content_type
|
337
|
+
c.encoding = 'gzip'
|
338
|
+
end
|
339
|
+
case method
|
340
|
+
when 'GET'
|
341
|
+
curl.http_get
|
342
|
+
when 'DELETE'
|
343
|
+
curl.http_delete
|
344
|
+
when 'POST'
|
345
|
+
curl.http_post(body)
|
346
|
+
when 'PUT'
|
347
|
+
curl.http_put(body)
|
348
|
+
end
|
349
|
+
|
350
|
+
[curl.response_code, curl.header_str, curl.body_str]
|
351
|
+
end
|
352
|
+
|
353
|
+
def url_pipe_join(arr, default_value='*')
|
354
|
+
arr = Array(arr).flatten
|
355
|
+
if arr.empty?
|
356
|
+
default_value
|
357
|
+
else
|
358
|
+
Rack::Utils.escape(arr.join('|'))
|
359
|
+
end
|
360
|
+
end
|
361
|
+
|
362
|
+
def identify_documents(docs)
|
363
|
+
[docs] if docs.is_a?(Hash)
|
364
|
+
if @document_id_method
|
365
|
+
docs.each { |d| d.send(@document_id_method) }
|
366
|
+
end
|
367
|
+
docs
|
368
|
+
end
|
369
|
+
end
|
370
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module CloudQuery
|
2
|
+
module Crypto
|
3
|
+
module Random
|
4
|
+
extend self
|
5
|
+
|
6
|
+
SecureRandom = (defined?(::SecureRandom) && ::SecureRandom) || (defined?(::ActiveSupport::SecureRandom) && ::ActiveSupport::SecureRandom)
|
7
|
+
if SecureRandom
|
8
|
+
def nonce
|
9
|
+
"#{SecureRandom.random_number}.#{Time.now.to_i}"[2..-1]
|
10
|
+
end
|
11
|
+
else
|
12
|
+
def nonce
|
13
|
+
"#{rand.to_s}.#{Time.now.to_i}"[2..-1]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
module URLSafeSHA1
|
19
|
+
extend self
|
20
|
+
|
21
|
+
def sign(*tokens)
|
22
|
+
tokens = tokens.flatten
|
23
|
+
digest = Digest::SHA1.digest(tokens.join)
|
24
|
+
Base64.encode64(digest).chomp.tr('+/', '-_')
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module CloudQuery
|
2
|
+
class Field
|
3
|
+
attr_accessor :name, :type, :usage
|
4
|
+
|
5
|
+
def initialize(name, type)
|
6
|
+
@name, @type = name, type
|
7
|
+
end
|
8
|
+
|
9
|
+
def to_node(builder)
|
10
|
+
builder.field attributes
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def attributes
|
16
|
+
hash = { :name => @name, :type => @type }
|
17
|
+
hash[:usage] = @usage unless @usage.nil?
|
18
|
+
|
19
|
+
hash
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module CloudQuery
|
2
|
+
class Request
|
3
|
+
attr_accessor :method, :headers, :scheme, :host, :port, :path, :params, :body
|
4
|
+
|
5
|
+
def initialize(options={})
|
6
|
+
@method = options[:method] || 'POST'
|
7
|
+
@headers = options[:headers] || {}
|
8
|
+
@scheme = options[:scheme] || SCHEME
|
9
|
+
@host = options[:host] || HOST
|
10
|
+
@port = options[:port] || (@scheme == 'https' ? URI::HTTPS::DEFAULT_PORT : URI::HTTP::DEFAULT_PORT)
|
11
|
+
@path = options[:path] || PATH
|
12
|
+
@params = options[:params] || {}
|
13
|
+
if ['PUT', 'DELETE'].include?(@method)
|
14
|
+
@params['_method'] = @method
|
15
|
+
@method = 'POST'
|
16
|
+
end
|
17
|
+
@body = options[:body]
|
18
|
+
|
19
|
+
@account = options[:account]
|
20
|
+
@secret = options[:secret]
|
21
|
+
end
|
22
|
+
|
23
|
+
def request_uri(account=@account, secret=@secret)
|
24
|
+
query = query_str(signature_params(account))
|
25
|
+
uri = if query.empty?
|
26
|
+
@path.dup
|
27
|
+
else
|
28
|
+
"#{@path}?#{query}"
|
29
|
+
end
|
30
|
+
uri = append_signature(uri, secret) if secret
|
31
|
+
uri
|
32
|
+
end
|
33
|
+
|
34
|
+
def url(account=@account, secret=@secret)
|
35
|
+
base_uri.merge(request_uri(account, secret)).to_s
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
def append_signature(uri, secret)
|
40
|
+
sig = Crypto::URLSafeSHA1.sign(secret, uri)
|
41
|
+
x_sig = Rack::Utils.build_query("x_sig" => sig)
|
42
|
+
"#{uri}&#{x_sig}"
|
43
|
+
end
|
44
|
+
|
45
|
+
def signature_params(account=@account)
|
46
|
+
return {} unless account
|
47
|
+
{
|
48
|
+
'x_name' => account,
|
49
|
+
'x_time' => Time.now.to_i_milliseconds,
|
50
|
+
'x_nonce' => CloudQuery::Crypto::Random.nonce,
|
51
|
+
'x_method' => SIGNING_METHOD,
|
52
|
+
}
|
53
|
+
end
|
54
|
+
|
55
|
+
def query_str(additional_params={})
|
56
|
+
Rack::Utils.build_query(@params.dup.merge(additional_params))
|
57
|
+
end
|
58
|
+
|
59
|
+
def base_uri
|
60
|
+
uri_class = (@scheme == 'https' ? URI::HTTPS : URI::HTTP)
|
61
|
+
uri_class.build(:scheme => @scheme, :host => @host, :port => @port)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module CloudQuery
|
2
|
+
class Schema
|
3
|
+
attr_accessor :name, :fields
|
4
|
+
|
5
|
+
def initialize(name)
|
6
|
+
@name = name
|
7
|
+
@fields = []
|
8
|
+
end
|
9
|
+
|
10
|
+
def to_xml
|
11
|
+
Nokogiri::XML::Builder.new { |doc|
|
12
|
+
doc.schema(:name => @name, :store => 'yes') {
|
13
|
+
@fields.each { |field| field.to_node(doc) }
|
14
|
+
}
|
15
|
+
}.to_xml
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module CloudQuery
|
2
|
+
class X64
|
3
|
+
Shift = 6
|
4
|
+
Mask = 0x3f
|
5
|
+
Munge = 0x30
|
6
|
+
NumberOfChars = (64 + Shift - 1)/Shift
|
7
|
+
Maximum = 0xffffffffffffffff
|
8
|
+
Compliment = 0x8000000000000000
|
9
|
+
Characters = '.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz'
|
10
|
+
|
11
|
+
def self.encode(object, characters = Characters, munge = Munge)
|
12
|
+
int = integer_from_object(object)
|
13
|
+
|
14
|
+
chars = []
|
15
|
+
while chars.length == 0 || int != 0
|
16
|
+
byte = int & Maximum
|
17
|
+
|
18
|
+
(0...NumberOfChars).each do |i|
|
19
|
+
index = byte & Mask
|
20
|
+
index |= munge if i == (NumberOfChars - 1)
|
21
|
+
chars.unshift characters[index, 1]
|
22
|
+
byte = (byte >> Shift) & Maximum
|
23
|
+
end
|
24
|
+
|
25
|
+
int = int >> 64
|
26
|
+
end
|
27
|
+
|
28
|
+
chars.join
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def self.integer_from_object(object)
|
34
|
+
case object
|
35
|
+
when String
|
36
|
+
UUIDTools::UUID.parse_raw(Digest::MD5.digest(object)).to_i
|
37
|
+
else
|
38
|
+
object.to_i
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/lib/cloud_query.rb
CHANGED
@@ -6,12 +6,14 @@ require "rack/utils"
|
|
6
6
|
require "curl"
|
7
7
|
require "json"
|
8
8
|
require 'nokogiri'
|
9
|
+
require 'uuidtools'
|
9
10
|
|
10
11
|
require 'cloud_query/client'
|
11
12
|
require 'cloud_query/crypto'
|
12
13
|
require 'cloud_query/field'
|
13
14
|
require 'cloud_query/request'
|
14
15
|
require 'cloud_query/schema'
|
16
|
+
require 'cloud_query/x64'
|
15
17
|
|
16
18
|
module CloudQuery
|
17
19
|
SCHEME = "https".freeze
|
@@ -37,7 +39,7 @@ module CloudQuery
|
|
37
39
|
end
|
38
40
|
|
39
41
|
class Time
|
40
|
-
def
|
42
|
+
def to_i_milliseconds
|
41
43
|
(to_f * 1000).to_i
|
42
44
|
end
|
43
45
|
end
|
data/spec/cloudquery_spec.rb
CHANGED
@@ -89,7 +89,7 @@ if ENV["TEST_REAL_HTTP"]
|
|
89
89
|
'spec.example.email' => ['steve.rogers@example.com','captain.america@marvel.com'],
|
90
90
|
'spec.example.telephone' => ['555-555-5555','123-456-6789'],
|
91
91
|
'spec.example.address' => ['Lower East Side, NY NY'],
|
92
|
-
'spec.example.birthday' =>
|
92
|
+
'spec.example.birthday' => Date.parse('July 4, 1917'),
|
93
93
|
'spec.example.note' => 'Captain America!',
|
94
94
|
}
|
95
95
|
end
|
@@ -125,7 +125,7 @@ if ENV["TEST_REAL_HTTP"]
|
|
125
125
|
'spec.example.telephone' => ['555-123-1234', '555-456-6789'],
|
126
126
|
'spec.example.address' =>
|
127
127
|
['344 Clinton St., Apt. #3B, Metropolis', 'The Fortess of Solitude, North Pole'],
|
128
|
-
'spec.example.birthday' =>
|
128
|
+
'spec.example.birthday' => Date.parse('June 18, 1938'),
|
129
129
|
'spec.example.note' =>
|
130
130
|
'Superhuman strength, speed, stamina, durability, senses, intelligence, regeneration, and longevity; super breath, heat vision, x-ray vision and flight. Member of the justice league.',
|
131
131
|
},
|
@@ -135,7 +135,7 @@ if ENV["TEST_REAL_HTTP"]
|
|
135
135
|
'spec.example.telephone' => ['555-123-6666', '555-456-6666'],
|
136
136
|
'spec.example.address' =>
|
137
137
|
['1007 Mountain Drive, Gotham', 'The Batcave, Gotham'],
|
138
|
-
'spec.example.birthday' =>
|
138
|
+
'spec.example.birthday' => Date.parse('February 19, 1939'),
|
139
139
|
'spec.example.note' =>
|
140
140
|
'Sidekick is Robin. Has problems with the Joker. Member of the justice league.',
|
141
141
|
},
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'spec/spec_helper'
|
2
|
+
|
3
|
+
describe CloudQuery::Field do
|
4
|
+
it "should initialize with a name and type" do
|
5
|
+
field = CloudQuery::Field.new(:name, :string)
|
6
|
+
|
7
|
+
field.name.should == :name
|
8
|
+
field.type.should == :string
|
9
|
+
end
|
10
|
+
|
11
|
+
describe "#.to_node" do
|
12
|
+
before :each do
|
13
|
+
@field = CloudQuery::Field.new(:name, :string)
|
14
|
+
@builder = Nokogiri::XML::Builder.new { |doc|
|
15
|
+
@field.to_node(doc)
|
16
|
+
}
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should append a field element" do
|
20
|
+
@builder.doc.at('field').should_not be_nil
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should set the name attribute" do
|
24
|
+
@builder.doc.at('field')['name'].should == 'name'
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should set the type attribute" do
|
28
|
+
@builder.doc.at('field')['type'].should == 'string'
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should set the usage attribute if one is provided" do
|
32
|
+
@field.usage = 'user'
|
33
|
+
@builder = Nokogiri::XML::Builder.new { |doc|
|
34
|
+
@field.to_node(doc)
|
35
|
+
}
|
36
|
+
@builder.doc.at('field')['usage'].should == 'user'
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should not have a usage attribute if not set" do
|
40
|
+
@builder.doc.at('field')['usage'].should be_nil
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'spec/spec_helper'
|
2
|
+
|
3
|
+
describe CloudQuery::Schema do
|
4
|
+
describe "#.to_xml" do
|
5
|
+
before :each do
|
6
|
+
@schema = CloudQuery::Schema.new('user.schema')
|
7
|
+
@schema.fields << CloudQuery::Field.new(:name, :string)
|
8
|
+
@schema.fields << CloudQuery::Field.new(:type, :literal)
|
9
|
+
@schema.fields << CloudQuery::Field.new(:description, :text)
|
10
|
+
|
11
|
+
@document = Nokogiri::XML(@schema.to_xml)
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should output a schema element" do
|
15
|
+
@document.at('schema').should_not be_nil
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should output the schema name as an attribute" do
|
19
|
+
@document.at('schema')['name'].should == 'user.schema'
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should output the store attribute set to yes" do
|
23
|
+
@document.at('schema')['store'].should == 'yes'
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should output each of the fields as elements" do
|
27
|
+
@schema.fields.each do |field|
|
28
|
+
@document.at('schema').css('field').detect { |element|
|
29
|
+
element['name'] == field.name.to_s
|
30
|
+
}.should_not be_nil
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'spec/spec_helper'
|
2
|
+
|
3
|
+
describe CloudQuery::X64 do
|
4
|
+
describe '.encode' do
|
5
|
+
# Built with the expectation of parity with the Python equivalent.
|
6
|
+
it "should return expected values from strings" do
|
7
|
+
CloudQuery::X64.encode('test').should == 'kaEPwp57SCnwfTIcBa8vIq'
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should return expected values from integers" do
|
11
|
+
CloudQuery::X64.encode(111).should == 'k........0j'
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'jeweler'
|
2
|
+
require 'yard'
|
3
|
+
|
4
|
+
desc 'Generate documentation'
|
5
|
+
YARD::Rake::YardocTask.new do |t|
|
6
|
+
config = YAML.load(File.read('VERSION.yml'))
|
7
|
+
version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
|
8
|
+
|
9
|
+
t.name = "CloudQuery #{version}"
|
10
|
+
t.files = ['lib/**/*.rb']
|
11
|
+
end
|
12
|
+
|
13
|
+
task :rdoc => :yardoc
|
14
|
+
|
15
|
+
Jeweler::Tasks.new do |gem|
|
16
|
+
gem.name = "cloud_query"
|
17
|
+
gem.summary = "Client for Xoopit's CloudQuery API"
|
18
|
+
gem.description = "Client for Xoopit's CloudQuery API"
|
19
|
+
gem.email = "cloudquery@xoopit.com"
|
20
|
+
gem.homepage = "http://github.com/xoopit/cloudquery_ruby"
|
21
|
+
gem.authors = ["Xoopit", "Cameron Walters", "nb.io", "Pat Allan"]
|
22
|
+
gem.files = FileList[
|
23
|
+
"lib/**/*.rb",
|
24
|
+
"LICENCE",
|
25
|
+
"README.markdown",
|
26
|
+
"tasks/**/*.rb"
|
27
|
+
]
|
28
|
+
gem.test_files = FileList["spec/**/*_spec.rb"]
|
29
|
+
# gem.rubyforge_project = "cloudquery"
|
30
|
+
|
31
|
+
gem.add_dependency 'rack', '>= 1.0'
|
32
|
+
gem.add_dependency 'json', '>= 1.1.4'
|
33
|
+
gem.add_dependency 'taf2-curb', '>= 0.2.8.0'
|
34
|
+
gem.add_dependency 'nokogiri', '>= 1.3.1'
|
35
|
+
gem.add_dependency 'uuidtools', '>= 2.0.0'
|
36
|
+
|
37
|
+
# See http://www.rubygems.org/read/chapter/20 for additional gemspec settings
|
38
|
+
end
|
data/tasks/testing.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'spec/rake/spectask'
|
2
|
+
|
3
|
+
Spec::Rake::SpecTask.new(:spec) do |t|
|
4
|
+
t.spec_files = FileList['spec/**/*_spec.rb']
|
5
|
+
t.spec_opts << "-c"
|
6
|
+
end
|
7
|
+
|
8
|
+
Spec::Rake::SpecTask.new(:rcov) do |t|
|
9
|
+
t.pattern = 'spec/**/*_spec.rb'
|
10
|
+
t.spec_opts << "-c"
|
11
|
+
|
12
|
+
t.rcov_opts = ['--exclude', 'spec', '--exclude', 'gems']
|
13
|
+
t.rcov = true
|
14
|
+
end
|
metadata
CHANGED
@@ -1,16 +1,18 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: xoopit-cloud_query
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
|
+
- Xoopit
|
7
8
|
- Cameron Walters
|
8
9
|
- nb.io
|
10
|
+
- Pat Allan
|
9
11
|
autorequire:
|
10
12
|
bindir: bin
|
11
13
|
cert_chain: []
|
12
14
|
|
13
|
-
date: 2009-
|
15
|
+
date: 2009-07-16 00:00:00 -07:00
|
14
16
|
default_executable:
|
15
17
|
dependencies:
|
16
18
|
- !ruby/object:Gem::Dependency
|
@@ -43,8 +45,28 @@ dependencies:
|
|
43
45
|
- !ruby/object:Gem::Version
|
44
46
|
version: 0.2.8.0
|
45
47
|
version:
|
48
|
+
- !ruby/object:Gem::Dependency
|
49
|
+
name: nokogiri
|
50
|
+
type: :runtime
|
51
|
+
version_requirement:
|
52
|
+
version_requirements: !ruby/object:Gem::Requirement
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: 1.3.1
|
57
|
+
version:
|
58
|
+
- !ruby/object:Gem::Dependency
|
59
|
+
name: uuidtools
|
60
|
+
type: :runtime
|
61
|
+
version_requirement:
|
62
|
+
version_requirements: !ruby/object:Gem::Requirement
|
63
|
+
requirements:
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: 2.0.0
|
67
|
+
version:
|
46
68
|
description: Client for Xoopit's CloudQuery API
|
47
|
-
email:
|
69
|
+
email: cloudquery@xoopit.com
|
48
70
|
executables: []
|
49
71
|
|
50
72
|
extensions: []
|
@@ -53,17 +75,20 @@ extra_rdoc_files:
|
|
53
75
|
- LICENSE
|
54
76
|
- README.markdown
|
55
77
|
files:
|
56
|
-
- LICENSE
|
57
78
|
- README.markdown
|
58
|
-
- Rakefile
|
59
|
-
- VERSION.yml
|
60
|
-
- lib/cloudquery.rb
|
61
79
|
- lib/cloud_query.rb
|
62
|
-
-
|
63
|
-
-
|
64
|
-
-
|
80
|
+
- lib/cloud_query/client.rb
|
81
|
+
- lib/cloud_query/crypto.rb
|
82
|
+
- lib/cloud_query/field.rb
|
83
|
+
- lib/cloud_query/request.rb
|
84
|
+
- lib/cloud_query/schema.rb
|
85
|
+
- lib/cloud_query/x64.rb
|
86
|
+
- lib/cloudquery.rb
|
87
|
+
- tasks/distribution.rb
|
88
|
+
- tasks/testing.rb
|
89
|
+
- LICENSE
|
65
90
|
has_rdoc: true
|
66
|
-
homepage: http://github.com/
|
91
|
+
homepage: http://github.com/xoopit/cloudquery_ruby
|
67
92
|
post_install_message:
|
68
93
|
rdoc_options:
|
69
94
|
- --charset=UTF-8
|
@@ -87,7 +112,9 @@ rubyforge_project:
|
|
87
112
|
rubygems_version: 1.2.0
|
88
113
|
signing_key:
|
89
114
|
specification_version: 2
|
90
|
-
summary: Client for Xoopit's
|
115
|
+
summary: Client for Xoopit's CloudQuery API
|
91
116
|
test_files:
|
92
117
|
- spec/cloudquery_spec.rb
|
93
|
-
- spec/
|
118
|
+
- spec/lib/cloud_query/field_spec.rb
|
119
|
+
- spec/lib/cloud_query/schema_spec.rb
|
120
|
+
- spec/lib/cloud_query/x64_spec.rb
|
data/Rakefile
DELETED
@@ -1,80 +0,0 @@
|
|
1
|
-
require 'rake'
|
2
|
-
|
3
|
-
begin
|
4
|
-
require 'jeweler'
|
5
|
-
Jeweler::Tasks.new do |gem|
|
6
|
-
gem.name = "cloud_query"
|
7
|
-
gem.summary = "Client for Xoopit's CloudQuery API"
|
8
|
-
gem.email = "us@nb.io"
|
9
|
-
gem.homepage = "http://github.com/xoopit/cloudquery_ruby"
|
10
|
-
gem.description = "Client for Xoopit's CloudQuery API"
|
11
|
-
gem.authors = ["Cameron Walters", "nb.io"]
|
12
|
-
gem.files = FileList["[A-Z]*", "{lib,spec}/**/*"]
|
13
|
-
# gem.rubyforge_project = "cloudquery"
|
14
|
-
gem.add_dependency('rack', ">= 1.0")
|
15
|
-
gem.add_dependency('json', ">= 1.1.4")
|
16
|
-
gem.add_dependency('taf2-curb', ">= 0.2.8.0")
|
17
|
-
|
18
|
-
|
19
|
-
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
20
|
-
end
|
21
|
-
rescue LoadError
|
22
|
-
puts "Jeweler not available. Install it with: sudo gem install jeweler"
|
23
|
-
end
|
24
|
-
|
25
|
-
require 'spec/rake/spectask'
|
26
|
-
Spec::Rake::SpecTask.new(:spec) do |spec|
|
27
|
-
spec.libs << 'lib' << 'spec'
|
28
|
-
spec.spec_files = FileList['spec/**/*_spec.rb']
|
29
|
-
spec.spec_opts << "-c"
|
30
|
-
end
|
31
|
-
|
32
|
-
Spec::Rake::SpecTask.new(:rcov) do |spec|
|
33
|
-
spec.libs << 'lib' << 'spec'
|
34
|
-
spec.pattern = 'spec/**/*_spec.rb'
|
35
|
-
spec.rcov = true
|
36
|
-
end
|
37
|
-
|
38
|
-
|
39
|
-
task :default => :spec
|
40
|
-
|
41
|
-
require 'rake/rdoctask'
|
42
|
-
Rake::RDocTask.new do |rdoc|
|
43
|
-
if File.exist?('VERSION.yml')
|
44
|
-
config = YAML.load(File.read('VERSION.yml'))
|
45
|
-
version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
|
46
|
-
else
|
47
|
-
version = ""
|
48
|
-
end
|
49
|
-
|
50
|
-
rdoc.rdoc_dir = 'rdoc'
|
51
|
-
rdoc.title = "CloudQuery #{version}"
|
52
|
-
rdoc.rdoc_files.include('README*')
|
53
|
-
rdoc.rdoc_files.include('lib/**/*.rb')
|
54
|
-
end
|
55
|
-
|
56
|
-
# begin
|
57
|
-
# require 'rake/contrib/sshpublisher'
|
58
|
-
# namespace :rubyforge do
|
59
|
-
#
|
60
|
-
# desc "Release gem and RDoc documentation to RubyForge"
|
61
|
-
# task :release => ["rubyforge:release:gem", "rubyforge:release:docs"]
|
62
|
-
#
|
63
|
-
# namespace :release do
|
64
|
-
# desc "Publish RDoc to RubyForge."
|
65
|
-
# task :docs => [:rdoc] do
|
66
|
-
# config = YAML.load(
|
67
|
-
# File.read(File.expand_path('~/.rubyforge/user-config.yml'))
|
68
|
-
# )
|
69
|
-
#
|
70
|
-
# host = "#{config['username']}@rubyforge.org"
|
71
|
-
# remote_dir = "/var/www/gforge-projects/cloudquery/"
|
72
|
-
# local_dir = 'rdoc'
|
73
|
-
#
|
74
|
-
# Rake::SshDirPublisher.new(host, remote_dir, local_dir).upload
|
75
|
-
# end
|
76
|
-
# end
|
77
|
-
# end
|
78
|
-
# rescue LoadError
|
79
|
-
# puts "Rake SshDirPublisher is unavailable or your rubyforge environment is not configured."
|
80
|
-
# end
|
data/VERSION.yml
DELETED
data/spec/example_schema.xml
DELETED
@@ -1,26 +0,0 @@
|
|
1
|
-
<schema name="spec.example" store="yes">
|
2
|
-
<!-- The full name of the contact -->
|
3
|
-
<field name="name"
|
4
|
-
type="string"
|
5
|
-
analyzer="LCWhitespaceAnalyzer"
|
6
|
-
usage="user" />
|
7
|
-
<!-- The email addresses. A json array: email address -->
|
8
|
-
<field name="email"
|
9
|
-
type="string"
|
10
|
-
usage="user" />
|
11
|
-
<!-- The phone numbers. A json array: phone number -->
|
12
|
-
<field name="telephone"
|
13
|
-
type="string"
|
14
|
-
usage="user" />
|
15
|
-
<!-- The addresses. A json array: address -->
|
16
|
-
<field name="address"
|
17
|
-
type="string"
|
18
|
-
usage="user" />
|
19
|
-
<!-- The birthday of the contact-->
|
20
|
-
<field name="birthday"
|
21
|
-
type="date" />
|
22
|
-
<!-- A note for the contact-->
|
23
|
-
<field name="note"
|
24
|
-
type="text"
|
25
|
-
usage="user" />
|
26
|
-
</schema>
|
data/spec/spec_helper.rb
DELETED