diff options
author | Jan Lehnardt <jan@apache.org> | 2008-07-06 18:52:56 +0000 |
---|---|---|
committer | Jan Lehnardt <jan@apache.org> | 2008-07-06 18:52:56 +0000 |
commit | c48e8ef02d8ba7a93dbb3e72c7be3441cb6bacbf (patch) | |
tree | 4500417f298159aaa03395be13d68d945cddb0d6 | |
parent | c6478386af0bce72be0910b541f8fd4ee5b9455f (diff) |
Add RESTful API for document attachments as per http://groups.google.com/group/couchdb/browse_thread/thread/c84c5f35afb5db2a with not yet comprehensive tests.
git-svn-id: https://svn.apache.org/repos/asf/incubator/couchdb/trunk@674334 13f79535-47bb-0310-9956-ffa450edef68
-rw-r--r-- | share/www/script/couch.js | 10 | ||||
-rw-r--r-- | share/www/script/couch_tests.js | 55 | ||||
-rw-r--r-- | src/couchdb/couch_httpd.erl | 108 |
3 files changed, 170 insertions, 3 deletions
diff --git a/share/www/script/couch.js b/share/www/script/couch.js index 861fd070..38c9cf34 100644 --- a/share/www/script/couch.js +++ b/share/www/script/couch.js @@ -80,6 +80,16 @@ function CouchDB(name) { return result; } + // Deletes an attachment from a document + this.deleteDocAttachment = function(doc, attachment_name) { + var req = request("DELETE", this.uri + encodeURIComponent(doc._id) + "/" + attachment_name + "?rev=" + doc._rev); + var result = JSON.parse(req.responseText); + if (req.status != 200) + throw result; + doc._rev = result.rev; //record rev in input document + return result; + } + this.bulkSave = function(docs, options) { var req = request("POST", this.uri + "_bulk_docs" + encodeOptions(options), { body: JSON.stringify({"docs": docs}) diff --git a/share/www/script/couch_tests.js b/share/www/script/couch_tests.js index 213c5cfc..a0e6b2ff 100644 --- a/share/www/script/couch_tests.js +++ b/share/www/script/couch_tests.js @@ -556,8 +556,59 @@ var tests = { T(db.save(binAttDoc).ok); var xhr = CouchDB.request("GET", "/test_suite_db/bin_doc/foo.txt"); - T(xhr.responseText == "This is a base64 encoded text") - T(xhr.getResponseHeader("Content-Type") == "text/plain") + T(xhr.responseText == "This is a base64 encoded text"); + T(xhr.getResponseHeader("Content-Type") == "text/plain"); + + // test RESTful doc API + + // test without rev, should fail + var xhr = CouchDB.request("DELETE", "/test_suite_db/bin_doc/foo.txt"); + T(xhr.status == 412); + + db.deleteDocAttachment({_id:"bin_doc"}, "foo.txt"); + var xhr = CouchDB.request("GET", "/test_suite_db/bin_doc/foo.txt"); + T(xhr.status == 404); + + var xhr = CouchDB.request("PUT", "/test_suite_db/bin_doc/foo.txt", { + body: "This is not base64 encoded text", + headers:{"Content-Type":"text/plain;charset=utf-8"} + }); + T(xhr.status == 201); + + var xhr = CouchDB.request("GET", "/test_suite_db/bin_doc/foo.txt"); + T(xhr.responseText == "This is not base64 encoded text"); + T(xhr.getResponseHeader("Content-Type") == "text/plain;charset=utf-8"); + + // test binary data + var xhr = CouchDB.request("GET", "/favicon.ico"); + var bin_data = xhr.responseText; + // bin_data = 123; + var xhr = CouchDB.request("PUT", "/test_suite_db/bin_doc/favicon.ico", { + headers:{"Content-Type":"image/vnd.microsoft.icon"}, + body:bin_data + }); + T(xhr.status == 201); + var rev = JSON.parse(xhr.responseText).rev; + + var xhr = CouchDB.request("GET", "/test_suite_db/bin_doc/favicon.ico"); + T(xhr.responseText == bin_data); + T(xhr.getResponseHeader("Content-Type") == "image/vnd.microsoft.icon"); + + var xhr = CouchDB.request("PUT", "/test_suite_db/bin_doc/favicon.ico", { + headers:{"Content-Type":"image/vnd.microsoft.icon"}, + body:bin_data + }); + T(xhr.status == 412); + + var xhr = CouchDB.request("PUT", "/test_suite_db/bin_doc/favicon.ico?rev="+rev, { + headers:{"Content-Type":"image/vnd.microsoft.icon"}, + body:bin_data + }); + T(xhr.status == 201); + var rev = JSON.parse(xhr.responseText).rev; + + var xhr = CouchDB.request("DELETE", "/test_suite_db/bin_doc/foo.txt?rev="+rev); + T(xhr.status == 200); }, content_negotiation: function(debug) { diff --git a/src/couchdb/couch_httpd.erl b/src/couchdb/couch_httpd.erl index 6ae51411..5615ac3e 100644 --- a/src/couchdb/couch_httpd.erl +++ b/src/couchdb/couch_httpd.erl @@ -633,8 +633,114 @@ handle_attachment_request(Req, 'GET', _DbName, Db, DocId, FileName) -> throw(Error) end; +handle_attachment_request(Req, 'DELETE', _DbName, Db, DocId, FileName) -> + QueryRev = proplists:get_value("rev", Req:parse_qs()), + Etag = case Req:get_header_value("If-Match") of + undefined -> + undefined; + Tag -> + string:strip(Tag, both, $") + end, + case {QueryRev, Etag} of + {undefined, undefined} -> + throw({missing_rev, "Document rev/etag must be specified to delete"}); + {_, undefined} -> + QueryRev; + {undefined, _} -> + Etag; + _ when QueryRev == Etag -> + Etag; + _ -> + throw({bad_request, "Document rev and etag have different values"}) + end, + + case couch_db:open_doc(Db, DocId, []) of + {ok, Doc} -> + #doc{attachments=Attachments,revs=Revs} = Doc, + case proplists:get_value(FileName, Attachments) of + undefined -> + throw({not_found, missing}); + {_Type, _Bin} -> + + NewAttachmentList = proplists:delete(FileName, Attachments), + + {ok, NewRev} = couch_db:update_doc(Db, Doc#doc{ + id=DocId, + revs=Revs, + attachments=NewAttachmentList + }, []), + + send_json(Req, 200, {obj, [ + {ok, true}, + {id, DocId}, + {rev, NewRev} + ]}) + end; + Error -> + throw(Error) + end; + +handle_attachment_request(Req, 'PUT', _DbName, Db, DocId, FileName) -> + case couch_db:open_doc(Db, DocId, []) of + {ok, Doc} -> + #doc{attachments=Attachments,revs=OldRevs} = Doc, + + NewAttachments = + case proplists:get_value(FileName, Attachments) of + undefined -> % new attachment, just append to list + Revs = OldRevs, + lists:append(Attachments, [{FileName, { + Req:get_header_value("Content-Type"), + Req:recv_body() + }}]); + + {_Type, _Bin} -> % update of an existing attachment, delete and re-add + QueryRev = proplists:get_value("rev", Req:parse_qs()), + Etag = case Req:get_header_value("If-Match") of + undefined -> + undefined; + Tag -> + string:strip(Tag, both, $") + end, + Revs = case {QueryRev, Etag} of + {undefined, undefined} -> + throw({missing_rev, "Document rev/etag must be specified to delete"}); + {_, undefined} -> + [QueryRev]; + {undefined, _} -> + [Etag]; + _ when QueryRev == Etag -> + [Etag]; + _ -> + throw({bad_request, "Document rev and etag have different values"}) + end, + + lists:append( + proplists:delete(FileName, Attachments), + [{FileName, { + Req:get_header_value("Content-Type"), + Req:recv_body() + } + }]) + end, + + {ok, NewRev} = couch_db:update_doc(Db, Doc#doc{ + id=DocId, + revs=Revs, + attachments=NewAttachments + }, []), + + send_json(Req, 201, {obj, [ + {ok, true}, + {id, DocId}, + {rev, NewRev} + ]}); + Error -> + throw(Error) + end; + handle_attachment_request(_Req, _Method, _DbName, _Db, _DocId, _FileName) -> - throw({method_not_allowed, "GET,HEAD"}). + throw({method_not_allowed, "GET,HEAD,DELETE,PUT"}). % View request handling internals |