mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2024-11-25 11:16:31 -05:00
Add new API to purge note history #1064
Signed-off-by: Abhilasha Sinha <abhisinha662000@gmail.com> Combine the describe block Signed-off-by: Abhilasha Sinha <abhisinha662000@gmail.com> Fix naming Signed-off-by: Abhilasha Sinha <abhisinha662000@gmail.com> Rename purgeRevision to purgeRevisions Signed-off-by: Abhilasha Sinha <abhisinha662000@gmail.com> Fix notes e2e test description Signed-off-by: Abhilasha Sinha <abhisinha662000@gmail.com> Add yarn.lock Fix lint and format Signed-off-by: Abhilasha Sinha <abhisinha662000@gmail.com>
This commit is contained in:
parent
170f4f6759
commit
f63a2b79b7
4 changed files with 147 additions and 0 deletions
|
@ -182,6 +182,39 @@ export class NotesController {
|
|||
}
|
||||
}
|
||||
|
||||
@Delete(':noteIdOrAlias/revisions')
|
||||
@HttpCode(204)
|
||||
async purgeNoteRevisions(
|
||||
@Param('noteIdOrAlias') noteIdOrAlias: string,
|
||||
): Promise<void> {
|
||||
try {
|
||||
// ToDo: use actual user here
|
||||
const user = await this.userService.getUserByUsername('hardcoded');
|
||||
const note = await this.noteService.getNoteByIdOrAlias(noteIdOrAlias);
|
||||
if (!this.permissionsService.mayRead(user, note)) {
|
||||
throw new UnauthorizedException('Reading note denied!');
|
||||
}
|
||||
this.logger.debug(
|
||||
'Purging history of note: ' + noteIdOrAlias,
|
||||
'purgeNoteRevisions',
|
||||
);
|
||||
await this.revisionsService.purgeRevisions(note);
|
||||
this.logger.debug(
|
||||
'Successfully purged history of note ' + noteIdOrAlias,
|
||||
'purgeNoteRevisions',
|
||||
);
|
||||
return;
|
||||
} catch (e) {
|
||||
if (e instanceof NotInDBError) {
|
||||
throw new NotFoundException(e.message);
|
||||
}
|
||||
if (e instanceof ForbiddenIdError) {
|
||||
throw new BadRequestException(e.message);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@Get(':noteIdOrAlias/revisions/:revisionId')
|
||||
async getNoteRevision(
|
||||
@Param('noteIdOrAlias', GetNotePipe) note: Note,
|
||||
|
|
|
@ -97,4 +97,63 @@ describe('RevisionsService', () => {
|
|||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('purgeRevisions', () => {
|
||||
it('purges the revision history', async () => {
|
||||
const note = {} as Note;
|
||||
note.id = 'test';
|
||||
let revisions: Revision[] = [];
|
||||
const revision1 = Revision.create('a', 'a');
|
||||
revision1.id = 1;
|
||||
const revision2 = Revision.create('b', 'b');
|
||||
revision2.id = 2;
|
||||
const revision3 = Revision.create('c', 'c');
|
||||
revision3.id = 3;
|
||||
revisions.push(revision1, revision2, revision3);
|
||||
note.revisions = Promise.resolve(revisions);
|
||||
jest.spyOn(revisionRepo, 'find').mockResolvedValueOnce(revisions);
|
||||
jest.spyOn(service, 'getLatestRevision').mockResolvedValueOnce(revision3);
|
||||
revisionRepo.remove = jest
|
||||
.fn()
|
||||
.mockImplementation((deleteList: Revision[]) => {
|
||||
revisions = revisions.filter(
|
||||
(item: Revision) => !deleteList.includes(item),
|
||||
);
|
||||
return Promise.resolve(deleteList);
|
||||
});
|
||||
|
||||
// expected to return all the purged revisions
|
||||
expect(await service.purgeRevisions(note)).toHaveLength(2);
|
||||
|
||||
// expected to have only the latest revision
|
||||
const updatedRevisions: Revision[] = [revision3];
|
||||
expect(revisions).toEqual(updatedRevisions);
|
||||
});
|
||||
it('has no effect on revision history when a single revision is present', async () => {
|
||||
const note = {} as Note;
|
||||
note.id = 'test';
|
||||
let revisions: Revision[] = [];
|
||||
const revision1 = Revision.create('a', 'a');
|
||||
revision1.id = 1;
|
||||
revisions.push(revision1);
|
||||
note.revisions = Promise.resolve(revisions);
|
||||
jest.spyOn(revisionRepo, 'find').mockResolvedValueOnce(revisions);
|
||||
jest.spyOn(service, 'getLatestRevision').mockResolvedValueOnce(revision1);
|
||||
revisionRepo.remove = jest
|
||||
.fn()
|
||||
.mockImplementation((deleteList: Revision[]) => {
|
||||
revisions = revisions.filter(
|
||||
(item: Revision) => !deleteList.includes(item),
|
||||
);
|
||||
return Promise.resolve(deleteList);
|
||||
});
|
||||
|
||||
// expected to return all the purged revisions
|
||||
expect(await service.purgeRevisions(note)).toHaveLength(0);
|
||||
|
||||
// expected to have only the latest revision
|
||||
const updatedRevisions: Revision[] = [revision1];
|
||||
expect(revisions).toEqual(updatedRevisions);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -34,6 +34,27 @@ export class RevisionsService {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @async
|
||||
* Purge revision history of a note.
|
||||
* @param {Note} note - the note to purge the history
|
||||
* @return {Revision[]} an array of purged revisions
|
||||
*/
|
||||
async purgeRevisions(note: Note): Promise<Revision[]> {
|
||||
const revisions = await this.revisionRepository.find({
|
||||
where: {
|
||||
note: note,
|
||||
},
|
||||
});
|
||||
const latestRevison = await this.getLatestRevision(note);
|
||||
// get all revisions except the latest
|
||||
const oldRevisions = revisions.filter(
|
||||
(item) => item.id !== latestRevison.id,
|
||||
);
|
||||
// delete the old revisions
|
||||
return await this.revisionRepository.remove(oldRevisions);
|
||||
}
|
||||
|
||||
async getRevision(note: Note, revisionId: number): Promise<Revision> {
|
||||
const revision = await this.revisionRepository.findOne({
|
||||
where: {
|
||||
|
|
|
@ -231,6 +231,40 @@ describe('Notes', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('DELETE /notes/{note}/revisions', () => {
|
||||
it('works with an existing alias', async () => {
|
||||
const noteId = 'test8';
|
||||
const note = await notesService.createNote(content, noteId, user);
|
||||
await notesService.updateNote(note, 'update');
|
||||
const responseBeforeDeleting = await request(app.getHttpServer())
|
||||
.get('/notes/test8/revisions')
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200);
|
||||
expect(responseBeforeDeleting.body).toHaveLength(2);
|
||||
await request(app.getHttpServer())
|
||||
.delete(`/notes/${noteId}/revisions`)
|
||||
.set('Content-Type', 'application/json')
|
||||
.expect(204);
|
||||
const responseAfterDeleting = await request(app.getHttpServer())
|
||||
.get('/notes/test8/revisions')
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200);
|
||||
expect(responseAfterDeleting.body).toHaveLength(1);
|
||||
});
|
||||
it('fails with a forbidden alias', async () => {
|
||||
await request(app.getHttpServer())
|
||||
.delete(`/notes/${forbiddenNoteId}/revisions`)
|
||||
.expect(400);
|
||||
});
|
||||
it('fails with non-existing alias', async () => {
|
||||
// check if a missing note correctly returns 404
|
||||
await request(app.getHttpServer())
|
||||
.delete('/notes/i_dont_exist/revisions')
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(404);
|
||||
});
|
||||
});
|
||||
|
||||
describe('GET /notes/{note}/revisions/{revision-id}', () => {
|
||||
it('works with an existing alias', async () => {
|
||||
const note = await notesService.createNote(content, 'test5', user);
|
||||
|
|
Loading…
Reference in a new issue