mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2025-05-19 01:35:18 -04:00
Merge pull request #1127 from hedgedoc/history/entryRename
This commit is contained in:
commit
0e5ce048a3
6 changed files with 179 additions and 28 deletions
|
@ -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) {
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
27
src/history/history-entry-import.dto.ts
Normal file
27
src/history/history-entry-import.dto.ts
Normal 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;
|
||||||
|
}
|
|
@ -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(
|
||||||
|
async (entry: HistoryEntry): Promise<HistoryEntry> => entry,
|
||||||
|
);
|
||||||
|
const createHistoryEntry = await service.createOrUpdateHistoryEntry(
|
||||||
Note.create(user, alias),
|
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);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 () => {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue