Prevents accidental overwriting of another users work
This commit is contained in:
@ -39,6 +39,7 @@ class Document extends BaseModel {
|
|||||||
updatedBy: User;
|
updatedBy: User;
|
||||||
url: string;
|
url: string;
|
||||||
views: number;
|
views: number;
|
||||||
|
revision: number;
|
||||||
|
|
||||||
data: Object;
|
data: Object;
|
||||||
|
|
||||||
@ -168,6 +169,7 @@ class Document extends BaseModel {
|
|||||||
id: this.id,
|
id: this.id,
|
||||||
title: this.title,
|
title: this.title,
|
||||||
text: this.text,
|
text: this.text,
|
||||||
|
lastRevision: this.revision,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
if (!this.title) {
|
if (!this.title) {
|
||||||
|
@ -232,7 +232,7 @@ class DocumentScene extends Component {
|
|||||||
message={DISCARD_CHANGES}
|
message={DISCARD_CHANGES}
|
||||||
/>
|
/>
|
||||||
<Editor
|
<Editor
|
||||||
key={document.id}
|
key={`${document.id}-${document.revision}`}
|
||||||
text={document.text}
|
text={document.text}
|
||||||
emoji={document.emoji}
|
emoji={document.emoji}
|
||||||
onImageUploadStart={this.onImageUploadStart}
|
onImageUploadStart={this.onImageUploadStart}
|
||||||
|
@ -45,6 +45,15 @@ Object {
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`#documents.update should fail if document lastRevision does not match 1`] = `
|
||||||
|
Object {
|
||||||
|
"error": "document_has_changed_since_last_revision",
|
||||||
|
"message": "Document has changed since last revision",
|
||||||
|
"ok": false,
|
||||||
|
"status": 400,
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`#documents.viewed should require authentication 1`] = `
|
exports[`#documents.viewed should require authentication 1`] = `
|
||||||
Object {
|
Object {
|
||||||
"error": "authentication_required",
|
"error": "authentication_required",
|
||||||
|
@ -242,7 +242,7 @@ router.post('documents.create', auth(), async ctx => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
router.post('documents.update', auth(), async ctx => {
|
router.post('documents.update', auth(), async ctx => {
|
||||||
const { id, title, text } = ctx.body;
|
const { id, title, text, lastRevision } = ctx.body;
|
||||||
ctx.assertPresent(id, 'id is required');
|
ctx.assertPresent(id, 'id is required');
|
||||||
ctx.assertPresent(title || text, 'title or text is required');
|
ctx.assertPresent(title || text, 'title or text is required');
|
||||||
|
|
||||||
@ -250,6 +250,10 @@ router.post('documents.update', auth(), async ctx => {
|
|||||||
const document = await Document.findById(id);
|
const document = await Document.findById(id);
|
||||||
const collection = document.collection;
|
const collection = document.collection;
|
||||||
|
|
||||||
|
if (lastRevision && lastRevision !== document.revisionCount) {
|
||||||
|
throw httpErrors.BadRequest('Document has changed since last revision');
|
||||||
|
}
|
||||||
|
|
||||||
authDocumentForUser(ctx, document);
|
authDocumentForUser(ctx, document);
|
||||||
|
|
||||||
// Update document
|
// Update document
|
||||||
|
@ -277,6 +277,7 @@ describe('#documents.update', async () => {
|
|||||||
id: document.id,
|
id: document.id,
|
||||||
title: 'Updated title',
|
title: 'Updated title',
|
||||||
text: 'Updated text',
|
text: 'Updated text',
|
||||||
|
lastRevision: document.revision,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const body = await res.json();
|
const body = await res.json();
|
||||||
@ -287,6 +288,23 @@ describe('#documents.update', async () => {
|
|||||||
expect(body.data.collection.documents[1].title).toBe('Updated title');
|
expect(body.data.collection.documents[1].title).toBe('Updated title');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should fail if document lastRevision does not match', async () => {
|
||||||
|
const { user, document } = await seed();
|
||||||
|
|
||||||
|
const res = await server.post('/api/documents.update', {
|
||||||
|
body: {
|
||||||
|
token: user.getJwtToken(),
|
||||||
|
id: document.id,
|
||||||
|
text: 'Updated text',
|
||||||
|
lastRevision: 123,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const body = await res.json();
|
||||||
|
|
||||||
|
expect(res.status).toEqual(400);
|
||||||
|
expect(body).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
it('should update document details for children', async () => {
|
it('should update document details for children', async () => {
|
||||||
const { user, document, collection } = await seed();
|
const { user, document, collection } = await seed();
|
||||||
collection.documentStructure = [
|
collection.documentStructure = [
|
||||||
|
@ -36,6 +36,7 @@ async function present(ctx: Object, document: Document, options: ?Options) {
|
|||||||
team: document.teamId,
|
team: document.teamId,
|
||||||
collaborators: [],
|
collaborators: [],
|
||||||
starred: !!(document.starred && document.starred.length),
|
starred: !!(document.starred && document.starred.length),
|
||||||
|
revision: document.revisionCount,
|
||||||
collectionId: document.atlasId,
|
collectionId: document.atlasId,
|
||||||
collaboratorCount: undefined,
|
collaboratorCount: undefined,
|
||||||
collection: undefined,
|
collection: undefined,
|
||||||
|
Reference in New Issue
Block a user