mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2025-05-18 00:54:43 -04:00
Merge pull request #993 from hedgedoc/publicApi/me
This commit is contained in:
commit
b67ec817e6
4 changed files with 217 additions and 49 deletions
|
@ -59,6 +59,26 @@ export class MeController {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@UseGuards(TokenAuthGuard)
|
||||||
|
@Get('history/:note')
|
||||||
|
async getHistoryEntry(
|
||||||
|
@Req() req: Request,
|
||||||
|
@Param('note') note: string,
|
||||||
|
): Promise<HistoryEntryDto> {
|
||||||
|
try {
|
||||||
|
const foundEntry = await this.historyService.getEntryByNoteIdOrAlias(
|
||||||
|
note,
|
||||||
|
req.user,
|
||||||
|
);
|
||||||
|
return this.historyService.toHistoryEntryDto(foundEntry);
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof NotInDBError) {
|
||||||
|
throw new NotFoundException(e.message);
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@UseGuards(TokenAuthGuard)
|
@UseGuards(TokenAuthGuard)
|
||||||
@Put('history/:note')
|
@Put('history/:note')
|
||||||
async updateHistoryEntry(
|
async updateHistoryEntry(
|
||||||
|
@ -86,13 +106,13 @@ export class MeController {
|
||||||
@UseGuards(TokenAuthGuard)
|
@UseGuards(TokenAuthGuard)
|
||||||
@Delete('history/:note')
|
@Delete('history/:note')
|
||||||
@HttpCode(204)
|
@HttpCode(204)
|
||||||
deleteHistoryEntry(
|
async deleteHistoryEntry(
|
||||||
@Req() req: Request,
|
@Req() req: Request,
|
||||||
@Param('note') note: string,
|
@Param('note') note: string,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
// ToDo: Check if user is allowed to delete note
|
// ToDo: Check if user is allowed to delete note
|
||||||
try {
|
try {
|
||||||
return this.historyService.deleteHistoryEntry(note, req.user);
|
await this.historyService.deleteHistoryEntry(note, req.user);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof NotInDBError) {
|
if (e instanceof NotInDBError) {
|
||||||
throw new NotFoundException(e.message);
|
throw new NotFoundException(e.message);
|
||||||
|
|
|
@ -121,6 +121,32 @@ describe('HistoryService', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('getEntryByNoteIdOrAlias', () => {
|
||||||
|
const user = {} as User;
|
||||||
|
const alias = 'alias';
|
||||||
|
describe('works', () => {
|
||||||
|
it('with history entry', async () => {
|
||||||
|
const note = Note.create(user, alias);
|
||||||
|
const historyEntry = HistoryEntry.create(user, note);
|
||||||
|
jest.spyOn(historyRepo, 'findOne').mockResolvedValueOnce(historyEntry);
|
||||||
|
jest.spyOn(noteRepo, 'findOne').mockResolvedValueOnce(note);
|
||||||
|
expect(await service.getEntryByNoteIdOrAlias(alias, user)).toEqual(
|
||||||
|
historyEntry,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('fails', () => {
|
||||||
|
it('with an non-existing note', async () => {
|
||||||
|
jest.spyOn(noteRepo, 'findOne').mockResolvedValueOnce(undefined);
|
||||||
|
try {
|
||||||
|
await service.getEntryByNoteIdOrAlias(alias, {} as User);
|
||||||
|
} catch (e) {
|
||||||
|
expect(e).toBeInstanceOf(NotInDBError);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('createOrUpdateHistoryEntry', () => {
|
describe('createOrUpdateHistoryEntry', () => {
|
||||||
describe('works', () => {
|
describe('works', () => {
|
||||||
it('without an preexisting entry', async () => {
|
it('without an preexisting entry', async () => {
|
||||||
|
@ -231,10 +257,11 @@ describe('HistoryService', () => {
|
||||||
);
|
);
|
||||||
await service.deleteHistoryEntry(alias, user);
|
await service.deleteHistoryEntry(alias, user);
|
||||||
});
|
});
|
||||||
|
});
|
||||||
it('without an entry', async () => {
|
describe('fails', () => {
|
||||||
const user = {} as User;
|
const user = {} as User;
|
||||||
const alias = 'alias';
|
const alias = 'alias';
|
||||||
|
it('without an entry', async () => {
|
||||||
const note = Note.create(user, alias);
|
const note = Note.create(user, alias);
|
||||||
jest.spyOn(historyRepo, 'findOne').mockResolvedValueOnce(undefined);
|
jest.spyOn(historyRepo, 'findOne').mockResolvedValueOnce(undefined);
|
||||||
jest.spyOn(noteRepo, 'findOne').mockResolvedValueOnce(note);
|
jest.spyOn(noteRepo, 'findOne').mockResolvedValueOnce(note);
|
||||||
|
@ -244,6 +271,14 @@ describe('HistoryService', () => {
|
||||||
expect(e).toBeInstanceOf(NotInDBError);
|
expect(e).toBeInstanceOf(NotInDBError);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
it('without a note', async () => {
|
||||||
|
jest.spyOn(noteRepo, 'findOne').mockResolvedValueOnce(undefined);
|
||||||
|
try {
|
||||||
|
await service.getEntryByNoteIdOrAlias(alias, {} as User);
|
||||||
|
} catch (e) {
|
||||||
|
expect(e).toBeInstanceOf(NotInDBError);
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,12 @@ export class HistoryService {
|
||||||
this.logger.setContext(HistoryService.name);
|
this.logger.setContext(HistoryService.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @async
|
||||||
|
* Get all entries of a user
|
||||||
|
* @param {User} user - the user the entries should be from
|
||||||
|
* @return {HistoryEntry[]} an array of history entries of the specified user
|
||||||
|
*/
|
||||||
async getEntriesByUser(user: User): Promise<HistoryEntry[]> {
|
async getEntriesByUser(user: User): Promise<HistoryEntry[]> {
|
||||||
return await this.historyEntryRepository.find({
|
return await this.historyEntryRepository.find({
|
||||||
where: { user: user },
|
where: { user: user },
|
||||||
|
@ -36,7 +42,15 @@ export class HistoryService {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getEntryByNoteIdOrAlias(
|
/**
|
||||||
|
* @async
|
||||||
|
* Get a history entry by the user and note, which is specified via id or alias
|
||||||
|
* @param {string} noteIdOrAlias - the id or alias specifying the note
|
||||||
|
* @param {User} user - the user that the note belongs to
|
||||||
|
* @throws {NotInDBError} the specified note does not exist
|
||||||
|
* @return {HistoryEntry} the requested history entry
|
||||||
|
*/
|
||||||
|
async getEntryByNoteIdOrAlias(
|
||||||
noteIdOrAlias: string,
|
noteIdOrAlias: string,
|
||||||
user: User,
|
user: User,
|
||||||
): Promise<HistoryEntry> {
|
): Promise<HistoryEntry> {
|
||||||
|
@ -44,6 +58,13 @@ export class HistoryService {
|
||||||
return await this.getEntryByNote(note, user);
|
return await this.getEntryByNote(note, user);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @async
|
||||||
|
* Get a history entry by the user and note
|
||||||
|
* @param {Note} note - the note that the history entry belongs to
|
||||||
|
* @param {User} user - the user that the history entry belongs to
|
||||||
|
* @return {HistoryEntry} the requested history entry
|
||||||
|
*/
|
||||||
private async getEntryByNote(note: Note, user: User): Promise<HistoryEntry> {
|
private async getEntryByNote(note: Note, user: User): Promise<HistoryEntry> {
|
||||||
return await this.historyEntryRepository.findOne({
|
return await this.historyEntryRepository.findOne({
|
||||||
where: {
|
where: {
|
||||||
|
@ -54,6 +75,13 @@ export class HistoryService {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @async
|
||||||
|
* Create or update a history entry by the user and note. If the entry is merely updated the updatedAt date is set to the current date.
|
||||||
|
* @param {Note} note - the note that the history entry belongs to
|
||||||
|
* @param {User} user - the user that the history entry belongs to
|
||||||
|
* @return {HistoryEntry} the requested history entry
|
||||||
|
*/
|
||||||
async createOrUpdateHistoryEntry(
|
async createOrUpdateHistoryEntry(
|
||||||
note: Note,
|
note: Note,
|
||||||
user: User,
|
user: User,
|
||||||
|
@ -67,6 +95,14 @@ export class HistoryService {
|
||||||
return await this.historyEntryRepository.save(entry);
|
return await this.historyEntryRepository.save(entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @async
|
||||||
|
* Update a history entry identified by the user and a note id or alias
|
||||||
|
* @param {string} noteIdOrAlias - the note that the history entry belongs to
|
||||||
|
* @param {User} user - the user that the history entry belongs to
|
||||||
|
* @param {HistoryEntryUpdateDto} updateDto - the change that should be applied to the history entry
|
||||||
|
* @return {HistoryEntry} the requested history entry
|
||||||
|
*/
|
||||||
async updateHistoryEntry(
|
async updateHistoryEntry(
|
||||||
noteIdOrAlias: string,
|
noteIdOrAlias: string,
|
||||||
user: User,
|
user: User,
|
||||||
|
@ -82,6 +118,13 @@ export class HistoryService {
|
||||||
return await this.historyEntryRepository.save(entry);
|
return await this.historyEntryRepository.save(entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @async
|
||||||
|
* Delete the history entry identified by the user and a note id or alias
|
||||||
|
* @param {string} noteIdOrAlias - the note that the history entry belongs to
|
||||||
|
* @param {User} user - the user that the history entry belongs to
|
||||||
|
* @throws {NotInDBError} the specified history entry does not exist
|
||||||
|
*/
|
||||||
async deleteHistoryEntry(noteIdOrAlias: string, user: User): Promise<void> {
|
async deleteHistoryEntry(noteIdOrAlias: string, user: User): Promise<void> {
|
||||||
const entry = await this.getEntryByNoteIdOrAlias(noteIdOrAlias, user);
|
const entry = await this.getEntryByNoteIdOrAlias(noteIdOrAlias, user);
|
||||||
if (!entry) {
|
if (!entry) {
|
||||||
|
@ -93,6 +136,11 @@ export class HistoryService {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build HistoryEntryDto from a history entry.
|
||||||
|
* @param {HistoryEntry} entry - the history entry to use
|
||||||
|
* @return {HistoryEntryDto} the built HistoryEntryDto
|
||||||
|
*/
|
||||||
toHistoryEntryDto(entry: HistoryEntry): HistoryEntryDto {
|
toHistoryEntryDto(entry: HistoryEntry): HistoryEntryDto {
|
||||||
return {
|
return {
|
||||||
identifier: entry.note.alias ? entry.note.alias : entry.note.id,
|
identifier: entry.note.alias ? entry.note.alias : entry.note.id,
|
||||||
|
|
|
@ -33,6 +33,7 @@ import { ConfigModule } from '@nestjs/config';
|
||||||
import mediaConfigMock from '../../src/config/media.config.mock';
|
import mediaConfigMock from '../../src/config/media.config.mock';
|
||||||
import appConfigMock from '../../src/config/app.config.mock';
|
import appConfigMock from '../../src/config/app.config.mock';
|
||||||
import { User } from '../../src/users/user.entity';
|
import { User } from '../../src/users/user.entity';
|
||||||
|
import { NoteMetadataDto } from '../../src/notes/note-metadata.dto';
|
||||||
|
|
||||||
// TODO Tests have to be reworked using UserService functions
|
// TODO Tests have to be reworked using UserService functions
|
||||||
|
|
||||||
|
@ -99,16 +100,55 @@ describe('Notes', () => {
|
||||||
.expect('Content-Type', /json/)
|
.expect('Content-Type', /json/)
|
||||||
.expect(200);
|
.expect(200);
|
||||||
const history = <HistoryEntryDto[]>response.body;
|
const history = <HistoryEntryDto[]>response.body;
|
||||||
|
expect(history.length).toEqual(1);
|
||||||
|
const historyDto = historyService.toHistoryEntryDto(createdHistoryEntry);
|
||||||
for (const historyEntry of history) {
|
for (const historyEntry of history) {
|
||||||
if (historyEntry.identifier === 'testGetHistory') {
|
expect(historyEntry.identifier).toEqual(historyDto.identifier);
|
||||||
expect(historyEntry).toEqual(createdHistoryEntry);
|
expect(historyEntry.title).toEqual(historyDto.title);
|
||||||
}
|
expect(historyEntry.tags).toEqual(historyDto.tags);
|
||||||
|
expect(historyEntry.pinStatus).toEqual(historyDto.pinStatus);
|
||||||
|
expect(historyEntry.lastVisited).toEqual(
|
||||||
|
historyDto.lastVisited.toISOString(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it(`PUT /me/history/{note}`, async () => {
|
describe(`GET /me/history/{note}`, () => {
|
||||||
|
it('works with an existing note', async () => {
|
||||||
const noteName = 'testGetNoteHistory2';
|
const noteName = 'testGetNoteHistory2';
|
||||||
const note = await notesService.createNote('', noteName);
|
const note = await notesService.createNote('', noteName);
|
||||||
|
const createdHistoryEntry = await historyService.createOrUpdateHistoryEntry(
|
||||||
|
note,
|
||||||
|
user,
|
||||||
|
);
|
||||||
|
const response = await request(app.getHttpServer())
|
||||||
|
.get(`/me/history/${noteName}`)
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(200);
|
||||||
|
const historyEntry = <HistoryEntryDto>response.body;
|
||||||
|
const historyEntryDto = historyService.toHistoryEntryDto(
|
||||||
|
createdHistoryEntry,
|
||||||
|
);
|
||||||
|
expect(historyEntry.identifier).toEqual(historyEntryDto.identifier);
|
||||||
|
expect(historyEntry.title).toEqual(historyEntryDto.title);
|
||||||
|
expect(historyEntry.tags).toEqual(historyEntryDto.tags);
|
||||||
|
expect(historyEntry.pinStatus).toEqual(historyEntryDto.pinStatus);
|
||||||
|
expect(historyEntry.lastVisited).toEqual(
|
||||||
|
historyEntryDto.lastVisited.toISOString(),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('fails with a non-existing note', async () => {
|
||||||
|
await request(app.getHttpServer())
|
||||||
|
.get('/me/history/i_dont_exist')
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(404);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe(`PUT /me/history/{note}`, () => {
|
||||||
|
it('works', async () => {
|
||||||
|
const noteName = 'testGetNoteHistory3';
|
||||||
|
const note = await notesService.createNote('', noteName);
|
||||||
await historyService.createOrUpdateHistoryEntry(note, user);
|
await historyService.createOrUpdateHistoryEntry(note, user);
|
||||||
const historyEntryUpdateDto = new HistoryEntryUpdateDto();
|
const historyEntryUpdateDto = new HistoryEntryUpdateDto();
|
||||||
historyEntryUpdateDto.pinStatus = true;
|
historyEntryUpdateDto.pinStatus = true;
|
||||||
|
@ -127,9 +167,17 @@ describe('Notes', () => {
|
||||||
}
|
}
|
||||||
expect(historyEntry.pinStatus).toEqual(true);
|
expect(historyEntry.pinStatus).toEqual(true);
|
||||||
});
|
});
|
||||||
|
it('fails with a non-existing note', async () => {
|
||||||
|
await request(app.getHttpServer())
|
||||||
|
.put('/me/history/i_dont_exist')
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(404);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it(`DELETE /me/history/{note}`, async () => {
|
describe(`DELETE /me/history/{note}`, () => {
|
||||||
const noteName = 'testGetNoteHistory3';
|
it('works', async () => {
|
||||||
|
const noteName = 'testGetNoteHistory4';
|
||||||
const note = await notesService.createNote('', noteName);
|
const note = await notesService.createNote('', noteName);
|
||||||
await historyService.createOrUpdateHistoryEntry(note, user);
|
await historyService.createOrUpdateHistoryEntry(note, user);
|
||||||
const response = await request(app.getHttpServer())
|
const response = await request(app.getHttpServer())
|
||||||
|
@ -145,16 +193,33 @@ describe('Notes', () => {
|
||||||
}
|
}
|
||||||
return expect(historyEntry).toBeNull();
|
return expect(historyEntry).toBeNull();
|
||||||
});
|
});
|
||||||
|
describe('fails', () => {
|
||||||
|
it('with a non-existing note', async () => {
|
||||||
|
await request(app.getHttpServer())
|
||||||
|
.delete('/me/history/i_dont_exist')
|
||||||
|
.expect(404);
|
||||||
|
});
|
||||||
|
it('with a non-existing history entry', async () => {
|
||||||
|
const noteName = 'testGetNoteHistory5';
|
||||||
|
await notesService.createNote('', noteName);
|
||||||
|
await request(app.getHttpServer())
|
||||||
|
.delete(`/me/history/${noteName}`)
|
||||||
|
.expect(404);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it.skip(`GET /me/notes/`, async () => {
|
it(`GET /me/notes/`, async () => {
|
||||||
// TODO use function from HistoryService to add an History Entry
|
const noteName = 'testNote';
|
||||||
await notesService.createNote('This is a test note.', 'test7');
|
await notesService.createNote('', noteName, user);
|
||||||
// usersService.getALLNotesOwnedByUser() TODO Implement function
|
|
||||||
const response = await request(app.getHttpServer())
|
const response = await request(app.getHttpServer())
|
||||||
.get('/me/notes/')
|
.get('/me/notes/')
|
||||||
.expect('Content-Type', /json/)
|
.expect('Content-Type', /json/)
|
||||||
.expect(200);
|
.expect(200);
|
||||||
expect(response.body.revisions).toHaveLength(1);
|
const noteMetaDtos = response.body as NoteMetadataDto[];
|
||||||
|
expect(noteMetaDtos).toHaveLength(1);
|
||||||
|
expect(noteMetaDtos[0].alias).toEqual(noteName);
|
||||||
|
expect(noteMetaDtos[0].updateUser.userName).toEqual(user.userName);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue