Merge pull request #1127 from hedgedoc/history/entryRename

This commit is contained in:
David Mehren 2021-04-17 19:23:19 +02:00 committed by GitHub
commit 0e5ce048a3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 179 additions and 28 deletions

View file

@ -19,7 +19,7 @@ import { UsersService } from '../../../../users/users.service';
import { NotesService } from '../../../../notes/notes.service'; import { NotesService } from '../../../../notes/notes.service';
import { HistoryEntryDto } from '../../../../history/history-entry.dto'; import { HistoryEntryDto } from '../../../../history/history-entry.dto';
import { NotInDBError } from '../../../../errors/errors'; import { NotInDBError } from '../../../../errors/errors';
import { HistoryEntryCreationDto } from '../../../../history/history-entry-creation.dto'; import { HistoryEntryImportDto } from '../../../../history/history-entry-import.dto';
import { HistoryEntryUpdateDto } from '../../../../history/history-entry-update.dto'; import { HistoryEntryUpdateDto } from '../../../../history/history-entry-update.dto';
import { ConsoleLoggerService } from '../../../../logger/console-logger.service'; import { ConsoleLoggerService } from '../../../../logger/console-logger.service';
import { HistoryService } from '../../../../history/history.service'; import { HistoryService } from '../../../../history/history.service';
@ -55,7 +55,7 @@ export class HistoryController {
@Post() @Post()
async setHistory( async setHistory(
@Body('history') history: HistoryEntryCreationDto[], @Body('history') history: HistoryEntryImportDto[],
): Promise<void> { ): Promise<void> {
try { try {
// ToDo: use actual user here // ToDo: use actual user here
@ -65,7 +65,12 @@ export class HistoryController {
const note = await this.noteService.getNoteByIdOrAlias( const note = await this.noteService.getNoteByIdOrAlias(
historyEntry.note, historyEntry.note,
); );
await this.historyService.createOrUpdateHistoryEntry(note, user); await this.historyService.createOrUpdateHistoryEntry(
note,
user,
historyEntry.pinStatus,
historyEntry.lastVisited,
);
} }
} catch (e) { } catch (e) {
if (e instanceof NotInDBError) { if (e instanceof NotInDBError) {

View file

@ -1,15 +0,0 @@
/*
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { IsString } from 'class-validator';
export class HistoryEntryCreationDto {
/**
* ID or Alias of the note
*/
@IsString()
note: string;
}

View file

@ -0,0 +1,27 @@
/*
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { IsBoolean, IsDate, IsString } from 'class-validator';
export class HistoryEntryImportDto {
/**
* ID or Alias of the note
*/
@IsString()
note: string;
/**
* True if the note should be pinned
* @example true
*/
@IsBoolean()
pinStatus: boolean;
/**
* Datestring of the last time this note was updated
* @example "2020-12-01 12:23:34"
*/
@IsDate()
lastVisited: Date;
}

View file

@ -141,9 +141,12 @@ describe('HistoryService', () => {
describe('createOrUpdateHistoryEntry', () => { describe('createOrUpdateHistoryEntry', () => {
describe('works', () => { describe('works', () => {
it('without an preexisting entry', async () => {
const user = {} as User; const user = {} as User;
const alias = 'alias'; const alias = 'alias';
const pinStatus = true;
const lastVisited = new Date('2020-12-01 12:23:34');
const historyEntry = HistoryEntry.create(user, Note.create(user, alias));
it('without an preexisting entry, without pinStatus and without lastVisited', async () => {
jest.spyOn(historyRepo, 'findOne').mockResolvedValueOnce(undefined); jest.spyOn(historyRepo, 'findOne').mockResolvedValueOnce(undefined);
jest jest
.spyOn(historyRepo, 'save') .spyOn(historyRepo, 'save')
@ -160,13 +163,65 @@ describe('HistoryService', () => {
expect(createHistoryEntry.pinStatus).toEqual(false); expect(createHistoryEntry.pinStatus).toEqual(false);
}); });
it('with an preexisting entry', async () => { it('without an preexisting entry, with pinStatus and without lastVisited', async () => {
const user = {} as User; jest.spyOn(historyRepo, 'findOne').mockResolvedValueOnce(undefined);
const alias = 'alias'; jest
const historyEntry = HistoryEntry.create( .spyOn(historyRepo, 'save')
user, .mockImplementation(
Note.create(user, alias), async (entry: HistoryEntry): Promise<HistoryEntry> => entry,
); );
const createHistoryEntry = await service.createOrUpdateHistoryEntry(
Note.create(user, alias),
user,
pinStatus,
);
expect(createHistoryEntry.note.alias).toEqual(alias);
expect(createHistoryEntry.note.owner).toEqual(user);
expect(createHistoryEntry.user).toEqual(user);
expect(createHistoryEntry.pinStatus).toEqual(pinStatus);
});
it('without an preexisting entry, without pinStatus and with lastVisited', async () => {
jest.spyOn(historyRepo, 'findOne').mockResolvedValueOnce(undefined);
jest
.spyOn(historyRepo, 'save')
.mockImplementation(
async (entry: HistoryEntry): Promise<HistoryEntry> => entry,
);
const createHistoryEntry = await service.createOrUpdateHistoryEntry(
Note.create(user, alias),
user,
undefined,
lastVisited,
);
expect(createHistoryEntry.note.alias).toEqual(alias);
expect(createHistoryEntry.note.owner).toEqual(user);
expect(createHistoryEntry.user).toEqual(user);
expect(createHistoryEntry.pinStatus).toEqual(false);
expect(createHistoryEntry.updatedAt).toEqual(lastVisited);
});
it('without an preexisting entry, with pinStatus and with lastVisited', async () => {
jest.spyOn(historyRepo, 'findOne').mockResolvedValueOnce(undefined);
jest
.spyOn(historyRepo, 'save')
.mockImplementation(
async (entry: HistoryEntry): Promise<HistoryEntry> => entry,
);
const createHistoryEntry = await service.createOrUpdateHistoryEntry(
Note.create(user, alias),
user,
pinStatus,
lastVisited,
);
expect(createHistoryEntry.note.alias).toEqual(alias);
expect(createHistoryEntry.note.owner).toEqual(user);
expect(createHistoryEntry.user).toEqual(user);
expect(createHistoryEntry.pinStatus).toEqual(pinStatus);
expect(createHistoryEntry.updatedAt).toEqual(lastVisited);
});
it('with an preexisting entry, without pinStatus and without lastVisited', async () => {
jest.spyOn(historyRepo, 'findOne').mockResolvedValueOnce(historyEntry); jest.spyOn(historyRepo, 'findOne').mockResolvedValueOnce(historyEntry);
jest jest
.spyOn(historyRepo, 'save') .spyOn(historyRepo, 'save')
@ -185,6 +240,67 @@ describe('HistoryService', () => {
historyEntry.updatedAt.getTime(), historyEntry.updatedAt.getTime(),
); );
}); });
it('with an preexisting entry, with pinStatus and without lastVisited', async () => {
jest.spyOn(historyRepo, 'findOne').mockResolvedValueOnce(historyEntry);
jest
.spyOn(historyRepo, 'save')
.mockImplementation(
async (entry: HistoryEntry): Promise<HistoryEntry> => entry,
);
const createHistoryEntry = await service.createOrUpdateHistoryEntry(
Note.create(user, alias),
user,
pinStatus,
);
expect(createHistoryEntry.note.alias).toEqual(alias);
expect(createHistoryEntry.note.owner).toEqual(user);
expect(createHistoryEntry.user).toEqual(user);
expect(createHistoryEntry.pinStatus).not.toEqual(pinStatus);
expect(createHistoryEntry.updatedAt.getTime()).toBeGreaterThanOrEqual(
historyEntry.updatedAt.getTime(),
);
});
it('with an preexisting entry, without pinStatus and with lastVisited', async () => {
jest.spyOn(historyRepo, 'findOne').mockResolvedValueOnce(historyEntry);
jest
.spyOn(historyRepo, 'save')
.mockImplementation(
async (entry: HistoryEntry): Promise<HistoryEntry> => entry,
);
const createHistoryEntry = await service.createOrUpdateHistoryEntry(
Note.create(user, alias),
user,
undefined,
lastVisited,
);
expect(createHistoryEntry.note.alias).toEqual(alias);
expect(createHistoryEntry.note.owner).toEqual(user);
expect(createHistoryEntry.user).toEqual(user);
expect(createHistoryEntry.pinStatus).toEqual(false);
expect(createHistoryEntry.updatedAt).not.toEqual(lastVisited);
});
it('with an preexisting entry, with pinStatus and with lastVisited', async () => {
jest.spyOn(historyRepo, 'findOne').mockResolvedValueOnce(historyEntry);
jest
.spyOn(historyRepo, 'save')
.mockImplementation(
async (entry: HistoryEntry): Promise<HistoryEntry> => entry,
);
const createHistoryEntry = await service.createOrUpdateHistoryEntry(
Note.create(user, alias),
user,
pinStatus,
lastVisited,
);
expect(createHistoryEntry.note.alias).toEqual(alias);
expect(createHistoryEntry.note.owner).toEqual(user);
expect(createHistoryEntry.user).toEqual(user);
expect(createHistoryEntry.pinStatus).not.toEqual(pinStatus);
expect(createHistoryEntry.updatedAt).not.toEqual(lastVisited);
});
}); });
}); });

View file

@ -80,15 +80,25 @@ export class HistoryService {
* 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. * 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 {Note} note - the note that the history entry belongs to
* @param {User} user - the user that the history entry belongs to * @param {User} user - the user that the history entry belongs to
* @param {boolean} pinStatus - if the pinStatus of the history entry should be set
* @param {Date} lastVisited - the last time the associated note was accessed
* @return {HistoryEntry} the requested history entry * @return {HistoryEntry} the requested history entry
*/ */
async createOrUpdateHistoryEntry( async createOrUpdateHistoryEntry(
note: Note, note: Note,
user: User, user: User,
pinStatus?: boolean,
lastVisited?: Date,
): Promise<HistoryEntry> { ): Promise<HistoryEntry> {
let entry = await this.getEntryByNote(note, user); let entry = await this.getEntryByNote(note, user);
if (!entry) { if (!entry) {
entry = HistoryEntry.create(user, note); entry = HistoryEntry.create(user, note);
if (pinStatus !== undefined) {
entry.pinStatus = pinStatus;
}
if (lastVisited !== undefined) {
entry.updatedAt = lastVisited;
}
} else { } else {
entry.updatedAt = new Date(); entry.updatedAt = new Date();
} }

View file

@ -26,7 +26,7 @@ import { UsersModule } from '../../src/users/users.module';
import { PrivateApiModule } from '../../src/api/private/private-api.module'; import { PrivateApiModule } from '../../src/api/private/private-api.module';
import { HistoryService } from '../../src/history/history.service'; import { HistoryService } from '../../src/history/history.service';
import { Note } from '../../src/notes/note.entity'; import { Note } from '../../src/notes/note.entity';
import { HistoryEntryCreationDto } from '../../src/history/history-entry-creation.dto'; import { HistoryEntryImportDto } from '../../src/history/history-entry-import.dto';
describe('History', () => { describe('History', () => {
let app: INestApplication; let app: INestApplication;
@ -100,8 +100,13 @@ describe('History', () => {
}); });
it('POST /me/history', async () => { it('POST /me/history', async () => {
const postEntryDto = new HistoryEntryCreationDto(); expect((await historyService.getEntriesByUser(user)).length).toEqual(1);
const pinStatus = true;
const lastVisited = new Date('2020-12-01 12:23:34');
const postEntryDto = new HistoryEntryImportDto();
postEntryDto.note = note2.alias; postEntryDto.note = note2.alias;
postEntryDto.pinStatus = pinStatus;
postEntryDto.lastVisited = lastVisited;
await request(app.getHttpServer()) await request(app.getHttpServer())
.post('/me/history') .post('/me/history')
.set('Content-Type', 'application/json') .set('Content-Type', 'application/json')
@ -110,6 +115,9 @@ describe('History', () => {
const userEntries = await historyService.getEntriesByUser(user); const userEntries = await historyService.getEntriesByUser(user);
expect(userEntries.length).toEqual(1); expect(userEntries.length).toEqual(1);
expect(userEntries[0].note.alias).toEqual(note2.alias); expect(userEntries[0].note.alias).toEqual(note2.alias);
expect(userEntries[0].user.userName).toEqual(user.userName);
expect(userEntries[0].pinStatus).toEqual(pinStatus);
expect(userEntries[0].updatedAt).toEqual(lastVisited);
}); });
it('DELETE /me/history', async () => { it('DELETE /me/history', async () => {