cloudkit 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. data/CHANGES +2 -0
  2. data/COPYING +20 -0
  3. data/README +55 -0
  4. data/Rakefile +35 -0
  5. data/TODO +22 -0
  6. data/cloudkit.gemspec +82 -0
  7. data/doc/curl.html +329 -0
  8. data/doc/images/example-code.gif +0 -0
  9. data/doc/images/json-title.gif +0 -0
  10. data/doc/images/oauth-discovery-logo.gif +0 -0
  11. data/doc/images/openid-logo.gif +0 -0
  12. data/doc/index.html +87 -0
  13. data/doc/main.css +151 -0
  14. data/doc/rest-api.html +358 -0
  15. data/examples/1.ru +3 -0
  16. data/examples/2.ru +3 -0
  17. data/examples/3.ru +6 -0
  18. data/examples/4.ru +5 -0
  19. data/examples/5.ru +10 -0
  20. data/examples/6.ru +10 -0
  21. data/examples/TOC +17 -0
  22. data/lib/cloudkit.rb +74 -0
  23. data/lib/cloudkit/flash_session.rb +22 -0
  24. data/lib/cloudkit/oauth_filter.rb +273 -0
  25. data/lib/cloudkit/oauth_store.rb +56 -0
  26. data/lib/cloudkit/openid_filter.rb +198 -0
  27. data/lib/cloudkit/openid_store.rb +101 -0
  28. data/lib/cloudkit/rack/builder.rb +120 -0
  29. data/lib/cloudkit/rack/router.rb +20 -0
  30. data/lib/cloudkit/request.rb +159 -0
  31. data/lib/cloudkit/service.rb +135 -0
  32. data/lib/cloudkit/store.rb +459 -0
  33. data/lib/cloudkit/store/adapter.rb +9 -0
  34. data/lib/cloudkit/store/extraction_view.rb +57 -0
  35. data/lib/cloudkit/store/response.rb +51 -0
  36. data/lib/cloudkit/store/response_helpers.rb +72 -0
  37. data/lib/cloudkit/store/sql_adapter.rb +36 -0
  38. data/lib/cloudkit/templates/authorize_request_token.erb +19 -0
  39. data/lib/cloudkit/templates/oauth_descriptor.erb +43 -0
  40. data/lib/cloudkit/templates/oauth_meta.erb +8 -0
  41. data/lib/cloudkit/templates/openid_login.erb +31 -0
  42. data/lib/cloudkit/templates/request_authorization.erb +23 -0
  43. data/lib/cloudkit/templates/request_token_denied.erb +18 -0
  44. data/lib/cloudkit/user_store.rb +44 -0
  45. data/lib/cloudkit/util.rb +60 -0
  46. data/test/ext_test.rb +57 -0
  47. data/test/flash_session_test.rb +22 -0
  48. data/test/helper.rb +50 -0
  49. data/test/oauth_filter_test.rb +331 -0
  50. data/test/oauth_store_test.rb +12 -0
  51. data/test/openid_filter_test.rb +54 -0
  52. data/test/openid_store_test.rb +12 -0
  53. data/test/rack_builder_test.rb +41 -0
  54. data/test/request_test.rb +197 -0
  55. data/test/service_test.rb +718 -0
  56. data/test/store_test.rb +99 -0
  57. data/test/user_store_test.rb +12 -0
  58. data/test/util_test.rb +13 -0
  59. metadata +190 -0
data/CHANGES ADDED
@@ -0,0 +1,2 @@
1
+ 0.9.0
2
+ - First public gem release
data/COPYING ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 Jon Crosby http://joncrosby.me
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ 'Software'), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
17
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
18
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
19
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
20
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,55 @@
1
+ =CloudKit
2
+
3
+ CloudKit is an Open Web JSON Appliance.
4
+
5
+ More specifically, CloudKit provides RESTful JSON storage with optional OpenID and OAuth support, including OAuth Discovery. Stored entities are versioned. Services manage their own storage and do not require schema updates when models change.
6
+
7
+ CloudKit is Rack middleware and as such can be used on its own or alongside other Rack-based applications or middleware components like Rails, Merb or Sinatra.
8
+
9
+ ===Examples
10
+
11
+ In a rackup file called config.ru:
12
+
13
+ require 'cloudkit'
14
+ expose :notes, :todos
15
+
16
+ The above creates a versioned HTTP service using JSON to represent two types of resource collections -- Notes and ToDos.
17
+
18
+ In a different rackup file:
19
+
20
+ require 'cloudkit'
21
+ contain :notes, :todos
22
+
23
+ The aboves provides the same API as example 1 with added authentication and authorization via OpenID and OAuth, respectively.
24
+
25
+ An explicit version of example 2, minus the default developer landing page:
26
+
27
+ require 'cloudkit'
28
+ use Rack::Pool::Session
29
+ use CloudKit::OAuthFilter
30
+ use CloudKit::OpenIDFilter
31
+ use CloudKit::Service, :collections => [:notes, :todos]
32
+ run lambda {|env| [200, {}, ['HELLO']]}
33
+
34
+ See the examples directory for more.
35
+
36
+ Copyright (c) 2008 Jon Crosby http://joncrosby.me
37
+
38
+ Permission is hereby granted, free of charge, to any person obtaining
39
+ a copy of this software and associated documentation files (the
40
+ 'Software'), to deal in the Software without restriction, including
41
+ without limitation the rights to use, copy, modify, merge, publish,
42
+ distribute, sublicense, and/or sell copies of the Software, and to
43
+ permit persons to whom the Software is furnished to do so, subject to
44
+ the following conditions:
45
+
46
+ The above copyright notice and this permission notice shall be
47
+ included in all copies or substantial portions of the Software.
48
+
49
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
50
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
51
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
52
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
53
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
54
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
55
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,35 @@
1
+ require 'rake/clean'
2
+
3
+ CLEAN.include 'doc/api'
4
+
5
+ task :default => :test
6
+
7
+ desc 'Run specs'
8
+ task :test => FileList['test/*_test.rb'] do |t|
9
+ suite = t.prerequisites.map{|f| "-r#{f.chomp('.rb')}"}.join(' ')
10
+ sh "ruby -Ilib:test #{suite} -e ''", :verbose => false
11
+ end
12
+
13
+ desc 'Generate rdoc'
14
+ task :rdoc do
15
+ rm_rf 'doc/api'
16
+ sh((<<-SH).gsub(/[\s\n]+/, ' ').strip)
17
+ hanna
18
+ --inline-source
19
+ --line-numbers
20
+ --include=lib/cloudkit.rb
21
+ --include=lib/cloudkit/*.rb
22
+ --include=lib/cloudkit/*/*.rb
23
+ --exclude=Rakefile
24
+ --exclude=TODO
25
+ --exclude=cloudkit.gemspec
26
+ --exclude=templates/*
27
+ --exclude=examples/*
28
+ --exclude=test/*
29
+ --exclude=doc/index.html
30
+ --exclude=doc/curl.html
31
+ --exclude=doc/rest-api.html
32
+ --exclude=doc/main.css
33
+ --op=doc/api
34
+ SH
35
+ end
data/TODO ADDED
@@ -0,0 +1,22 @@
1
+ 1.0
2
+ - jquery.cloudkit
3
+ titanium/gears
4
+ - openid sreg
5
+ - titanium middleware
6
+ version/upgrade middleware
7
+ rake automation
8
+ - oauth token management
9
+ - oauth consumer registration
10
+ - acls i.e. allowed methods per user per uri
11
+ - complete user data export
12
+ - method filtering on collections
13
+ - js functions as observers (validation, mapping, etc.)
14
+ - custom templates for openid / oauth
15
+
16
+ Backlog
17
+ - expect header/100-continue
18
+ - deployable gem + admin app
19
+ - cappuccino adapter
20
+ - sproutcore adapter
21
+ - tokyocabinet
22
+ - ldap adapter for UserStore
data/cloudkit.gemspec ADDED
@@ -0,0 +1,82 @@
1
+ Gem::Specification.new do |s|
2
+ s.specification_version = 2 if s.respond_to? :specification_version=
3
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
4
+ s.name = "cloudkit"
5
+ s.version = "0.9.0"
6
+ s.date = "2008-12-22"
7
+ s.summary = "An Open Web JSON Appliance."
8
+ s.description = "An Open Web JSON Appliance."
9
+ s.authors = ["Jon Crosby"]
10
+ s.email = "jon@joncrosby.me"
11
+ s.homepage = "http://getcloudkit.com"
12
+ s.files = %w[
13
+ CHANGES
14
+ cloudkit.gemspec
15
+ COPYING
16
+ README
17
+ Rakefile
18
+ TODO
19
+ doc/curl.html
20
+ doc/images/example-code.gif
21
+ doc/images/json-title.gif
22
+ doc/images/oauth-discovery-logo.gif
23
+ doc/images/openid-logo.gif
24
+ doc/index.html
25
+ doc/main.css
26
+ doc/rest-api.html
27
+ examples/1.ru
28
+ examples/2.ru
29
+ examples/3.ru
30
+ examples/4.ru
31
+ examples/5.ru
32
+ examples/6.ru
33
+ examples/TOC
34
+ lib/cloudkit.rb
35
+ lib/cloudkit/flash_session.rb
36
+ lib/cloudkit/oauth_filter.rb
37
+ lib/cloudkit/oauth_store.rb
38
+ lib/cloudkit/openid_filter.rb
39
+ lib/cloudkit/openid_store.rb
40
+ lib/cloudkit/rack/builder.rb
41
+ lib/cloudkit/rack/router.rb
42
+ lib/cloudkit/request.rb
43
+ lib/cloudkit/service.rb
44
+ lib/cloudkit/store.rb
45
+ lib/cloudkit/store/adapter.rb
46
+ lib/cloudkit/store/extraction_view.rb
47
+ lib/cloudkit/store/response.rb
48
+ lib/cloudkit/store/response_helpers.rb
49
+ lib/cloudkit/store/sql_adapter.rb
50
+ lib/cloudkit/templates/authorize_request_token.erb
51
+ lib/cloudkit/templates/oauth_descriptor.erb
52
+ lib/cloudkit/templates/oauth_meta.erb
53
+ lib/cloudkit/templates/openid_login.erb
54
+ lib/cloudkit/templates/request_authorization.erb
55
+ lib/cloudkit/templates/request_token_denied.erb
56
+ lib/cloudkit/user_store.rb
57
+ lib/cloudkit/util.rb
58
+ test/ext_test.rb
59
+ test/flash_session_test.rb
60
+ test/helper.rb
61
+ test/oauth_filter_test.rb
62
+ test/oauth_store_test.rb
63
+ test/openid_filter_test.rb
64
+ test/openid_store_test.rb
65
+ test/rack_builder_test.rb
66
+ test/request_test.rb
67
+ test/service_test.rb
68
+ test/store_test.rb
69
+ test/user_store_test.rb
70
+ test/util_test.rb
71
+ ]
72
+ s.test_files = s.files.select {|path| path =~ /^test\/.*_test.rb/}
73
+ s.rubyforge_project = "cloudkit"
74
+ s.rubygems_version = "1.1.1"
75
+ s.add_dependency 'rack', '~> 0.4'
76
+ s.add_dependency 'rack-config', '>= 0.9'
77
+ s.add_dependency 'uuid', '= 2.0.1'
78
+ s.add_dependency 'sequel', '= 2.6.0'
79
+ s.add_dependency 'oauth', '>= 0.2.7'
80
+ s.add_dependency 'ruby-openid', '= 2.1.2'
81
+ s.add_dependency 'json', '= 1.1.3'
82
+ end
data/doc/curl.html ADDED
@@ -0,0 +1,329 @@
1
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
2
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
3
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
4
+ <head>
5
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
6
+ <title>CloudKit via cURL</title>
7
+ <link rel="stylesheet" href="main.css" type="text/css" charset="utf-8"/>
8
+ </head>
9
+ <body>
10
+ <div class="wrapper">
11
+ <ul class="nav">
12
+ <li><a href="rest-api.html" title="REST API">REST API</a></li>
13
+ <li><a href="curl.html" title="cURL Tutorial">cURL</a></li>
14
+ <li><a href="api" title="RDoc">RDoc</a></li>
15
+ <li><a href="http://github.com/jcrosby/cloudkit" title="GitHub Source Repository">Code</a></li>
16
+ <li><a href="http://blog.joncrosby.me" title="Author's Blog">Blog</a></li>
17
+ </ul>
18
+ </div>
19
+ <div id="header">
20
+ <div class="wrapper">
21
+ <h1><a href="index.html">CloudKit</a></h1>
22
+ <div class="subpage-subtitle">via cURL</div>
23
+ </div>
24
+ </div>
25
+ <div class="meta">
26
+ <p class="wrapper">
27
+ This is a tour of the CloudKit <a href="http://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm">REST</a>/HTTP 1.1 API
28
+ using <a href="http://curl.haxx.se/docs/manpage.html">curl</a>. For the complete
29
+ spec, see the <a href="rest-api.html">CloudKit REST API</a>.
30
+ </p>
31
+ </div>
32
+ <div class="wrapper">
33
+ <p>
34
+ If you haven't already installed the gem:
35
+ <div class="code">
36
+ $ gem install cloudkit
37
+ </div>
38
+ </p>
39
+
40
+ <p>
41
+ Create a rackup file named config.ru, containing these two lines of code:
42
+ <div class="code">
43
+ require 'cloudkit'<br/>
44
+ expose :notes
45
+ </div>
46
+ </p>
47
+
48
+ <p>
49
+ Run the app using <a href="http://code.macournoyer.com/thin">Thin</a>:
50
+ <div class="code">
51
+ $ thin start -R config.ru
52
+ </div>
53
+ </p>
54
+
55
+ <p>
56
+ CloudKit is discoverable from top to bottom. Let's see what resource collections
57
+ we're hosting:
58
+ <div class="code">
59
+ $ curl -i http://localhost:3000/cloudkit-meta<br/>
60
+ HTTP/1.1 200 OK<br/>
61
+ ETag: "ef2f29b1834ef8c2bf0d8f1abb100177"<br/>
62
+ Cache-Control: proxy-revalidate<br/>
63
+ Content-Type: application/json<br/>
64
+ Content-Length: 20<br/>
65
+ Connection: keep-alive<br/><br/>
66
+
67
+ {"uris":["\/notes"]}
68
+ </div>
69
+ </p>
70
+
71
+ <p>
72
+ See what we can do with these note resources:
73
+ <div class="code">
74
+ $ curl -i -XOPTIONS http://localhost:3000/notes<br/>
75
+ HTTP/1.1 200 OK<br/>
76
+ Content-Length: 0<br/>
77
+ Allow: GET, HEAD, POST, OPTIONS<br/>
78
+ Connection: keep-alive<br/>
79
+ </div>
80
+ </p>
81
+
82
+ <p>
83
+ List the currently available notes:
84
+ <div class="code">
85
+ $ curl -i http://localhost:3000/notes<br/>
86
+ HTTP/1.1 200 OK<br/>
87
+ ETag: "df392c5664e6ecd64b83210fb925f6c8"<br/>
88
+ Cache-Control: proxy-revalidate<br/>
89
+ Content-Type: application/json<br/>
90
+ Content-Length: 32<br/>
91
+ Connection: keep-alive<br/><br/>
92
+
93
+ {"uris":[],"total":0,"offset":0}
94
+ </div>
95
+ </p>
96
+
97
+ <p>
98
+ The Link header will be explained momentarily.
99
+ </p>
100
+
101
+ <p>
102
+ Create a note using <a href="http://tools.ietf.org/html/rfc2616#section-9.5">POST</a>:
103
+ <div class="code">
104
+ $ curl -i -XPOST -d'{"title":"projects"}' http://localhost:3000/notes<br/>
105
+ HTTP/1.1 201 Created<br/>
106
+ Cache-Control: no-cache<br/>
107
+ Content-Type: application/json<br/>
108
+ Content-Length: 159<br/>
109
+ Connection: keep-alive<br/><br/>
110
+
111
+ {<br/>
112
+ "uri":"\/notes\/0dda06f0-b134-012b-a2d8-0017f2c62348",<br/>
113
+ "ok":true,<br/>
114
+ "etag":"0dda0de0-b134-012b-a2d8-0017f2c62348",<br/>
115
+ "last_modified":"Sun, 21 Dec 2008 02:21:52 GMT"<br/>
116
+ }
117
+ </div>
118
+ </p>
119
+
120
+ <p>
121
+ List the currently available notes again:
122
+ <div class="code">
123
+ $ curl -i http://localhost:3000/notes<br/>
124
+ HTTP/1.1 200 OK<br/>
125
+ Last-Modified: Sun, 21 Dec 2008 02:21:52 GMT<br/>
126
+ ETag: "dd30142ff023386d2515b41fb88447a5"<br/>
127
+ Cache-Control: proxy-revalidate<br/>
128
+ Content-Type: application/json<br/>
129
+ Content-Length: 79<br/>
130
+ Connection: keep-alive<br/><br/>
131
+
132
+ {<br/>
133
+ "uris":["\/notes\/0dda06f0-b134-012b-a2d8-0017f2c62348"],<br/>
134
+ "total":1,<br/>
135
+ "offset":0<br/>
136
+ }
137
+ </div>
138
+ </p>
139
+
140
+ <p>
141
+ Create a note using <a href="http://tools.ietf.org/html/rfc2616#section-9.6">PUT</a>
142
+ so that we can specify its location:
143
+ <div class="code">
144
+ $ curl -i -XPUT -d'{"title":"reminders"}' http://localhost:3000/notes/abc<br/>
145
+ HTTP/1.1 201 Created<br/>
146
+ Cache-Control: no-cache<br/>
147
+ Content-Type: application/json<br/>
148
+ Content-Length: 126<br/>
149
+ Connection: keep-alive<br/><br/>
150
+
151
+ {<br/>
152
+ "uri":"\/notes\/abc",<br/>
153
+ "ok":true,<br/>
154
+ "etag":"89487620-b134-012b-a2d8-0017f2c62348",<br/>
155
+ "last_modified":"Sun, 21 Dec 2008 02:25:19 GMT"<br/>
156
+ }
157
+ </div>
158
+ </p>
159
+
160
+ <p>
161
+ View the new note:
162
+ <div class="code">
163
+ $ curl -i http://localhost:3000/notes/abc<br/>
164
+ HTTP/1.1 200 OK<br/>
165
+ Last-Modified: Sun, 21 Dec 2008 02:25:19 GMT<br/>
166
+ ETag: "89487620-b134-012b-a2d8-0017f2c62348"<br/>
167
+ Link: &lt;http://localhost:3000/notes/abc/versions&gt;; rel="http://joncrosby.me/cloudkit/1.0/rel/versions"<br/>
168
+ Cache-Control: proxy-revalidate<br/>
169
+ Content-Type: application/json<br/>
170
+ Content-Length: 21<br/>
171
+ Connection: keep-alive<br/><br/>
172
+
173
+ {"title":"reminders"}
174
+ </div>
175
+ </p>
176
+
177
+ <p>
178
+ Along with the usual metadata, individual resources also provide discovery
179
+ information via
180
+ <a href="http://www.mnot.net/drafts/draft-nottingham-http-link-header-03.txt">Link Headers</a>
181
+ as shown above. These links allow user agents to find related resources such as
182
+ the complete history of a document and its
183
+ <a href="http://tools.ietf.org/html/rfc2616#section-14.19">ETags</a>.
184
+ The utility of these items will be demonstrated momentarily.
185
+ </p>
186
+
187
+ <p>
188
+ Attempt a careless update and enjoy the failure:
189
+ <div class="code">
190
+ $ curl -i -XPUT -d'{"title":"foo"}' http://localhost:3000/notes/abc<br/>
191
+ HTTP/1.1 400 Bad Request<br/>
192
+ Cache-Control: no-cache<br/>
193
+ Content-Type: application/json<br/>
194
+ Content-Length: 25<br/>
195
+ Connection: keep-alive<br/><br/>
196
+
197
+ {"error":"etag required"}
198
+ </div>
199
+ </p>
200
+
201
+ <p>
202
+ Succeed in updating by being specific:
203
+ <div class="code">
204
+ $ curl -i -XPUT -H'If-Match:89487620-b134-012b-a2d8-0017f2c62348' -d'{"title":"foo"}' http://localhost:3000/notes/abc<br/>
205
+ HTTP/1.1 200 OK<br/>
206
+ Cache-Control: no-cache<br/>
207
+ Content-Type: application/json<br/>
208
+ Content-Length: 126<br/>
209
+ Connection: keep-alive<br/><br/>
210
+
211
+ {<br/>
212
+ "uri":"\/notes\/abc",<br/>
213
+ "ok":true,<br/>
214
+ "etag":"522be9f0-b135-012b-a2d8-0017f2c62348",<br/>
215
+ "last_modified":"Sun, 21 Dec 2008 02:30:56 GMT"<br/>
216
+ }
217
+ </div>
218
+ </p>
219
+
220
+ <p>
221
+ (Note: Your ETag will likely be different so substitute the one that curl
222
+ provided when you created your own "abc" resource.)
223
+ </p>
224
+
225
+ <p>
226
+ Watch a secondary, out-of-date client fail at updating by being specific but also being stale:
227
+ <div class="code">
228
+ $ curl -i -XPUT -H'If-Match:89487620-b134-012b-a2d8-0017f2c62348' -d'{"title":"foo"}' http://localhost:3000/notes/abc<br/>
229
+ HTTP/1.1 412 Precondition Failed<br/>
230
+ Cache-Control: no-cache<br/>
231
+ Content-Type: application/json<br/>
232
+ Content-Length: 31<br/>
233
+ Connection: keep-alive<br/><br/>
234
+
235
+ {"error":"precondition failed"}
236
+ </div>
237
+ </p>
238
+
239
+ <p>
240
+ A <a href="http://tools.ietf.org/html/rfc2616#section-10.4.13">412</a> code is
241
+ returned indicating a precondition for this request failed. Specifically, the
242
+ ETag was out of date. In this case, our second client can fall back on the
243
+ resource's history to "catch up" and apply its changes to the most recent
244
+ version of the resource.
245
+ </p>
246
+
247
+ <p>
248
+ Get all versions of the document using the URI tagged with rel="versions" in the
249
+ link header mentioned above, reverse sorted by Last-Modified, feed style:
250
+ <div class="code">
251
+ $ curl -i http://localhost:3000/notes/abc/versions<br/>
252
+ HTTP/1.1 200 OK<br/>
253
+ Last-Modified: Sun, 21 Dec 2008 02:30:56 GMT<br/>
254
+ ETag: "bd9226d0c5f34fd761f6c64a0fe8bcc9"<br/>
255
+ Cache-Control: proxy-revalidate<br/>
256
+ Content-Type: application/json<br/>
257
+ Content-Length: 109<br/>
258
+ Connection: keep-alive<br/><br/>
259
+
260
+ {<br/>
261
+ "uris":[<br/>
262
+ "\/notes\/abc",<br/>
263
+ "\/notes\/abc\/versions\/89487620-b134-012b-a2d8-0017f2c62348"<br/>
264
+ ],<br/>
265
+ "total":2,<br/>
266
+ "offset":0<br/>
267
+ }
268
+ </div>
269
+ </p>
270
+
271
+ <p>
272
+ Delete the document:
273
+ <div class="code">
274
+ $ curl -i -XDELETE -H'If-Match:522be9f0-b135-012b-a2d8-0017f2c62348' http://localhost:3000/notes/abc<br/>
275
+ HTTP/1.1 200 OK<br/>
276
+ Cache-Control: no-cache<br/>
277
+ Content-Type: application/json<br/>
278
+ Content-Length: 174<br/>
279
+ Connection: keep-alive<br/><br/>
280
+
281
+ {<br/>
282
+ "uri":"\/notes\/abc\/versions\/522be9f0-b135-012b-a2d8-0017f2c62348",<br/>
283
+ "ok":true,<br/>
284
+ "etag":"522be9f0-b135-012b-a2d8-0017f2c62348",<br/>
285
+ "last_modified":"Sun, 21 Dec 2008 02:30:56 GMT"<br/>
286
+ }
287
+ </div>
288
+ </p>
289
+
290
+ <p>
291
+ Try to GET it again and notice the helpful <a href="http://tools.ietf.org/html/rfc2616#section-10.4.11">410</a>:
292
+ <div class="code">
293
+ $ curl -i http://localhost:3000/notes/abc<br/>
294
+ HTTP/1.1 410 Gone<br/>
295
+ Link: &lt;http://localhost:3000/notes/abc/versions&gt;; rel="http://joncrosby.me/cloudkit/1.0/rel/versions"<br/>
296
+ Cache-Control: no-cache<br/>
297
+ Content-Type: application/json<br/>
298
+ Content-Length: 37<br/>
299
+ Connection: keep-alive<br/><br/>
300
+
301
+ {"error":"entity previously deleted"}
302
+ </div>
303
+ </p>
304
+
305
+ <p>
306
+ Notice the history is preserved:
307
+ <div class="code">
308
+ $ curl -i http://localhost:3000/notes/abc/versions<br/>
309
+ HTTP/1.1 200 OK<br/>
310
+ Last-Modified: Sun, 21 Dec 2008 02:30:56 GMT<br/>
311
+ ETag: "2308ee33e953c9be41221ff7612e5217"<br/>
312
+ Cache-Control: proxy-revalidate<br/>
313
+ Content-Type: application/json<br/>
314
+ Content-Length: 157<br/>
315
+ Connection: keep-alive<br/><br/>
316
+
317
+ {<br/>
318
+ "uris":[<br/>
319
+ "\/notes\/abc\/versions\/522be9f0-b135-012b-a2d8-0017f2c62348",<br/>
320
+ "\/notes\/abc\/versions\/89487620-b134-012b-a2d8-0017f2c62348"<br/>
321
+ ],<br/>
322
+ "total":2,<br/>
323
+ "offset":0<br/>
324
+ }
325
+ </div>
326
+ </p>
327
+ </div>
328
+ </body>
329
+ </html>