diff --git a/src/notes/notes.service.ts b/src/notes/notes.service.ts index d05a2159b..df264c259 100644 --- a/src/notes/notes.service.ts +++ b/src/notes/notes.service.ts @@ -7,7 +7,11 @@ import { forwardRef, Inject, Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; -import { NotInDBError } from '../errors/errors'; +import { + AlreadyInDBError, + NotInDBError, + PermissionsUpdateInconsistentError, +} from '../errors/errors'; import { ConsoleLoggerService } from '../logger/console-logger.service'; import { Revision } from '../revisions/revision.entity'; import { RevisionsService } from '../revisions/revisions.service'; @@ -25,6 +29,7 @@ import { HistoryEntry } from '../history/history-entry.entity'; import { NoteUserPermission } from '../permissions/note-user-permission.entity'; import { NoteGroupPermission } from '../permissions/note-group-permission.entity'; import { GroupsService } from '../groups/groups.service'; +import { checkArrayForDuplicates } from '../utils/arrayDuplicatCheck'; @Injectable() export class NotesService { @@ -46,25 +51,21 @@ export class NotesService { * @param {User} user - the user who owns the notes * @return {Note[]} arary of notes owned by the user */ - getUserNotes(user: User): Note[] { - this.logger.warn('Using hardcoded data!'); - return [ - { - id: 'foobar-barfoo', - alias: null, - shortid: 'abc', - owner: user, - description: 'Very descriptive text.', - userPermissions: [], - groupPermissions: [], - historyEntries: [], - tags: [], - revisions: Promise.resolve([]), - authorColors: [], - title: 'Title!', - viewcount: 42, - }, - ]; + async getUserNotes(user: User): Promise { + const notes = await this.noteRepository.find({ + where: { owner: user }, + relations: [ + 'owner', + 'userPermissions', + 'groupPermissions', + 'authorColors', + 'tags', + ], + }); + if (notes === undefined) { + return []; + } + return notes; } /** @@ -81,8 +82,8 @@ export class NotesService { owner?: User, ): Promise { const newNote = Note.create(); + //TODO: Calculate patch newNote.revisions = Promise.resolve([ - //TODO: Calculate patch Revision.create(noteContent, noteContent), ]); if (alias) { @@ -122,7 +123,7 @@ export class NotesService { * @return {Revision} the first revision of the note */ async getLatestRevision(note: Note): Promise { - return this.revisionsService.getLatestRevision(note.id); + return await this.revisionsService.getLatestRevision(note.id); } /** @@ -132,7 +133,7 @@ export class NotesService { * @return {Revision} the last revision of the note */ async getFirstRevision(note: Note): Promise { - return this.revisionsService.getFirstRevision(note.id); + return await this.revisionsService.getFirstRevision(note.id); } /** @@ -161,6 +162,7 @@ export class NotesService { 'owner', 'groupPermissions', 'userPermissions', + 'tags', ], }); if (note === undefined) { @@ -215,44 +217,90 @@ export class NotesService { * @param {NotePermissionsUpdateDto} newPermissions - the permissions the not should be set to * @return {Note} the note with the new permissions * @throws {NotInDBError} there is no note with this id or alias - * @throws {PermissionsUpdateInconsistent} the new permissions specify a user or group twice. + * @throws {PermissionsUpdateInconsistentError} the new permissions specify a user or group twice. */ async updateNotePermissions( noteIdOrAlias: string, newPermissions: NotePermissionsUpdateDto, - ): Note { - this.logger.warn('Using hardcoded data!', 'updateNotePermissions'); - return { - id: 'foobar-barfoo', - alias: null, - shortid: 'abc', - owner: { - authTokens: [], - createdAt: new Date(), - displayName: 'hardcoded', - id: '1', - identities: [], - ownedNotes: [], - historyEntries: [], - updatedAt: new Date(), - userName: 'Testy', - groups: [], - }, - description: 'Very descriptive text.', - userPermissions: [], - groupPermissions: [], - historyEntries: [], - tags: [], - revisions: Promise.resolve([]), - authorColors: [], - title: 'Title!', - viewcount: 42, - }; + ): Promise { + const note = await this.getNoteByIdOrAlias(noteIdOrAlias); + + const users = newPermissions.sharedToUsers.map( + (userPermission) => userPermission.username, + ); + + const groups = newPermissions.sharedToGroups.map( + (groupPermission) => groupPermission.groupname, + ); + + if (checkArrayForDuplicates(users) || checkArrayForDuplicates(groups)) { + this.logger.debug( + `The PermissionUpdate requested specifies the same user or group multiple times.`, + 'updateNotePermissions', + ); + throw new PermissionsUpdateInconsistentError( + 'The PermissionUpdate requested specifies the same user or group multiple times.', + ); + } + + note.userPermissions = []; + note.groupPermissions = []; + + // Create new userPermissions + for (const newUserPermission of newPermissions.sharedToUsers) { + const user = await this.usersService.getUserByUsername( + newUserPermission.username, + ); + const createdPermission = NoteUserPermission.create( + user, + newUserPermission.canEdit, + ); + note.userPermissions.push(createdPermission); + } + + // Create groupPermissions + for (const newGroupPermission of newPermissions.sharedToGroups) { + const group = await this.groupsService.getGroupByName( + newGroupPermission.groupname, + ); + const createdPermission = NoteGroupPermission.create( + group, + newGroupPermission.canEdit, + ); + note.groupPermissions.push(createdPermission); + } + + return await this.noteRepository.save(note); } - async getNoteContent(noteIdOrAlias: string): Promise { + /** + * @async + * Get the current content of the note by either their id or alias. + * @param {string} noteIdOrAlias - the notes id or alias + * @return {string} the content of the note + */ + async getNoteContentByIdOrAlias(noteIdOrAlias: string): Promise { const note = await this.getNoteByIdOrAlias(noteIdOrAlias); - return this.getCurrentContent(note); + return this.getNoteContentByNote(note); + } + + /** + * @async + * Calculate the updateUser (for the NoteDto) for a Note. + * @param {Note} note - the note to use + * @return {User} user to be used as updateUser in the NoteDto + */ + async calculateUpdateUser(note: Note): Promise { + const lastRevision = await this.getLatestRevision(note); + if (lastRevision && lastRevision.authorships) { + // Sort the last Revisions Authorships by their updatedAt Date to get the latest one + // the user of that Authorship is the updateUser + return lastRevision.authorships.sort( + (a, b) => b.updatedAt.getTime() - a.updatedAt.getTime(), + )[0].user; + } + // If there are no Authorships, the owner is the updateUser + return note.owner; } /** @@ -278,7 +326,7 @@ export class NotesService { canEdit: noteUserPermission.canEdit, })), sharedToGroups: note.groupPermissions.map((noteGroupPermission) => ({ - group: noteGroupPermission.group, + group: this.groupsService.toGroupDto(noteGroupPermission.group), canEdit: noteGroupPermission.canEdit, })), }; @@ -301,18 +349,13 @@ export class NotesService { editedBy: note.authorColors.map( (authorColor) => authorColor.user.userName, ), - // TODO: Extract into method permissions: await this.toNotePermissionsDto(note), tags: this.toTagList(note), updateTime: (await this.getLatestRevision(note)).createdAt, - // TODO: Get actual updateUser - updateUser: { - displayName: 'Hardcoded User', - userName: 'hardcoded', - email: 'foo@example.com', - photo: '', - }, - viewCount: 42, + updateUser: this.usersService.toUserDto( + await this.calculateUpdateUser(note), + ), + viewCount: note.viewcount, }; } @@ -324,7 +367,7 @@ export class NotesService { */ async toNoteDto(note: Note): Promise { return { - content: await this.getCurrentContent(note), + content: await this.getNoteContentByNote(note), metadata: await this.toNoteMetadataDto(note), editedByAtPosition: [], }; diff --git a/src/utils/arrayDuplicatCheck.ts b/src/utils/arrayDuplicatCheck.ts new file mode 100644 index 000000000..6a9880c30 --- /dev/null +++ b/src/utils/arrayDuplicatCheck.ts @@ -0,0 +1,9 @@ +/* + * SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export function checkArrayForDuplicates(array: Array): boolean { + return new Set(array).size !== array.length; +}