Merge pull request #1595 from Abhilasha06/deleteRevisions

Add new API to purge note history
This commit is contained in:
David Mehren 2021-09-07 18:09:42 +02:00 committed by GitHub
commit 67297f71a4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 147 additions and 0 deletions

View file

@ -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,

View file

@ -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);
});
});
});

View file

@ -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: {

View file

@ -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);