feat: add realtime announcements for permission changes and note deletion

Co-authored-by: Erik Michelson <github@erik.michelson.eu>
Signed-off-by: Erik Michelson <github@erik.michelson.eu>
Signed-off-by: Philip Molares <philip.molares@udo.edu>
This commit is contained in:
Philip Molares 2022-09-04 23:38:26 +02:00
parent c363d0834e
commit 331747f61b
6 changed files with 56 additions and 3 deletions

View file

@ -260,6 +260,10 @@ export class NotesService {
* @throws {NotInDBError} there is no note with this id or alias * @throws {NotInDBError} there is no note with this id or alias
*/ */
async deleteNote(note: Note): Promise<Note> { async deleteNote(note: Note): Promise<Note> {
const realtimeNote = this.realtimeNoteStore.find(note.id);
if (realtimeNote) {
realtimeNote.announceNoteDeletion();
}
return await this.noteRepository.remove(note); return await this.noteRepository.remove(note);
} }

View file

@ -3,12 +3,13 @@
* *
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */
import { Module } from '@nestjs/common'; import { forwardRef, Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm'; import { TypeOrmModule } from '@nestjs/typeorm';
import { GroupsModule } from '../groups/groups.module'; import { GroupsModule } from '../groups/groups.module';
import { LoggerModule } from '../logger/logger.module'; import { LoggerModule } from '../logger/logger.module';
import { Note } from '../notes/note.entity'; import { Note } from '../notes/note.entity';
import { RealtimeNoteModule } from '../realtime/realtime-note/realtime-note.module';
import { UsersModule } from '../users/users.module'; import { UsersModule } from '../users/users.module';
import { PermissionsService } from './permissions.service'; import { PermissionsService } from './permissions.service';
@ -18,6 +19,7 @@ import { PermissionsService } from './permissions.service';
UsersModule, UsersModule,
GroupsModule, GroupsModule,
LoggerModule, LoggerModule,
forwardRef(() => RealtimeNoteModule),
], ],
exports: [PermissionsService], exports: [PermissionsService],
providers: [PermissionsService], providers: [PermissionsService],

View file

@ -34,6 +34,7 @@ import {
import { Note } from '../notes/note.entity'; import { Note } from '../notes/note.entity';
import { NotesModule } from '../notes/notes.module'; import { NotesModule } from '../notes/notes.module';
import { Tag } from '../notes/tag.entity'; import { Tag } from '../notes/tag.entity';
import { RealtimeNoteModule } from '../realtime/realtime-note/realtime-note.module';
import { Edit } from '../revisions/edit.entity'; import { Edit } from '../revisions/edit.entity';
import { Revision } from '../revisions/revision.entity'; import { Revision } from '../revisions/revision.entity';
import { Session } from '../users/session.entity'; import { Session } from '../users/session.entity';
@ -109,6 +110,7 @@ describe('PermissionsService', () => {
], ],
}), }),
GroupsModule, GroupsModule,
RealtimeNoteModule,
], ],
}) })
.overrideProvider(getRepositoryToken(User)) .overrideProvider(getRepositoryToken(User))

View file

@ -19,6 +19,8 @@ import { SpecialGroup } from '../groups/groups.special';
import { ConsoleLoggerService } from '../logger/console-logger.service'; import { ConsoleLoggerService } from '../logger/console-logger.service';
import { NotePermissionsUpdateDto } from '../notes/note-permissions.dto'; import { NotePermissionsUpdateDto } from '../notes/note-permissions.dto';
import { Note } from '../notes/note.entity'; 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 { User } from '../users/user.entity';
import { UsersService } from '../users/users.service'; import { UsersService } from '../users/users.service';
import { checkArrayForDuplicates } from '../utils/arrayDuplicatCheck'; import { checkArrayForDuplicates } from '../utils/arrayDuplicatCheck';
@ -34,6 +36,8 @@ export class PermissionsService {
private readonly logger: ConsoleLoggerService, private readonly logger: ConsoleLoggerService,
@Inject(noteConfiguration.KEY) @Inject(noteConfiguration.KEY)
private noteConfig: NoteConfig, private noteConfig: NoteConfig,
private realtimeNoteService: RealtimeNoteService,
private realtimeNoteStore: RealtimeNoteStore,
) {} ) {}
/** /**
@ -150,6 +154,13 @@ export class PermissionsService {
return false; return false;
} }
private notifyOthers(noteId: Note['id']): void {
const realtimeNote = this.realtimeNoteStore.find(noteId);
if (realtimeNote) {
realtimeNote.announcePermissionChange();
}
}
/** /**
* @async * @async
* Update a notes permissions. * Update a notes permissions.
@ -211,7 +222,7 @@ export class PermissionsService {
createdPermission.note = Promise.resolve(note); createdPermission.note = Promise.resolve(note);
(await note.groupPermissions).push(createdPermission); (await note.groupPermissions).push(createdPermission);
} }
this.notifyOthers(note.id);
return await this.noteRepository.save(note); return await this.noteRepository.save(note);
} }
@ -245,6 +256,7 @@ export class PermissionsService {
); );
(await note.userPermissions).push(noteUserPermission); (await note.userPermissions).push(noteUserPermission);
} }
this.notifyOthers(note.id);
return await this.noteRepository.save(note); return await this.noteRepository.save(note);
} }
@ -264,6 +276,7 @@ export class PermissionsService {
} }
} }
note.userPermissions = Promise.resolve(newPermissions); note.userPermissions = Promise.resolve(newPermissions);
this.notifyOthers(note.id);
return await this.noteRepository.save(note); return await this.noteRepository.save(note);
} }
@ -305,6 +318,7 @@ export class PermissionsService {
); );
(await note.groupPermissions).push(noteGroupPermission); (await note.groupPermissions).push(noteGroupPermission);
} }
this.notifyOthers(note.id);
return await this.noteRepository.save(note); return await this.noteRepository.save(note);
} }
@ -327,6 +341,7 @@ export class PermissionsService {
} }
} }
note.groupPermissions = Promise.resolve(newPermissions); note.groupPermissions = Promise.resolve(newPermissions);
this.notifyOthers(note.id);
return await this.noteRepository.save(note); return await this.noteRepository.save(note);
} }
@ -339,6 +354,7 @@ export class PermissionsService {
*/ */
async changeOwner(note: Note, owner: User): Promise<Note> { async changeOwner(note: Note, owner: User): Promise<Note> {
note.owner = Promise.resolve(owner); note.owner = Promise.resolve(owner);
this.notifyOthers(note.id);
return await this.noteRepository.save(note); return await this.noteRepository.save(note);
} }
} }

View file

@ -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. * 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. * @throws NotInDBError if note doesn't exist or has no revisions.
* @return A {@link RealtimeNote} that is linked to the given note. * @return A {@link RealtimeNote} that is linked to the given note.
*/ */

View file

@ -3,6 +3,10 @@
* *
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */
import {
encodeDocumentDeletedMessage,
encodeMetadataUpdatedMessage,
} from '@hedgedoc/realtime';
import { Logger } from '@nestjs/common'; import { Logger } from '@nestjs/common';
import { EventEmitter } from 'events'; import { EventEmitter } from 'events';
import TypedEventEmitter, { EventMap } from 'typed-emitter'; import TypedEventEmitter, { EventMap } from 'typed-emitter';
@ -130,4 +134,29 @@ export class RealtimeNote extends (EventEmitter as TypedEventEmitterConstructor<
public getNote(): Note { public getNote(): Note {
return this.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);
});
}
} }