diff --git a/Makefile b/Makefile index 5080bbd8..6b4bb0ba 100644 --- a/Makefile +++ b/Makefile @@ -9,8 +9,11 @@ build: test: docker-compose run --rm outline yarn test +watch: + docker-compose run --rm outline yarn test:watch + destroy: docker-compose stop docker-compose rm -f -.PHONY: up build destroy # let's go to reserve rules names +.PHONY: up build destroy test watch # let's go to reserve rules names diff --git a/package.json b/package.json index ee57b100..e48d715f 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "test": "npm run test:app && npm run test:server", "test:app": "jest", "test:server": "jest --config=server/.jestconfig.json --runInBand --forceExit", + "test:watch": "jest --config=server/.jestconfig.json --runInBand --forceExit --watchAll", "precommit": "lint-staged" }, "lint-staged": { diff --git a/server/api/shares.js b/server/api/shares.js index ee4a46db..16cfd7ed 100644 --- a/server/api/shares.js +++ b/server/api/shares.js @@ -61,4 +61,18 @@ router.post('shares.create', auth(), async ctx => { }; }); +router.post('shares.delete', auth(), async ctx => { + const { id } = ctx.body; + ctx.assertPresent(id, 'id is required'); + + const share = await Share.findById(id); + authorize(ctx.state.user, 'delete', share); + + await share.destroy(); + + ctx.body = { + success: true, + }; +}); + export default router; diff --git a/server/api/shares.test.js b/server/api/shares.test.js index 02e437f6..c9d78f85 100644 --- a/server/api/shares.test.js +++ b/server/api/shares.test.js @@ -2,7 +2,7 @@ import TestServer from 'fetch-test-server'; import app from '..'; import { flushdb, seed } from '../test/support'; -import { buildUser } from '../test/factories'; +import { buildUser, buildShare } from '../test/factories'; const server = new TestServer(app.callback()); @@ -11,11 +11,20 @@ afterAll(server.close); describe('#shares.list', async () => { it('should return a list of shares', async () => { - const { user } = await seed(); + const { user, document } = await seed(); + const share = await buildShare({ + documentId: document.id, + teamId: user.teamId, + }); const res = await server.post('/api/shares.list', { body: { token: user.getJwtToken() }, }); + const body = await res.json(); + expect(res.status).toEqual(200); + expect(body.data.length).toEqual(1); + expect(body.data[0].id).toEqual(share.id); + expect(body.data[0].documentTitle).toBe(document.title); }); it('should require authentication', async () => { diff --git a/server/policies/index.js b/server/policies/index.js index b9ac4f5a..2e149240 100644 --- a/server/policies/index.js +++ b/server/policies/index.js @@ -4,6 +4,7 @@ import './apiKey'; import './collection'; import './document'; import './integration'; +import './share'; import './user'; export default policy; diff --git a/server/policies/share.js b/server/policies/share.js new file mode 100644 index 00000000..9872103d --- /dev/null +++ b/server/policies/share.js @@ -0,0 +1,15 @@ +// @flow +import policy from './policy'; +import { Share, User } from '../models'; +import { AdminRequiredError } from '../errors'; + +const { allow } = policy; + +allow(User, ['read'], Share, (user, share) => user.teamId === share.teamId); +allow(User, ['update'], Share, (user, share) => false); +allow(User, ['delete'], Share, (user, share) => { + if (!share || user.teamId !== share.teamId) return false; + if (user.id === share.userId) return false; + if (user.isAdmin) return true; + throw new AdminRequiredError(); +}); diff --git a/server/test/factories.js b/server/test/factories.js index 92e6fb5b..b7620908 100644 --- a/server/test/factories.js +++ b/server/test/factories.js @@ -1,9 +1,22 @@ // @flow -import { Team, User } from '../models'; +import { Share, Team, User } from '../models'; import uuid from 'uuid'; let count = 0; +export async function buildShare(overrides: Object = {}) { + if (!overrides.teamId) { + const team = await buildTeam(); + overrides.teamId = team.id; + } + if (!overrides.userId) { + const user = await buildUser({ teamId: overrides.teamId }); + overrides.userId = user.id; + } + + return Share.create(overrides); +} + export function buildTeam(overrides: Object = {}) { count++;