diff --git a/src/notes/notes.service.ts b/src/notes/notes.service.ts index 8fa2646d8..257f73dbd 100644 --- a/src/notes/notes.service.ts +++ b/src/notes/notes.service.ts @@ -260,6 +260,10 @@ export class NotesService { * @throws {NotInDBError} there is no note with this id or alias */ async deleteNote(note: Note): Promise<Note> { + const realtimeNote = this.realtimeNoteStore.find(note.id); + if (realtimeNote) { + realtimeNote.announceNoteDeletion(); + } return await this.noteRepository.remove(note); } diff --git a/src/permissions/permissions.module.ts b/src/permissions/permissions.module.ts index 8192ffe23..3eed6cbfc 100644 --- a/src/permissions/permissions.module.ts +++ b/src/permissions/permissions.module.ts @@ -3,12 +3,13 @@ * * SPDX-License-Identifier: AGPL-3.0-only */ -import { Module } from '@nestjs/common'; +import { forwardRef, Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { GroupsModule } from '../groups/groups.module'; import { LoggerModule } from '../logger/logger.module'; import { Note } from '../notes/note.entity'; +import { RealtimeNoteModule } from '../realtime/realtime-note/realtime-note.module'; import { UsersModule } from '../users/users.module'; import { PermissionsService } from './permissions.service'; @@ -18,6 +19,7 @@ import { PermissionsService } from './permissions.service'; UsersModule, GroupsModule, LoggerModule, + forwardRef(() => RealtimeNoteModule), ], exports: [PermissionsService], providers: [PermissionsService], diff --git a/src/permissions/permissions.service.spec.ts b/src/permissions/permissions.service.spec.ts index caabf2fca..a88438348 100644 --- a/src/permissions/permissions.service.spec.ts +++ b/src/permissions/permissions.service.spec.ts @@ -34,6 +34,7 @@ import { import { Note } from '../notes/note.entity'; import { NotesModule } from '../notes/notes.module'; import { Tag } from '../notes/tag.entity'; +import { RealtimeNoteModule } from '../realtime/realtime-note/realtime-note.module'; import { Edit } from '../revisions/edit.entity'; import { Revision } from '../revisions/revision.entity'; import { Session } from '../users/session.entity'; @@ -109,6 +110,7 @@ describe('PermissionsService', () => { ], }), GroupsModule, + RealtimeNoteModule, ], }) .overrideProvider(getRepositoryToken(User)) diff --git a/src/permissions/permissions.service.ts b/src/permissions/permissions.service.ts index 6518f767f..095fc8b22 100644 --- a/src/permissions/permissions.service.ts +++ b/src/permissions/permissions.service.ts @@ -19,6 +19,8 @@ import { SpecialGroup } from '../groups/groups.special'; import { ConsoleLoggerService } from '../logger/console-logger.service'; import { NotePermissionsUpdateDto } from '../notes/note-permissions.dto'; import { Note } from '../notes/note.entity'; +import { RealtimeNoteStore } from '../realtime/realtime-note/realtime-note-store'; +import { RealtimeNoteService } from '../realtime/realtime-note/realtime-note.service'; import { User } from '../users/user.entity'; import { UsersService } from '../users/users.service'; import { checkArrayForDuplicates } from '../utils/arrayDuplicatCheck'; @@ -34,6 +36,8 @@ export class PermissionsService { private readonly logger: ConsoleLoggerService, @Inject(noteConfiguration.KEY) private noteConfig: NoteConfig, + private realtimeNoteService: RealtimeNoteService, + private realtimeNoteStore: RealtimeNoteStore, ) {} /** @@ -150,6 +154,13 @@ export class PermissionsService { return false; } + private notifyOthers(noteId: Note['id']): void { + const realtimeNote = this.realtimeNoteStore.find(noteId); + if (realtimeNote) { + realtimeNote.announcePermissionChange(); + } + } + /** * @async * Update a notes permissions. @@ -211,7 +222,7 @@ export class PermissionsService { createdPermission.note = Promise.resolve(note); (await note.groupPermissions).push(createdPermission); } - + this.notifyOthers(note.id); return await this.noteRepository.save(note); } @@ -245,6 +256,7 @@ export class PermissionsService { ); (await note.userPermissions).push(noteUserPermission); } + this.notifyOthers(note.id); return await this.noteRepository.save(note); } @@ -264,6 +276,7 @@ export class PermissionsService { } } note.userPermissions = Promise.resolve(newPermissions); + this.notifyOthers(note.id); return await this.noteRepository.save(note); } @@ -305,6 +318,7 @@ export class PermissionsService { ); (await note.groupPermissions).push(noteGroupPermission); } + this.notifyOthers(note.id); return await this.noteRepository.save(note); } @@ -327,6 +341,7 @@ export class PermissionsService { } } note.groupPermissions = Promise.resolve(newPermissions); + this.notifyOthers(note.id); return await this.noteRepository.save(note); } @@ -339,6 +354,7 @@ export class PermissionsService { */ async changeOwner(note: Note, owner: User): Promise<Note> { note.owner = Promise.resolve(owner); + this.notifyOthers(note.id); return await this.noteRepository.save(note); } } diff --git a/src/realtime/realtime-note/realtime-note.service.ts b/src/realtime/realtime-note/realtime-note.service.ts index 89e13b73a..bef57e6f8 100644 --- a/src/realtime/realtime-note/realtime-note.service.ts +++ b/src/realtime/realtime-note/realtime-note.service.ts @@ -47,7 +47,7 @@ export class RealtimeNoteService implements BeforeApplicationShutdown { /** * Creates or reuses a {@link RealtimeNote} that is handling the real time editing of the {@link Note} which is identified by the given note id. - * @param note The for which a {@link RealtimeNote realtime note} should be retrieved. + * @param note The {@link Note} for which a {@link RealtimeNote realtime note} should be retrieved. * @throws NotInDBError if note doesn't exist or has no revisions. * @return A {@link RealtimeNote} that is linked to the given note. */ diff --git a/src/realtime/realtime-note/realtime-note.ts b/src/realtime/realtime-note/realtime-note.ts index 9820760ce..da1895301 100644 --- a/src/realtime/realtime-note/realtime-note.ts +++ b/src/realtime/realtime-note/realtime-note.ts @@ -3,6 +3,10 @@ * * SPDX-License-Identifier: AGPL-3.0-only */ +import { + encodeDocumentDeletedMessage, + encodeMetadataUpdatedMessage, +} from '@hedgedoc/realtime'; import { Logger } from '@nestjs/common'; import { EventEmitter } from 'events'; import TypedEventEmitter, { EventMap } from 'typed-emitter'; @@ -130,4 +134,29 @@ export class RealtimeNote extends (EventEmitter as TypedEventEmitterConstructor< public getNote(): Note { return this.note; } + + /** + * Announce to all clients that the permissions of the note have been changed. + */ + public announcePermissionChange(): void { + this.sendToAllClients(encodeMetadataUpdatedMessage()); + } + + /** + * Announce to all clients that the note has been deleted. + */ + public announceNoteDeletion(): void { + this.sendToAllClients(encodeDocumentDeletedMessage()); + } + + /** + * Broadcasts the given content to all connected clients. + * + * @param {Uint8Array} content The binary message to broadcast + */ + private sendToAllClients(content: Uint8Array): void { + this.getConnections().forEach((connection) => { + connection.send(content); + }); + } }