citysdk 0.1.2.5 → 1.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/citysdk.gemspec CHANGED
@@ -19,11 +19,12 @@ Gem::Specification.new do |gem|
19
19
  gem.require_paths = ["lib"]
20
20
 
21
21
 
22
- gem.add_dependency('dbf')
23
- gem.add_dependency('georuby', '>= 2.0.0')
24
- gem.add_dependency('faraday', '>= 0.8.5')
25
- gem.add_dependency('charlock_holmes', '>= 0.6.9.4')
22
+ gem.add_dependency('dbf', '~> 2.0')
23
+ gem.add_dependency('georuby', '~> 2.0')
24
+ gem.add_dependency('faraday', '~> 0.8')
25
+ gem.add_dependency('charlock_holmes', '~> 0.6')
26
+ gem.add_dependency('curses', '~> 1.0')
26
27
 
27
- gem.add_development_dependency "rspec"
28
+ gem.add_development_dependency "rspec", '~> 3.0'
28
29
  end
29
30
 
@@ -0,0 +1,68 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # check the wiki for detailed api usage: https://github.com/waagsociety/citysdk-ld/wiki
4
+
5
+ require 'citysdk'
6
+ include CitySDK
7
+
8
+ api = API.new('api.citysdk.waag.org') # point to a hosted instance of the api.
9
+
10
+ # simple GET
11
+ # GET requests do not need authentication
12
+
13
+ endpoint = api.get '/'
14
+ puts "Enpoint is: #{endpoint[:features][0][:properties][:title]}."
15
+
16
+ first10layers = api.get('/layers')
17
+ puts "First layer: #{JSON.pretty_generate(first10layers[:features][0])}"
18
+
19
+ # find out how many owners the endpoint knows
20
+ owners = api.get('/owners?per_page=1&count')
21
+ puts "Number of data maintaners: #{api.last_result[:headers]['x-result-count']}"
22
+
23
+ # authenticate for write actions.
24
+ unless api.authenticate('<name>','<passw>')
25
+ puts "Did not authenticate..."
26
+ exit
27
+ end
28
+
29
+ # make a layer
30
+ # everybody can wite to the temporary 'test' domain:
31
+
32
+ layer = {
33
+ name: "test.cities",
34
+ owner: "citysdk",
35
+ title: "Cities: 🏠🏢🏣🏨🏫🏬🏭🏡",
36
+ description: "Cities big and small.",
37
+ data_sources: [ "http://fantasy.com" ],
38
+ authoritative: false,
39
+ rdf_type: "dbp:City",
40
+ category: "administrative",
41
+ subcategory: "cities",
42
+ licence: "CC0"
43
+ }
44
+
45
+ api.post('/layers',layer)
46
+
47
+ # add data to this layer
48
+ # attach to the node representing the city of Rotterdam
49
+ object = {type: "Feature",
50
+ geometry: {
51
+ type: 'Point',
52
+ coordinates: [4.4646,51.9222] },
53
+ properties: {
54
+ id: 'Rotterdam',
55
+ title: 'Rotterdam',
56
+ data: {
57
+ a: 1,
58
+ b: 2
59
+ }
60
+ }
61
+ }
62
+
63
+ api.post('/layers/test.cities/objects', object)
64
+
65
+
66
+ # don't forget to release! this will also send 'unfilled' batches to the backend.
67
+ api.release
68
+
data/lib/citysdk/api.rb CHANGED
@@ -1,61 +1,62 @@
1
1
  require 'json'
2
2
  require 'faraday'
3
3
 
4
+
5
+
4
6
  module CitySDK
7
+
8
+ def log(m)
9
+ File.open(File.expand_path('~/csdk.log'), "a") do |f|
10
+ f.write(m + "\n")
11
+ end
12
+ end
5
13
 
6
14
  class HostException < ::Exception
7
15
  end
8
16
 
9
17
  class API
10
- attr_reader :error
18
+ attr_reader :last_result
19
+ attr_reader :error
11
20
  attr_accessor :batch_size
12
-
13
- @@match_tpl = {
14
- :match => {
15
- :params => {}
16
- },
17
- :nodes => []
18
- }
21
+ attr_accessor :page_size
22
+ attr_accessor :format
23
+
19
24
  @@create_tpl = {
20
- :create => {
21
- :params => {
22
- :create_type => "create"
23
- }
24
- },
25
- :nodes => []
25
+ type: "FeatureCollection",
26
+ features: []
26
27
  }
27
28
 
28
29
  def initialize(host, port=nil)
29
30
  @error = '';
30
31
  @layer = '';
31
- @batch_size = 10;
32
+ @batch_size = 1000;
33
+ @page_size = 25;
34
+ @format = 'jsonld'
32
35
  @updated = @created = 0;
33
36
  set_host(host,port)
34
37
  end
35
38
 
36
- def authenticate(e,p)
37
- @email = e;
39
+ def authenticate(n,p)
40
+ @name = n;
38
41
  @passw = p;
39
- if !( @host == 'api.dev' or @host == 'localhost' or @host == '127.0.0.1' or @host == '0.0.0.0')
40
- auth_connection = Faraday.new :url => "https://#{@host}", :ssl => {:verify => false}
41
- resp = auth_connection.get '/get_session', { :e => @email, :p => @passw }
42
- else
43
- resp = @connection.get '/get_session', { :e => @email, :p => @passw }
44
- end
45
- if resp.status == 200
42
+
43
+ resp = @connection.get '/session', { :name => @name, :password => @passw }
44
+ if resp.status.between?(200, 299)
46
45
  resp = CitySDK::parseJson(resp.body)
47
- if resp[:status] == 'success'
48
- @connection.headers['X-Auth'] = resp[:results][0]
46
+ if (resp.class == Hash) and resp[:session_key]
47
+ @connection.headers['X-Auth'] = resp[:session_key]
49
48
  else
50
- raise Exception.new(resp[:message])
49
+ raise Exception.new("Invalid credentials")
51
50
  end
52
51
  else
53
52
  raise Exception.new(resp.body)
54
53
  end
55
-
56
54
  if block_given?
57
- yield
58
- return self.release
55
+ begin
56
+ yield
57
+ ensure
58
+ self.release
59
+ end
59
60
  end
60
61
  true
61
62
  end
@@ -64,6 +65,8 @@ module CitySDK
64
65
  @host = host
65
66
  @port = port
66
67
 
68
+ @host.gsub!(/^http(s)?:\/\//,'')
69
+
67
70
  if port.nil?
68
71
  if host =~ /^(.*):(\d+)$/
69
72
  @port = $2
@@ -73,7 +76,11 @@ module CitySDK
73
76
  end
74
77
  end
75
78
 
76
- @connection = Faraday.new :url => "http://#{@host}:#{@port}"
79
+ if !($nohttps or @host =~ /.+\.dev/ or @host == 'localhost' or @host == '127.0.0.1' or @host == '0.0.0.0')
80
+ @connection = Faraday.new :url => "https://#{@host}", :ssl => {:verify => false }
81
+ else
82
+ @connection = Faraday.new :url => "http://#{@host}:#{@port}"
83
+ end
77
84
  @connection.headers = {
78
85
  :user_agent => 'CitySDK_API GEM ' + CitySDK::VERSION,
79
86
  :content_type => 'application/json'
@@ -84,16 +91,18 @@ module CitySDK
84
91
  raise CitySDK::Exception.new("Trouble connecting to api @ #{host}")
85
92
  end
86
93
  @create = @@create_tpl
87
- @match = @@match_tpl
88
94
  end
89
-
90
- def set_matchTemplate(mtpl)
91
- mtpl[:nodes] = []
92
- @match = @@match_tpl = mtpl
95
+
96
+ def addFormat(path)
97
+ if path !~ /format/
98
+ path = path + ((path =~ /\?/) ? "&" : "?") + "format=#{@format}"
99
+ end
100
+ return path if path =~ /page_size/
101
+ path + "&page_size=#{@page_size}"
93
102
  end
94
103
 
95
104
  def set_createTemplate(ctpl)
96
- ctpl[:nodes] = []
105
+ ctpl[:features] = []
97
106
  @create = @@create_tpl = ctpl
98
107
  end
99
108
 
@@ -101,123 +110,114 @@ module CitySDK
101
110
  @layer = l
102
111
  end
103
112
 
104
- def set_layer_status(status)
105
- put("/layer/#{@layer}/status",{:data => status})
113
+ def next
114
+ @next ? get(@next) : "{}"
106
115
  end
107
-
108
- def match_node(n)
109
- @match[:nodes] << n
110
- return match_flush if @match[:nodes].length >= @batch_size
111
- return nil
116
+
117
+ def layers
118
+ get "/layers"
119
+ end
120
+
121
+ def owners
122
+ get "/owners"
112
123
  end
113
124
 
114
- def match_create_node(n)
115
- @match[:nodes] << n
116
- return match_create_flush if @match[:nodes].length >= @batch_size
125
+ def objects(layer=nil)
126
+ !!layer ? get("/layers/#{layer}/objects") : get("/objects")
117
127
  end
118
128
 
119
- def create_node(n)
120
- @create[:nodes] << n
121
- create_flush if @create[:nodes].length >= @batch_size
129
+ def create_object(n)
130
+ @create[:features] << n
131
+ create_flush if @create[:features].length >= @batch_size
122
132
  end
123
133
 
124
134
  def authorized?
125
- @connection.headers['X-Auth']
135
+ !! @connection.headers['X-Auth']
126
136
  end
127
137
 
128
138
  def release
129
- match_flush
130
139
  create_flush # send any remaining entries in the create buffer
131
- match_create_flush
132
140
  if authorized?
133
- resp = @connection.get('/release_session')
134
- if resp.status == 200
141
+ resp = @connection.delete('/session')
142
+ if resp.status.between?(200, 299)
135
143
  @connection.headers.delete('X-Auth')
136
144
  else
137
- @error = CitySDK::parseJson(resp.body)[:message]
145
+ @error = CitySDK::parseJson(resp.body)[:error]
138
146
  raise HostException.new(@error)
139
147
  end
140
148
  end
141
- return [@updated, @created]
149
+ return {created: @created}
142
150
  end
143
151
 
144
152
  def delete(path)
145
153
  if authorized?
146
- resp = @connection.delete(path)
147
- if resp.status == 200
148
- return CitySDK::parseJson(resp.body)
154
+ resp = @connection.delete(addFormat(path))
155
+ if resp.status.between?(200, 299)
156
+ @last_result = { status: resp.status, headers: resp.headers }
157
+ return (resp.body and resp.body !~ /\s*/) ? CitySDK::parseJson(resp.body) : ''
149
158
  end
150
- @error = CitySDK::parseJson(resp.body)[:message]
159
+ @error = CitySDK::parseJson(resp.body)[:error]
151
160
  raise HostException.new(@error)
152
161
  end
153
- raise CitySDK::Exception.new("DEL needs authorization.")
162
+ raise CitySDK::Exception.new("DELETE needs authorization.")
154
163
  end
155
164
 
156
165
  def post(path,data)
157
166
  if authorized?
158
- resp = @connection.post(path,data.to_json)
159
- return CitySDK::parseJson(resp.body) if resp.status == 200
160
- @error = CitySDK::parseJson(resp.body)[:message]
167
+ resp = @connection.post(addFormat(path),data.to_json)
168
+ @last_result = { status: resp.status, headers: resp.headers }
169
+ return CitySDK::parseJson(resp.body) if resp.status.between?(200, 299)
170
+ @error = resp.body # CitySDK::parseJson(resp.body)[:error]
171
+
172
+ File.open(File.expand_path("~/post_error_data.json"),"w") do |fd|
173
+ fd.write(JSON.pretty_generate({error: @error, data: data}))
174
+ end
175
+
161
176
  raise HostException.new(@error)
162
177
  end
163
178
  raise CitySDK::Exception.new("POST needs authorization.")
164
179
  end
165
180
 
181
+ def patch(path,data)
182
+ if authorized?
183
+ resp = @connection.patch(addFormat(path),data.to_json)
184
+ @last_result = { status: resp.status, headers: resp.headers }
185
+ return CitySDK::parseJson(resp.body) if resp.status.between?(200, 299)
186
+ @error = CitySDK::parseJson(resp.body)[:error]
187
+ raise HostException.new(@error)
188
+ end
189
+ raise CitySDK::Exception.new("PATCH needs authorization.")
190
+ end
191
+
166
192
  def put(path,data)
167
193
  if authorized?
168
- resp = @connection.put(path,data.to_json)
169
- return CitySDK::parseJson(resp.body) if resp.status == 200
170
- @error = CitySDK::parseJson(resp.body)[:message]
194
+ resp = @connection.put(addFormat(path),data.to_json)
195
+ @last_result = { status: resp.status, headers: resp.headers }
196
+ return CitySDK::parseJson(resp.body) if resp.status.between?(200, 299)
197
+ @error = CitySDK::parseJson(resp.body)[:error] || {status: resp.status}
171
198
  raise HostException.new(@error)
172
199
  end
173
200
  raise CitySDK::Exception.new("PUT needs authorization.")
174
201
  end
175
202
 
176
203
  def get(path)
177
- resp = @connection.get(path)
178
- return CitySDK::parseJson(resp.body) if resp.status == 200
179
- @error = CitySDK::parseJson(resp.body)[:message]
204
+ resp = @connection.get(addFormat(path))
205
+ @next = (resp.headers['Link'] =~ /^<(.+)>;\s*rel="next"/) ? $1 : nil
206
+ @last_result = { status: resp.status, headers: resp.headers }
207
+ return CitySDK::parseJson(resp.body) if resp.status.between?(200, 299)
208
+ @error = CitySDK::parseJson(resp.body)[:error]
180
209
  raise HostException.new(@error)
181
210
  end
182
-
183
- def match_create_flush
184
-
185
- if @match[:nodes].length > 0
186
- resp = post('util/match',@match)
187
- if resp[:nodes].length > 0
188
- @create[:nodes] = resp[:nodes]
189
- res = put("/nodes/#{@layer}",@create)
190
- tally(res)
191
- @create[:nodes] = []
192
- end
193
- @match[:nodes] = []
194
- res
195
- end
196
- nil
197
- end
198
-
199
- def match_flush
200
- if @match[:nodes].length > 0
201
- resp = post('util/match',@match)
202
- @match[:nodes] = []
203
- return resp
204
- end
205
- end
206
-
207
-
211
+
208
212
  def create_flush
209
- if @create[:nodes].length > 0
210
- tally put("/nodes/#{@layer}",@create)
211
- @create[:nodes] = []
213
+ if @create[:features].length > 0
214
+ tally post("/layers/#{@layer}/objects",@create)
215
+ @create[:features] = []
212
216
  end
213
217
  end
214
218
 
215
219
  def tally(res)
216
- if res[:status] == "success"
217
- # TODO: also tally debug data!
218
- @updated += res[:create][:results][:totals][:updated]
219
- @created += res[:create][:results][:totals][:created]
220
- end
220
+ @created += res.length
221
221
  end
222
222
 
223
223
  end