Skip to content

Commit

Permalink
feat: add HEAD for entry to the API (#150)
Browse files Browse the repository at this point in the history
* feat: add HEAD for entry to the API

Can be optimized later. Allows to get the revision ID of a document without
transferring it completely.

Closes: #143
  • Loading branch information
targos authored Aug 30, 2018
1 parent 9c91979 commit a5eadac
Show file tree
Hide file tree
Showing 8 changed files with 69 additions and 22 deletions.
27 changes: 14 additions & 13 deletions API.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,20 @@

### Entry

| Method | Route | Action | Description |
| :----: | :-------------------------------------- | :------------------------: | :-------------------------------: |
| POST | `/db/:dbname/entry` | Insert / Update an entry | Based on \_id or $id of the entry |
| GET | `/db/:dbname/entry/_all` | Get all entries | Returns an array of documents |
| GET | `/db/:dbname/entry/:uuid` | Get an entry by UUID | |
| PUT | `/db/:dbname/entry/:uuid` | Update an entry by UUID | |
| DELETE | `/db/:dbname/entry/:uuid` | Delete an an entry by UUID | |
| GET | `/db/:dbname/entry/:uuid/_owner` | Get a list of owners | |
| PUT | `/db/:dbname/entry/:uuid/_owner/:owner` | Add an owner | |
| DELETE | `/db/:dbname/entry/:uuid/_owner/:owner` | Remove an owner | |
| GET | `/db/:dbname/entry/:uuid/:attachment+` | Get an attachment | |
| PUT | `/db/:dbname/entry/:uuid/:attachment+` | Save an attachment | |
| DELETE | `/db/:dbname/entry/:uuid/:attachment+` | Delete an attachment | |
| Method | Route | Action | Description |
| :----: | :-------------------------------------- | :---------------------------------: | :----------------------------------------: |
| POST | `/db/:dbname/entry` | Insert / Update an entry | Based on \_id or $id of the entry |
| GET | `/db/:dbname/entry/_all` | Get all entries | Returns an array of documents |
| HEAD | `/db/:dbname/entry/:uuid` | Get HTTP headers about the document | Similar to HEAD /:dbname/:docid in CouchDB |
| GET | `/db/:dbname/entry/:uuid` | Get an entry by UUID | |
| PUT | `/db/:dbname/entry/:uuid` | Update an entry by UUID | |
| DELETE | `/db/:dbname/entry/:uuid` | Delete an an entry by UUID | |
| GET | `/db/:dbname/entry/:uuid/_owner` | Get a list of owners | |
| PUT | `/db/:dbname/entry/:uuid/_owner/:owner` | Add an owner | |
| DELETE | `/db/:dbname/entry/:uuid/_owner/:owner` | Remove an owner | |
| GET | `/db/:dbname/entry/:uuid/:attachment+` | Get an attachment | |
| PUT | `/db/:dbname/entry/:uuid/:attachment+` | Save an attachment | |
| DELETE | `/db/:dbname/entry/:uuid/:attachment+` | Delete an attachment | |

### User

Expand Down
2 changes: 1 addition & 1 deletion src/server/middleware/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const nanoPromise = require('../../util/nanoPromise');
const isEmail = require('../../util/isEmail');

const respondOk = require('./respondOk');
const decorateError = require('./decorateError');
const { decorateError } = require('./decorateError');

passport.serializeUser(function (user, done) {
done(null, user);
Expand Down
16 changes: 16 additions & 0 deletions src/server/middleware/couch.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,22 @@ exports.getAllDbs = async (ctx) => {
ctx.body = result;
};

exports.headDocument = composeWithError(async (ctx) => {
const result = await ctx.state.couch.getEntry(
ctx.params.uuid,
ctx.state.userEmail,
ctx.query
);
const ifNoneMatch = ctx.request.get('If-None-Match');
const rev = `"${result._rev}"`;
ctx.response.set('ETag', rev);
if (ifNoneMatch === rev) {
ctx.status = 304;
} else {
ctx.status = 200;
}
});

exports.getDocument = composeWithError(async (ctx) => {
ctx.body = await ctx.state.couch.getEntry(
ctx.params.uuid,
Expand Down
18 changes: 13 additions & 5 deletions src/server/middleware/decorateError.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,18 @@ const statusMessages = {

function decorateError(ctx, status, error = true) {
ctx.status = status;
ctx.body = {
error,
code: statusMessages[status] || `error ${status}`
};
if (responseHasBody(ctx)) {
ctx.body = {
error,
code: statusMessages[status] || `error ${status}`
};
}
}

module.exports = decorateError;
function responseHasBody(ctx) {
const method = ctx.method;
if (method === 'HEAD' || method === 'OPTIONS') return false;
return true;
}

module.exports = { decorateError, responseHasBody };
4 changes: 2 additions & 2 deletions src/server/middleware/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const compose = require('koa-compose');
const config = require('../../config/config').globalConfig;
const debug = require('../../util/debug')('middleware:util');

const decorateError = require('./decorateError');
const { decorateError, responseHasBody } = require('./decorateError');

exports.parseBody = function (options) {
return bodyParser(options);
Expand Down Expand Up @@ -68,7 +68,7 @@ function onGetError(ctx, e, secure) {
}
break;
}
if (config.debugrest) {
if (config.debugrest && responseHasBody(ctx)) {
ctx.body.stack = e.stack;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/server/middleware/zenodo.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const config = require('../../config/config').globalConfig;
const debug = require('../../util/debug')('zenodo');
const { RocZenodo } = require('../../roc-zenodo');

const decorateError = require('./decorateError');
const { decorateError } = require('./decorateError');
const { composeWithError } = require('./util');

let rocZenodoProd = new RocZenodo({
Expand Down
1 change: 1 addition & 0 deletions src/server/routes/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ router.use(couch.tokenLookup);
// Entries
router.post('/:dbname/entry', parseJson100mb, couch.newOrUpdateEntry);
router.get('/:dbname/entry/_all', couch.allEntries);
router.head('/:dbname/entry/:uuid', couch.headDocument);
router.get('/:dbname/entry/:uuid', couch.getDocument);
router.put('/:dbname/entry/:uuid', parseJson100mb, couch.updateEntry);
router.delete('/:dbname/entry/:uuid', couch.deleteEntry);
Expand Down
21 changes: 21 additions & 0 deletions test/rest-api/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,27 @@ describe('basic rest-api as b@b.com', () => {
return data().then(() => authenticateAs(request, 'b@b.com', '123'));
});

test('head existing entry', async () => {
const entry = await couch.getEntryById('A', 'b@b.com');
return request
.head(`/db/test/entry/${entry._id}`)
.expect(200)
.expect('ETag', `"${entry._rev}"`);
});

test('head non-existing entry', async () => {
return request.head('/db/test/entry/bad').expect(404);
});

test('head with if-none-match', async () => {
const entry = await couch.getEntryById('A', 'b@b.com');
return request
.head(`/db/test/entry/${entry._id}`)
.set('If-None-Match', `"${entry._rev}"`)
.expect(304)
.expect('ETag', `"${entry._rev}"`);
});

test('get an entry', () => {
return couch.getEntryById('A', 'b@b.com').then((entry) => {
return request.get(`/db/test/entry/${entry._id}`).expect(200);
Expand Down

0 comments on commit a5eadac

Please sign in to comment.