diff --git a/src/api/private/me/history/history.controller.ts b/src/api/private/me/history/history.controller.ts index d74795847..da00d7223 100644 --- a/src/api/private/me/history/history.controller.ts +++ b/src/api/private/me/history/history.controller.ts @@ -69,6 +69,7 @@ export class HistoryController { note, user, historyEntry.pinStatus, + historyEntry.lastVisited, ); } } catch (e) { diff --git a/src/history/history-entry-import.dto.ts b/src/history/history-entry-import.dto.ts index edc8b7c1e..7e258c7f6 100644 --- a/src/history/history-entry-import.dto.ts +++ b/src/history/history-entry-import.dto.ts @@ -4,7 +4,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { IsBoolean, IsString } from 'class-validator'; +import { IsBoolean, IsDate, IsString } from 'class-validator'; export class HistoryEntryImportDto { /** @@ -18,4 +18,10 @@ export class HistoryEntryImportDto { */ @IsBoolean() pinStatus: boolean; + /** + * Datestring of the last time this note was updated + * @example "2020-12-01 12:23:34" + */ + @IsDate() + lastVisited: Date; } diff --git a/src/history/history.service.spec.ts b/src/history/history.service.spec.ts index f823b8400..206b40d14 100644 --- a/src/history/history.service.spec.ts +++ b/src/history/history.service.spec.ts @@ -152,7 +152,9 @@ describe('HistoryService', () => { const user = {} as User; const alias = 'alias'; const pinStatus = true; - it('without an preexisting entry and without pinStatus', async () => { + 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, 'save') @@ -169,7 +171,7 @@ describe('HistoryService', () => { expect(createHistoryEntry.pinStatus).toEqual(false); }); - it('without an preexisting entry and with pinStatus', async () => { + it('without an preexisting entry, with pinStatus and without lastVisited', async () => { jest.spyOn(historyRepo, 'findOne').mockResolvedValueOnce(undefined); jest .spyOn(historyRepo, 'save') @@ -187,11 +189,47 @@ describe('HistoryService', () => { expect(createHistoryEntry.pinStatus).toEqual(pinStatus); }); - it('with an preexisting entry and without pinStatus', async () => { - const historyEntry = HistoryEntry.create( - user, + 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 => 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 => 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, 'save') @@ -211,12 +249,7 @@ describe('HistoryService', () => { ); }); - it('with an preexisting entry and with pinStatus', async () => { - const historyEntry = HistoryEntry.create( - user, - Note.create(user, alias), - pinStatus, - ); + it('with an preexisting entry, with pinStatus and without lastVisited', async () => { jest.spyOn(historyRepo, 'findOne').mockResolvedValueOnce(historyEntry); jest .spyOn(historyRepo, 'save') @@ -231,11 +264,51 @@ describe('HistoryService', () => { expect(createHistoryEntry.note.alias).toEqual(alias); expect(createHistoryEntry.note.owner).toEqual(user); expect(createHistoryEntry.user).toEqual(user); - expect(createHistoryEntry.pinStatus).toEqual(pinStatus); + 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 => 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 => 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); + }); }); }); diff --git a/src/history/history.service.ts b/src/history/history.service.ts index de4ad0264..276af8190 100644 --- a/src/history/history.service.ts +++ b/src/history/history.service.ts @@ -81,12 +81,14 @@ export class HistoryService { * @param {Note} note - the note 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 */ async createOrUpdateHistoryEntry( note: Note, user: User, pinStatus?: boolean, + lastVisited?: Date, ): Promise { let entry = await this.getEntryByNote(note, user); if (!entry) { @@ -94,6 +96,9 @@ export class HistoryService { if (pinStatus !== undefined) { entry.pinStatus = pinStatus; } + if (lastVisited !== undefined) { + entry.updatedAt = lastVisited; + } } else { entry.updatedAt = new Date(); } diff --git a/test/private-api/history.e2e-spec.ts b/test/private-api/history.e2e-spec.ts index 3dbf1db90..8f9262101 100644 --- a/test/private-api/history.e2e-spec.ts +++ b/test/private-api/history.e2e-spec.ts @@ -105,10 +105,13 @@ describe('History', () => { }); it('POST /me/history', async () => { + 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.pinStatus = pinStatus; + postEntryDto.lastVisited = lastVisited; await request(app.getHttpServer()) .post('/me/history') .set('Content-Type', 'application/json') @@ -119,6 +122,7 @@ describe('History', () => { 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 () => {