mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2025-06-05 17:14:40 -04:00
chore(esdoc): update and unify ESDoc and parameter names (2)
Signed-off-by: Erik Michelson <github@erik.michelson.eu>
This commit is contained in:
parent
f47915dbb3
commit
3278e25dd1
22 changed files with 273 additions and 129 deletions
|
@ -10,7 +10,8 @@ export const PERMISSION_METADATA_KEY = 'requiredPermission';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This decorator gathers the {@link PermissionLevel} a user must hold for the {@link PermissionsGuard}
|
* This decorator gathers the {@link PermissionLevel} a user must hold for the {@link PermissionsGuard}
|
||||||
* @param permissionLevel the required permission for the decorated action.
|
* @param permissionLevel the required permission for the decorated action
|
||||||
|
* @returns The custom decorator action
|
||||||
*/
|
*/
|
||||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
export function RequirePermission(
|
export function RequirePermission(
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
SPDX-FileCopyrightText: The author of https://www.randomlists.com/
|
|
||||||
|
|
||||||
SPDX-License-Identifier: CC0-1.0
|
|
|
@ -12,7 +12,7 @@ import * as HedgeDocCommonsModule from '@hedgedoc/commons';
|
||||||
import { FieldNameUser, User } from '@hedgedoc/database';
|
import { FieldNameUser, User } from '@hedgedoc/database';
|
||||||
import { Mock } from 'ts-mockery';
|
import { Mock } from 'ts-mockery';
|
||||||
|
|
||||||
import * as NameRandomizerModule from './random-word-lists/name-randomizer';
|
import * as NameRandomizerModule from '../../users/random-word-lists/name-randomizer';
|
||||||
import { RealtimeConnection } from './realtime-connection';
|
import { RealtimeConnection } from './realtime-connection';
|
||||||
import { RealtimeNote } from './realtime-note';
|
import { RealtimeNote } from './realtime-note';
|
||||||
import {
|
import {
|
||||||
|
@ -21,7 +21,7 @@ import {
|
||||||
} from './realtime-user-status-adapter';
|
} from './realtime-user-status-adapter';
|
||||||
import * as RealtimeUserStatusModule from './realtime-user-status-adapter';
|
import * as RealtimeUserStatusModule from './realtime-user-status-adapter';
|
||||||
|
|
||||||
jest.mock('./random-word-lists/name-randomizer');
|
jest.mock('../../users/random-word-lists/name-randomizer');
|
||||||
jest.mock('./realtime-user-status-adapter');
|
jest.mock('./realtime-user-status-adapter');
|
||||||
jest.mock(
|
jest.mock(
|
||||||
'@hedgedoc/commons',
|
'@hedgedoc/commons',
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
@ -26,8 +26,8 @@ export class RealtimeConnection {
|
||||||
* @param username The username of the user of the client
|
* @param username The username of the user of the client
|
||||||
* @param displayName The displayName of the user of the client
|
* @param displayName The displayName of the user of the client
|
||||||
* @param authorStyle The authorStyle of the user of the client
|
* @param authorStyle The authorStyle of the user of the client
|
||||||
* @param realtimeNote The {@link RealtimeNote} that the client connected to.
|
* @param realtimeNote The {@link RealtimeNote} that the client connected to
|
||||||
* @param acceptEdits If edits by this connection should be accepted.
|
* @param acceptEdits If edits by this connection should be accepted
|
||||||
* @throws Error if the socket is not open
|
* @throws Error if the socket is not open
|
||||||
*/
|
*/
|
||||||
constructor(
|
constructor(
|
||||||
|
@ -62,34 +62,74 @@ export class RealtimeConnection {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the realtime user state adapter of this connection.
|
||||||
|
*
|
||||||
|
* @returns the realtime user state adapter
|
||||||
|
*/
|
||||||
public getRealtimeUserStateAdapter(): RealtimeUserStatusAdapter {
|
public getRealtimeUserStateAdapter(): RealtimeUserStatusAdapter {
|
||||||
return this.realtimeUserStateAdapter;
|
return this.realtimeUserStateAdapter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the message transporter of this connection.
|
||||||
|
*
|
||||||
|
* @returns the message transporter
|
||||||
|
*/
|
||||||
public getTransporter(): MessageTransporter {
|
public getTransporter(): MessageTransporter {
|
||||||
return this.transporter;
|
return this.transporter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the YDoc sync adapter of this connection.
|
||||||
|
*
|
||||||
|
* @returns the YDoc sync adapter
|
||||||
|
*/
|
||||||
public getSyncAdapter(): YDocSyncServerAdapter {
|
public getSyncAdapter(): YDocSyncServerAdapter {
|
||||||
return this.yDocSyncAdapter;
|
return this.yDocSyncAdapter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the user id of the user of this connection.
|
||||||
|
*
|
||||||
|
* @returns the user id
|
||||||
|
*/
|
||||||
public getUserId(): number {
|
public getUserId(): number {
|
||||||
return this.userId;
|
return this.userId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the display name of the user of this connection.
|
||||||
|
*
|
||||||
|
* @returns the display name
|
||||||
|
*/
|
||||||
public getDisplayName(): string {
|
public getDisplayName(): string {
|
||||||
return this.displayName;
|
return this.displayName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the username of the user of this connection.
|
||||||
|
*
|
||||||
|
* @returns the username or null for guest users
|
||||||
|
*/
|
||||||
public getUsername(): string | null {
|
public getUsername(): string | null {
|
||||||
return this.username;
|
return this.username;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the author style of the user of this connection.
|
||||||
|
*
|
||||||
|
* @returns the author style
|
||||||
|
*/
|
||||||
public getAuthorStyle(): number {
|
public getAuthorStyle(): number {
|
||||||
return this.authorStyle;
|
return this.authorStyle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the realtime note that this connection is connected to.
|
||||||
|
*
|
||||||
|
* @returns the realtime note
|
||||||
|
*/
|
||||||
public getRealtimeNote(): RealtimeNote {
|
public getRealtimeNote(): RealtimeNote {
|
||||||
return this.realtimeNote;
|
return this.realtimeNote;
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,17 +7,21 @@ import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
import { RealtimeNote } from './realtime-note';
|
import { RealtimeNote } from './realtime-note';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A store for {@link RealtimeNote} instances that are linked to a specific note.
|
||||||
|
* It allows creating, finding, and retrieving all realtime notes.
|
||||||
|
*/
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class RealtimeNoteStore {
|
export class RealtimeNoteStore {
|
||||||
private noteIdToRealtimeNote = new Map<number, RealtimeNote>();
|
private noteIdToRealtimeNote = new Map<number, RealtimeNote>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new {@link RealtimeNote} for the given {@link Note} and memorizes it.
|
* Creates a new {@link RealtimeNote} for the given note and memorizes it
|
||||||
*
|
*
|
||||||
* @param noteId The note for which the realtime note should be created
|
* @param noteId The id of the note for which the realtime note should be created
|
||||||
* @param initialTextContent the initial text content of realtime doc
|
* @param initialTextContent the initial text content of realtime doc
|
||||||
* @param initialYjsState the initial yjs state. If provided this will be used instead of the text content
|
* @param initialYjsState the initial yjs state. If provided this will be used instead of the text content
|
||||||
* @throws Error if there is already an realtime note for the given note.
|
* @throws Error if there is already a realtime note for the given note.
|
||||||
* @returns The created realtime note
|
* @returns The created realtime note
|
||||||
*/
|
*/
|
||||||
public create(
|
public create(
|
||||||
|
@ -41,16 +45,19 @@ export class RealtimeNoteStore {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves a {@link RealtimeNote} that is linked to the given {@link Note} id.
|
* Retrieves a {@link RealtimeNote} that is linked to the given note id
|
||||||
* @param noteId The id of the {@link Note}
|
*
|
||||||
* @returns A {@link RealtimeNote} or {@code undefined} if no instance is existing.
|
* @param noteId The id of the note
|
||||||
|
* @returns A {@link RealtimeNote} or undefined if no instance is existing
|
||||||
*/
|
*/
|
||||||
public find(noteId: number): RealtimeNote | undefined {
|
public find(noteId: number): RealtimeNote | undefined {
|
||||||
return this.noteIdToRealtimeNote.get(noteId);
|
return this.noteIdToRealtimeNote.get(noteId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns all registered {@link RealtimeNote realtime notes}.
|
* Returns all registered {@link RealtimeNote realtime notes}
|
||||||
|
*
|
||||||
|
* @returns An array of all realtime notes
|
||||||
*/
|
*/
|
||||||
public getAllRealtimeNotes(): RealtimeNote[] {
|
public getAllRealtimeNotes(): RealtimeNote[] {
|
||||||
return [...this.noteIdToRealtimeNote.values()];
|
return [...this.noteIdToRealtimeNote.values()];
|
||||||
|
|
|
@ -31,6 +31,10 @@ export class RealtimeNoteService implements BeforeApplicationShutdown {
|
||||||
private permissionService: PermissionService,
|
private permissionService: PermissionService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cleans up all {@link RealtimeNote} instances before the application is shut down
|
||||||
|
* This method is called by NestJS when the application is shutting down
|
||||||
|
*/
|
||||||
beforeApplicationShutdown(): void {
|
beforeApplicationShutdown(): void {
|
||||||
this.realtimeNoteStore
|
this.realtimeNoteStore
|
||||||
.getAllRealtimeNotes()
|
.getAllRealtimeNotes()
|
||||||
|
@ -38,7 +42,7 @@ export class RealtimeNoteService implements BeforeApplicationShutdown {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads the current content from the given {@link RealtimeNote} and creates a new {@link Revision} for the linked {@link Note}.
|
* Reads the current content from the given {@link RealtimeNote} and creates a new revision for the linked note.
|
||||||
*
|
*
|
||||||
* @param realtimeNote The realtime note for which a revision should be created
|
* @param realtimeNote The realtime note for which a revision should be created
|
||||||
*/
|
*/
|
||||||
|
@ -58,10 +62,11 @@ 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 note which is identified by the given note id
|
||||||
* @param noteId The {@link Note} for which a {@link RealtimeNote realtime note} should be retrieved.
|
*
|
||||||
|
* @param noteId The id of the note for which a {@link RealtimeNote} should be retrieved
|
||||||
|
* @returns A RealtimeNote that is linked to the given note.
|
||||||
* @throws NotInDBError if note doesn't exist or has no revisions.
|
* @throws NotInDBError if note doesn't exist or has no revisions.
|
||||||
* @returns A {@link RealtimeNote} that is linked to the given note.
|
|
||||||
*/
|
*/
|
||||||
public async getOrCreateRealtimeNote(noteId: number): Promise<RealtimeNote> {
|
public async getOrCreateRealtimeNote(noteId: number): Promise<RealtimeNote> {
|
||||||
return (
|
return (
|
||||||
|
@ -71,11 +76,12 @@ export class RealtimeNoteService implements BeforeApplicationShutdown {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new {@link RealtimeNote} for the given {@link Note}.
|
* Creates a new {@link RealtimeNote} for the given note and registers event listeners
|
||||||
|
* to persist the note periodically and before it is destroyed
|
||||||
*
|
*
|
||||||
* @param noteId The note for which the realtime note should be created
|
* @param noteId The id of the note for which the realtime note should be created
|
||||||
* @throws NotInDBError if note doesn't exist or has no revisions.
|
|
||||||
* @returns The created realtime note
|
* @returns The created realtime note
|
||||||
|
* @throws NotInDBError if the note doesn't exist or has no revisions
|
||||||
*/
|
*/
|
||||||
private async createNewRealtimeNote(noteId: number): Promise<RealtimeNote> {
|
private async createNewRealtimeNote(noteId: number): Promise<RealtimeNote> {
|
||||||
const lastRevision = await this.revisionsService.getLatestRevision(noteId);
|
const lastRevision = await this.revisionsService.getLatestRevision(noteId);
|
||||||
|
@ -117,16 +123,31 @@ export class RealtimeNoteService implements BeforeApplicationShutdown {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reflects the changes of the note's permissions to all connections of the note
|
||||||
|
*
|
||||||
|
* @param noteId The id of the note for that permissions changed
|
||||||
|
*/
|
||||||
@OnEvent(NoteEvent.PERMISSION_CHANGE)
|
@OnEvent(NoteEvent.PERMISSION_CHANGE)
|
||||||
public async handleNotePermissionChanged(noteId: number): Promise<void> {
|
public async handleNotePermissionChanged(noteId: number): Promise<void> {
|
||||||
const realtimeNote = this.realtimeNoteStore.find(noteId);
|
const realtimeNote = this.realtimeNoteStore.find(noteId);
|
||||||
if (!realtimeNote) return;
|
if (realtimeNote === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
realtimeNote.announceMetadataUpdate();
|
realtimeNote.announceMetadataUpdate();
|
||||||
const allConnections = realtimeNote.getConnections();
|
const allConnections = realtimeNote.getConnections();
|
||||||
await this.updateOrCloseConnection(allConnections, noteId);
|
await this.updateOrCloseConnection(allConnections, noteId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the connections of the given note based on the current permissions of the user.
|
||||||
|
* If the user has no permission to edit the note, the connection is closed.
|
||||||
|
* Otherwise, it updates the acceptEdits property of the connection.
|
||||||
|
*
|
||||||
|
* @param connections The connections to update
|
||||||
|
* @param noteId The id of the note for which the connections should be updated
|
||||||
|
*/
|
||||||
private async updateOrCloseConnection(
|
private async updateOrCloseConnection(
|
||||||
connections: RealtimeConnection[],
|
connections: RealtimeConnection[],
|
||||||
noteId: number,
|
noteId: number,
|
||||||
|
@ -145,6 +166,11 @@ export class RealtimeNoteService implements BeforeApplicationShutdown {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reflects the deletion of a note to all connections of the note
|
||||||
|
*
|
||||||
|
* @param noteId The id of the just deleted note
|
||||||
|
*/
|
||||||
@OnEvent(NoteEvent.DELETION)
|
@OnEvent(NoteEvent.DELETION)
|
||||||
public handleNoteDeleted(noteId: number): void {
|
public handleNoteDeleted(noteId: number): void {
|
||||||
const realtimeNote = this.realtimeNoteStore.find(noteId);
|
const realtimeNote = this.realtimeNoteStore.find(noteId);
|
||||||
|
@ -153,11 +179,16 @@ export class RealtimeNoteService implements BeforeApplicationShutdown {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes the realtime note for the given note id and saves its content
|
||||||
|
* This is called when the note is updated externally, e.g. by the API
|
||||||
|
*
|
||||||
|
* @param noteId The id of the note for which the realtime note should be closed
|
||||||
|
*/
|
||||||
@OnEvent(NoteEvent.CLOSE_REALTIME)
|
@OnEvent(NoteEvent.CLOSE_REALTIME)
|
||||||
public closeRealtimeNote(noteId: number): void {
|
public closeRealtimeNote(noteId: number): void {
|
||||||
const realtimeNote = this.realtimeNoteStore.find(noteId);
|
const realtimeNote = this.realtimeNoteStore.find(noteId);
|
||||||
if (realtimeNote) {
|
if (realtimeNote) {
|
||||||
this.saveRealtimeNote(realtimeNote);
|
|
||||||
realtimeNote.destroy();
|
realtimeNote.destroy();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,6 +31,13 @@ export class RealtimeNote extends EventEmitter2<RealtimeNoteEventMap> {
|
||||||
private isClosing = false;
|
private isClosing = false;
|
||||||
private destroyEventTimer: NodeJS.Timeout | null = null;
|
private destroyEventTimer: NodeJS.Timeout | null = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new realtime note for the given note id and initial text content
|
||||||
|
*
|
||||||
|
* @param noteId the id of the note that is being edited
|
||||||
|
* @param initialTextContent the initial text content of the note
|
||||||
|
* @param initialYjsState the initial yjs state of the note, if available
|
||||||
|
*/
|
||||||
constructor(
|
constructor(
|
||||||
private readonly noteId: number,
|
private readonly noteId: number,
|
||||||
initialTextContent: string,
|
initialTextContent: string,
|
||||||
|
@ -58,9 +65,8 @@ export class RealtimeNote extends EventEmitter2<RealtimeNoteEventMap> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Connects a new client to the note.
|
* Connects a new client to the note
|
||||||
*
|
* For this purpose a {@link RealtimeConnection} is created and added to the client map
|
||||||
* For this purpose a {@link RealtimeConnection} is created and added to the client map.
|
|
||||||
*
|
*
|
||||||
* @param client the websocket connection to the client
|
* @param client the websocket connection to the client
|
||||||
*/
|
*/
|
||||||
|
@ -73,7 +79,7 @@ export class RealtimeNote extends EventEmitter2<RealtimeNoteEventMap> {
|
||||||
/**
|
/**
|
||||||
* Disconnects the given websocket client while cleaning-up if it was the last user in the realtime note.
|
* Disconnects the given websocket client while cleaning-up if it was the last user in the realtime note.
|
||||||
*
|
*
|
||||||
* @param client The websocket client that disconnects.
|
* @param client The websocket client that disconnects
|
||||||
*/
|
*/
|
||||||
public removeClient(client: RealtimeConnection): void {
|
public removeClient(client: RealtimeConnection): void {
|
||||||
this.clients.delete(client);
|
this.clients.delete(client);
|
||||||
|
@ -94,7 +100,7 @@ export class RealtimeNote extends EventEmitter2<RealtimeNoteEventMap> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Destroys the current realtime note by deleting the y-js doc and disconnecting all clients.
|
* Destroys the current realtime note by deleting the yjs doc and disconnecting all clients
|
||||||
*
|
*
|
||||||
* @throws Error if note has already been destroyed
|
* @throws Error if note has already been destroyed
|
||||||
*/
|
*/
|
||||||
|
@ -112,60 +118,60 @@ export class RealtimeNote extends EventEmitter2<RealtimeNoteEventMap> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if there's still clients connected to this note.
|
* Checks if there are still clients connected to this note
|
||||||
*
|
*
|
||||||
* @returns {@code true} if there a still clinets connected, otherwise {@code false}
|
* @returns true if there are still clients connected, otherwise false
|
||||||
*/
|
*/
|
||||||
public hasConnections(): boolean {
|
public hasConnections(): boolean {
|
||||||
return this.clients.size !== 0;
|
return this.clients.size !== 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns all {@link RealtimeConnection WebsocketConnections} currently hold by this note.
|
* Returns all {@link RealtimeConnection} currently hold by this note
|
||||||
*
|
*
|
||||||
* @returns an array of {@link RealtimeConnection WebsocketConnections}
|
* @returns an array of {@link RealtimeConnection}s
|
||||||
*/
|
*/
|
||||||
public getConnections(): RealtimeConnection[] {
|
public getConnections(): RealtimeConnection[] {
|
||||||
return [...this.clients];
|
return [...this.clients];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the {@link RealtimeDoc realtime note} of the note.
|
* Gets the {@link RealtimeDoc} for the note.
|
||||||
*
|
*
|
||||||
* @returns the {@link RealtimeDoc realtime note} of the note
|
* @returns the {@link RealtimeDoc} for the note
|
||||||
*/
|
*/
|
||||||
public getRealtimeDoc(): RealtimeDoc {
|
public getRealtimeDoc(): RealtimeDoc {
|
||||||
return this.doc;
|
return this.doc;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the {@link Note note} that is edited.
|
* Gets the id of the note that is edited.
|
||||||
*
|
*
|
||||||
* @returns the {@link Note note}
|
* @returns the note id
|
||||||
*/
|
*/
|
||||||
public getNoteId(): number {
|
public getNoteId(): number {
|
||||||
return this.noteId;
|
return this.noteId;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Announce to all clients that the metadata of the note have been changed.
|
* Announces to all clients that the metadata of the note has been changed,
|
||||||
* This could for example be a permission change or a revision being saved.
|
* for example on a permission change or a revision being saved
|
||||||
*/
|
*/
|
||||||
public announceMetadataUpdate(): void {
|
public announceMetadataUpdate(): void {
|
||||||
this.sendToAllClients({ type: MessageType.METADATA_UPDATED });
|
this.sendToAllClients({ type: MessageType.METADATA_UPDATED });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Announce to all clients that the note has been deleted.
|
* Announces to all clients that the note has been deleted
|
||||||
*/
|
*/
|
||||||
public announceNoteDeletion(): void {
|
public announceNoteDeletion(): void {
|
||||||
this.sendToAllClients({ type: MessageType.DOCUMENT_DELETED });
|
this.sendToAllClients({ type: MessageType.DOCUMENT_DELETED });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Broadcasts the given content to all connected clients.
|
* Broadcasts the given content to all connected clients
|
||||||
*
|
*
|
||||||
* @param {Uint8Array} content The binary message to broadcast
|
* @param content The binary message to broadcast
|
||||||
*/
|
*/
|
||||||
private sendToAllClients(content: Message<MessageType>): void {
|
private sendToAllClients(content: Message<MessageType>): void {
|
||||||
this.getConnections().forEach((connection) => {
|
this.getConnections().forEach((connection) => {
|
||||||
|
@ -174,7 +180,9 @@ export class RealtimeNote extends EventEmitter2<RealtimeNoteEventMap> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates if a realtime note is ready to get destroyed.
|
* Indicates if a realtime note is ready to get destroyed
|
||||||
|
*
|
||||||
|
* @returns true if the note can be destroyed, otherwise false
|
||||||
*/
|
*/
|
||||||
private canBeDestroyed(): boolean {
|
private canBeDestroyed(): boolean {
|
||||||
return !this.hasConnections() && !this.isClosing;
|
return !this.hasConnections() && !this.isClosing;
|
||||||
|
|
|
@ -19,6 +19,16 @@ export type OtherAdapterCollector = () => RealtimeUserStatusAdapter[];
|
||||||
export class RealtimeUserStatusAdapter {
|
export class RealtimeUserStatusAdapter {
|
||||||
private readonly realtimeUser: RealtimeUser;
|
private readonly realtimeUser: RealtimeUser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new realtime user status adapter.
|
||||||
|
*
|
||||||
|
* @param username the username of the user, or null if the user is a guest
|
||||||
|
* @param displayName the display name of the user
|
||||||
|
* @param authorStyle the style index of the author
|
||||||
|
* @param collectOtherAdapters a function that returns all other adapters to send updates to
|
||||||
|
* @param messageTransporter the message transporter to use for sending messages
|
||||||
|
* @param acceptCursorUpdateProvider a function that returns whether cursor updates should be accepted
|
||||||
|
*/
|
||||||
constructor(
|
constructor(
|
||||||
private readonly username: string | null,
|
private readonly username: string | null,
|
||||||
private readonly displayName: string,
|
private readonly displayName: string,
|
||||||
|
@ -31,6 +41,11 @@ export class RealtimeUserStatusAdapter {
|
||||||
this.bindRealtimeUserStateEvents();
|
this.bindRealtimeUserStateEvents();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the current realtime user state
|
||||||
|
*
|
||||||
|
* @returns the current realtime user state
|
||||||
|
*/
|
||||||
private createInitialRealtimeUserState(): RealtimeUser {
|
private createInitialRealtimeUserState(): RealtimeUser {
|
||||||
return {
|
return {
|
||||||
username: this.username,
|
username: this.username,
|
||||||
|
@ -46,6 +61,9 @@ export class RealtimeUserStatusAdapter {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers the listeners for the realtime user state events
|
||||||
|
*/
|
||||||
private bindRealtimeUserStateEvents(): void {
|
private bindRealtimeUserStateEvents(): void {
|
||||||
const transporterMessagesListener = this.messageTransporter.on(
|
const transporterMessagesListener = this.messageTransporter.on(
|
||||||
MessageType.REALTIME_USER_SINGLE_UPDATE,
|
MessageType.REALTIME_USER_SINGLE_UPDATE,
|
||||||
|
@ -102,10 +120,19 @@ export class RealtimeUserStatusAdapter {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the current real-time user state if the message transporter is ready
|
||||||
|
*
|
||||||
|
* @returns the current real-time user state or undefined if the transporter is not ready
|
||||||
|
*/
|
||||||
private getSendableState(): RealtimeUser | undefined {
|
private getSendableState(): RealtimeUser | undefined {
|
||||||
return this.messageTransporter.isReady() ? this.realtimeUser : undefined;
|
return this.messageTransporter.isReady() ? this.realtimeUser : undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends the current real-time user state to all other clients
|
||||||
|
* This includes the own user state and the states of all other users
|
||||||
|
*/
|
||||||
public sendCompleteStateToClient(): void {
|
public sendCompleteStateToClient(): void {
|
||||||
if (!this.messageTransporter.isReady()) {
|
if (!this.messageTransporter.isReady()) {
|
||||||
return;
|
return;
|
||||||
|
@ -126,29 +153,4 @@ export class RealtimeUserStatusAdapter {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private findLeastUsedStyleIndex(map: Map<number, number>): number {
|
|
||||||
let leastUsedStyleIndex = 0;
|
|
||||||
let leastUsedStyleIndexCount = map.get(0) ?? 0;
|
|
||||||
for (let styleIndex = 0; styleIndex < 8; styleIndex++) {
|
|
||||||
const count = map.get(styleIndex) ?? 0;
|
|
||||||
if (count < leastUsedStyleIndexCount) {
|
|
||||||
leastUsedStyleIndexCount = count;
|
|
||||||
leastUsedStyleIndex = styleIndex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return leastUsedStyleIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
private createStyleIndexToCountMap(): Map<number, number> {
|
|
||||||
return this.collectOtherAdapters()
|
|
||||||
.map((adapter) => adapter.realtimeUser.styleIndex)
|
|
||||||
.reduce((map, styleIndex) => {
|
|
||||||
if (styleIndex !== undefined) {
|
|
||||||
const count = (map.get(styleIndex) ?? 0) + 1;
|
|
||||||
map.set(styleIndex, count);
|
|
||||||
}
|
|
||||||
return map;
|
|
||||||
}, new Map<number, number>());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,7 +42,7 @@ export class MockConnectionBuilder {
|
||||||
public withGuestUser(displayName: string): this {
|
public withGuestUser(displayName: string): this {
|
||||||
this.username = null;
|
this.username = null;
|
||||||
this.displayName = displayName;
|
this.displayName = displayName;
|
||||||
this.authorStyle = 8;
|
this.authorStyle = 2;
|
||||||
this.userId = 1000;
|
this.userId = 1000;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
@ -50,7 +50,7 @@ export class MockConnectionBuilder {
|
||||||
/**
|
/**
|
||||||
* Defines that the user who belongs to this connection is a logged-in user.
|
* Defines that the user who belongs to this connection is a logged-in user.
|
||||||
*
|
*
|
||||||
* @param username the username of the mocked user. If this value is omitted then the builder will user a {@link MOCK_FALLBACK_USERNAME fallback}.
|
* @param username the username of the mocked user
|
||||||
*/
|
*/
|
||||||
public withLoggedInUser(username: string): this {
|
public withLoggedInUser(username: string): this {
|
||||||
this.username = username;
|
this.username = username;
|
||||||
|
@ -79,7 +79,7 @@ export class MockConnectionBuilder {
|
||||||
/**
|
/**
|
||||||
* Creates a new connection based on the given configuration.
|
* Creates a new connection based on the given configuration.
|
||||||
*
|
*
|
||||||
* @returns {RealtimeConnection} The constructed mocked connection
|
* @returns The constructed mocked connection
|
||||||
* @throws Error if neither withGuestUser nor withLoggedInUser has been called.
|
* @throws Error if neither withGuestUser nor withLoggedInUser has been called.
|
||||||
*/
|
*/
|
||||||
public build(): RealtimeConnection {
|
public build(): RealtimeConnection {
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
import { MessageTransporter, MessageType } from '@hedgedoc/commons';
|
import { MessageTransporter, MessageType } from '@hedgedoc/commons';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A message transporter that is only used in testing where certain conditions like resending of requests isn't needed.
|
* A message transporter that is only used in testing where certain conditions like resending of requests aren't needed.
|
||||||
*/
|
*/
|
||||||
export class MockMessageTransporter extends MessageTransporter {
|
export class MockMessageTransporter extends MessageTransporter {
|
||||||
protected startSendingOfReadyRequests(): void {
|
protected startSendingOfReadyRequests(): void {
|
||||||
|
|
|
@ -194,6 +194,13 @@ export class RevisionsService {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a revision by its UUID
|
||||||
|
*
|
||||||
|
* @param revisionUuid The UUID of the revision to get
|
||||||
|
* @returns The revision DTO
|
||||||
|
* @throws NotInDBError if the revision with the given UUID does not exist
|
||||||
|
*/
|
||||||
async getRevisionDto(revisionUuid: string): Promise<RevisionDto> {
|
async getRevisionDto(revisionUuid: string): Promise<RevisionDto> {
|
||||||
const revision = await this.knex(TableRevision)
|
const revision = await this.knex(TableRevision)
|
||||||
.select(
|
.select(
|
||||||
|
@ -225,9 +232,10 @@ export class RevisionsService {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the latest
|
* Gets the latest revision of a note
|
||||||
* @param noteId
|
*
|
||||||
* @param transaction
|
* @param noteId The id of the note for which the latest revision should be retrieved
|
||||||
|
* @param transaction The optional pre-existing database transaction to use
|
||||||
*/
|
*/
|
||||||
async getLatestRevision(
|
async getLatestRevision(
|
||||||
noteId: number,
|
noteId: number,
|
||||||
|
@ -249,6 +257,13 @@ export class RevisionsService {
|
||||||
return revision;
|
return revision;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the user information of the authors of a revision
|
||||||
|
*
|
||||||
|
* @param revisionUuid The UUID of the revision for which the user information should be retrieved
|
||||||
|
* @param transaction The optional pre-existing database transaction to use
|
||||||
|
* @returns An object containing the usernames and guest UUIDs of the authors and the count of guest users
|
||||||
|
*/
|
||||||
async getRevisionUserInfo(
|
async getRevisionUserInfo(
|
||||||
revisionUuid: string,
|
revisionUuid: string,
|
||||||
transaction?: Knex,
|
transaction?: Knex,
|
||||||
|
@ -291,17 +306,15 @@ export class RevisionsService {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates (but does not persist(!)) a new {@link Revision} for the given {@link Note}.
|
* Creates a new revision for the given note
|
||||||
* Useful if the revision is saved together with the note in one action.
|
* This method wraps the actual action in a database transaction
|
||||||
*
|
|
||||||
*
|
*
|
||||||
* @param noteId The note for which the revision should be created
|
* @param noteId The note for which the revision should be created
|
||||||
* @param newContent The new note content
|
* @param newContent The new note content
|
||||||
* @param firstRevision Whether this is called for the first revision of a note
|
* @param firstRevision Whether this is called for the first revision of a note
|
||||||
* @param transaction The optional pre-existing database transaction to use
|
* @param transaction The optional pre-existing database transaction to use
|
||||||
* @param yjsStateVector The yjs state vector that describes the new content
|
* @param yjsStateVector The yjs state vector that describes the new content
|
||||||
* @returns {Revision} the created revision
|
* @returns the created revision or undefined if the revision couldn't be created
|
||||||
* @returns {undefined} if the revision couldn't be created because e.g. the content hasn't changed
|
|
||||||
*/
|
*/
|
||||||
async createRevision(
|
async createRevision(
|
||||||
noteId: number,
|
noteId: number,
|
||||||
|
@ -331,6 +344,16 @@ export class RevisionsService {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal method to create a revision for the given note
|
||||||
|
* This method is used by the public createRevision method and should not be called directly
|
||||||
|
*
|
||||||
|
* @param noteId The note for which the revision should be created
|
||||||
|
* @param newContent The new note content
|
||||||
|
* @param firstRevision Whether this is called for the first revision of a note
|
||||||
|
* @param transaction The database transaction to use
|
||||||
|
* @param yjsStateVector The yjs state vector that describes the new content
|
||||||
|
*/
|
||||||
private async innerCreateRevision(
|
private async innerCreateRevision(
|
||||||
noteId: number,
|
noteId: number,
|
||||||
newContent: string,
|
newContent: string,
|
||||||
|
@ -388,6 +411,13 @@ export class RevisionsService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all tags of a revision
|
||||||
|
*
|
||||||
|
* @param revisionUuid The UUID of the revision for which the tags should be retrieved
|
||||||
|
* @param transaction The optional pre-existing database transaction to use
|
||||||
|
* @returns An array of tags associated with the revision
|
||||||
|
*/
|
||||||
async getTagsByRevisionUuid(
|
async getTagsByRevisionUuid(
|
||||||
revisionUuid: string,
|
revisionUuid: string,
|
||||||
transaction?: Knex,
|
transaction?: Knex,
|
||||||
|
@ -412,7 +442,7 @@ export class RevisionsService {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete old {@link Revision}s except the latest one.
|
* Deletes old revisions except the latest one if the clean-up is enabled
|
||||||
*/
|
*/
|
||||||
async removeOldRevisions(): Promise<void> {
|
async removeOldRevisions(): Promise<void> {
|
||||||
const currentTime = new Date().getTime();
|
const currentTime = new Date().getTime();
|
||||||
|
|
|
@ -29,9 +29,10 @@ interface FrontmatterParserResult {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses the frontmatter of the given content and extracts the metadata that are necessary to create a new revision..
|
* Parses the frontmatter of the given content and extracts the metadata that are necessary to create a new revision
|
||||||
*
|
*
|
||||||
* @param content the revision content that contains the frontmatter.
|
* @param content the revision content that contains the frontmatter
|
||||||
|
* @returns the extracted metadata, including the title, description, tags, and note type
|
||||||
*/
|
*/
|
||||||
export function extractRevisionMetadataFromContent(
|
export function extractRevisionMetadataFromContent(
|
||||||
content: string,
|
content: string,
|
||||||
|
@ -52,6 +53,13 @@ export function extractRevisionMetadataFromContent(
|
||||||
return { title, description, tags, noteType };
|
return { title, description, tags, noteType };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates the content of a revision without the frontmatter.
|
||||||
|
*
|
||||||
|
* @param firstLineOfContentIndex the index of the first line of content after the frontmatter
|
||||||
|
* @param content the full content including frontmatter
|
||||||
|
* @returns the content without frontmatter
|
||||||
|
*/
|
||||||
function generateContentWithoutFrontmatter(
|
function generateContentWithoutFrontmatter(
|
||||||
firstLineOfContentIndex: number | undefined,
|
firstLineOfContentIndex: number | undefined,
|
||||||
content: string,
|
content: string,
|
||||||
|
@ -61,6 +69,12 @@ function generateContentWithoutFrontmatter(
|
||||||
: content.split('\n').slice(firstLineOfContentIndex).join('\n');
|
: content.split('\n').slice(firstLineOfContentIndex).join('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the frontmatter from the given content and returns the parsed frontmatter and the index of the first line of content.
|
||||||
|
*
|
||||||
|
* @param content the content to parse
|
||||||
|
* @returns an object containing the parsed frontmatter and the index of the first line of content, or undefined if no frontmatter was found
|
||||||
|
*/
|
||||||
function parseFrontmatter(
|
function parseFrontmatter(
|
||||||
content: string,
|
content: string,
|
||||||
): FrontmatterParserResult | undefined {
|
): FrontmatterParserResult | undefined {
|
||||||
|
@ -82,6 +96,12 @@ function parseFrontmatter(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts the first heading from the given markdown content
|
||||||
|
*
|
||||||
|
* @param content the content to extract the first heading from
|
||||||
|
* @returns the first heading or undefined if no heading was found
|
||||||
|
*/
|
||||||
function extractFirstHeadingFromContent(content: string): string | undefined {
|
function extractFirstHeadingFromContent(content: string): string | undefined {
|
||||||
const markdownIt = new MarkdownIt('default');
|
const markdownIt = new MarkdownIt('default');
|
||||||
const html = markdownIt.render(content);
|
const html = markdownIt.render(content);
|
||||||
|
|
|
@ -60,7 +60,7 @@ export class SessionService {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extracts the hedgedoc session cookie from the given {@link IncomingMessage request} and checks if the signature is correct.
|
* Extracts the HedgeDoc session cookie from the given {@link IncomingMessage request} and checks if the signature is correct.
|
||||||
*
|
*
|
||||||
* @param request The http request that contains a session cookie
|
* @param request The http request that contains a session cookie
|
||||||
* @returns An {@link Optional optional} that either contains the extracted session id or is empty if no session cookie has been found
|
* @returns An {@link Optional optional} that either contains the extracted session id or is empty if no session cookie has been found
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
|
@ -6,7 +6,7 @@
|
||||||
import { adjectives, items } from './random-words';
|
import { adjectives, items } from './random-words';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates a random names based on an adjective and a noun.
|
* Generates a random name based on an adjective and a noun
|
||||||
*
|
*
|
||||||
* @returns the generated name
|
* @returns the generated name
|
||||||
*/
|
*/
|
||||||
|
@ -16,6 +16,12 @@ export function generateRandomName(): string {
|
||||||
return `${adjective} ${things}`;
|
return `${adjective} ${things}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a random word from a given list and capitalizes the first letter
|
||||||
|
*
|
||||||
|
* @param list - The list of words to choose from
|
||||||
|
* @returns a randomly selected word with the first letter capitalized
|
||||||
|
*/
|
||||||
function generateRandomWord(list: string[]): string {
|
function generateRandomWord(list: string[]): string {
|
||||||
const index = Math.floor(Math.random() * list.length);
|
const index = Math.floor(Math.random() * list.length);
|
||||||
const word = list[index];
|
const word = list[index];
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
|
@ -23,7 +23,7 @@ import { v4 as uuidv4 } from 'uuid';
|
||||||
|
|
||||||
import { GenericDBError, NotInDBError } from '../errors/errors';
|
import { GenericDBError, NotInDBError } from '../errors/errors';
|
||||||
import { ConsoleLoggerService } from '../logger/console-logger.service';
|
import { ConsoleLoggerService } from '../logger/console-logger.service';
|
||||||
import { generateRandomName } from '../realtime/realtime-note/random-word-lists/name-randomizer';
|
import { generateRandomName } from './random-word-lists/name-randomizer';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class UsersService {
|
export class UsersService {
|
||||||
|
@ -41,13 +41,13 @@ export class UsersService {
|
||||||
*
|
*
|
||||||
* @param username New user's username
|
* @param username New user's username
|
||||||
* @param displayName New user's displayName
|
* @param displayName New user's displayName
|
||||||
* @param [email] New user's email address if exists
|
* @param email New user's email address if exists
|
||||||
* @param [photoUrl] URL of the user's profile picture if exists
|
* @param photoUrl URL of the user's profile picture if exists
|
||||||
* @param transaction The optional transaction to access the db
|
* @param transaction The optional transaction to access the db
|
||||||
* @returns The id of newly created user
|
* @returns The id of newly created user
|
||||||
* @throws {BadRequestException} if the username contains invalid characters or is too short
|
* @throws BadRequestException if the username contains invalid characters or is too short
|
||||||
* @throws {AlreadyInDBError} the username is already taken.
|
* @throws AlreadyInDBError if the username is already taken
|
||||||
* @thorws {GenericDBError} the database returned a non-expected value
|
* @thorws GenericDBError if the database returned a non-expected value
|
||||||
*/
|
*/
|
||||||
async createUser(
|
async createUser(
|
||||||
username: string,
|
username: string,
|
||||||
|
@ -97,7 +97,7 @@ export class UsersService {
|
||||||
* Creates a new guest user with a random displayName
|
* Creates a new guest user with a random displayName
|
||||||
*
|
*
|
||||||
* @returns The guest uuid and the id of the newly created user
|
* @returns The guest uuid and the id of the newly created user
|
||||||
* @throws {GenericDBError} the database returned a non-expected value
|
* @throws GenericDBError if the database returned a non-expected value
|
||||||
*/
|
*/
|
||||||
async createGuestUser(): Promise<[string, number]> {
|
async createGuestUser(): Promise<[string, number]> {
|
||||||
const randomName = generateRandomName();
|
const randomName = generateRandomName();
|
||||||
|
@ -128,7 +128,7 @@ export class UsersService {
|
||||||
* Deletes a user by its id
|
* Deletes a user by its id
|
||||||
*
|
*
|
||||||
* @param userId id of the user to be deleted
|
* @param userId id of the user to be deleted
|
||||||
* @throws {NotInDBError} the username has no user associated with it
|
* @throws NotInDBError if the username has no user associated with it
|
||||||
*/
|
*/
|
||||||
async deleteUser(userId: number): Promise<void> {
|
async deleteUser(userId: number): Promise<void> {
|
||||||
const usersDeleted = await this.knex(TableUser)
|
const usersDeleted = await this.knex(TableUser)
|
||||||
|
@ -229,7 +229,7 @@ export class UsersService {
|
||||||
*
|
*
|
||||||
* @param username The username to fetch
|
* @param username The username to fetch
|
||||||
* @returns The found user object
|
* @returns The found user object
|
||||||
* @throws {NotInDBError} if the user could not be found
|
* @throws NotInDBError if the user could not be found
|
||||||
*/
|
*/
|
||||||
async getUserIdByUsername(username: string): Promise<number> {
|
async getUserIdByUsername(username: string): Promise<number> {
|
||||||
const userId = await this.knex(TableUser)
|
const userId = await this.knex(TableUser)
|
||||||
|
@ -247,11 +247,11 @@ export class UsersService {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches the userId for a given username from the database
|
* Fetches the userId for a given guest uuid from the database
|
||||||
*
|
*
|
||||||
* @param uuid The uuid to fetch
|
* @param uuid The guest uuid to fetch
|
||||||
* @returns The found user object
|
* @returns The found user object
|
||||||
* @throws {NotInDBError} if the user could not be found
|
* @throws NotInDBError if the guest user could not be found
|
||||||
*/
|
*/
|
||||||
async getUserIdByGuestUuid(uuid: string): Promise<User[FieldNameUser.id]> {
|
async getUserIdByGuestUuid(uuid: string): Promise<User[FieldNameUser.id]> {
|
||||||
const userId = await this.knex(TableUser)
|
const userId = await this.knex(TableUser)
|
||||||
|
@ -273,7 +273,7 @@ export class UsersService {
|
||||||
*
|
*
|
||||||
* @param username The username to fetch
|
* @param username The username to fetch
|
||||||
* @returns The found user object
|
* @returns The found user object
|
||||||
* @throws {NotInDBError} if the user could not be found
|
* @throws NotInDBError if the user could not be found
|
||||||
*/
|
*/
|
||||||
async getUserDtoByUsername(username: string): Promise<UserInfoDto> {
|
async getUserDtoByUsername(username: string): Promise<UserInfoDto> {
|
||||||
const user = await this.knex(TableUser)
|
const user = await this.knex(TableUser)
|
||||||
|
|
|
@ -1,9 +0,0 @@
|
||||||
/*
|
|
||||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
*/
|
|
||||||
|
|
||||||
export function hasArrayDuplicates<T>(array: Array<T>): boolean {
|
|
||||||
return new Set(array).size !== array.length;
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
/*
|
|
||||||
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The base class for all DTOs.
|
|
||||||
*/
|
|
||||||
export abstract class BaseDto {}
|
|
|
@ -37,11 +37,11 @@ export async function checkPassword(
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Transform a {@link Buffer} into a base64Url encoded string
|
* Transforms a {@link Buffer} into a base64Url encoded string
|
||||||
*
|
*
|
||||||
* This is necessary as the is no base64url encoding in the toString method
|
* This is necessary as there is no base64url encoding in the toString method
|
||||||
* but as can be seen on https://tools.ietf.org/html/rfc4648#page-7
|
* but as can be seen on https://tools.ietf.org/html/rfc4648#page-7
|
||||||
* base64url is quite easy buildable from base64
|
* base64url is quite easily buildable from base64
|
||||||
*
|
*
|
||||||
* @param text The buffer we want to decode
|
* @param text The buffer we want to decode
|
||||||
* @returns The base64Url encoded string
|
* @returns The base64Url encoded string
|
||||||
|
@ -73,7 +73,7 @@ export function hashApiToken(token: string): string {
|
||||||
*
|
*
|
||||||
* @param userSecret The secret of the token the user gave us
|
* @param userSecret The secret of the token the user gave us
|
||||||
* @param databaseSecretHash The secret hash we have saved in the database.
|
* @param databaseSecretHash The secret hash we have saved in the database.
|
||||||
* @returns Wether or not the tokens are the equal
|
* @returns Whether or not the tokens are equal
|
||||||
*/
|
*/
|
||||||
export function checkTokenEquality(
|
export function checkTokenEquality(
|
||||||
userSecret: string,
|
userSecret: string,
|
||||||
|
|
|
@ -11,10 +11,10 @@ import { join as joinPath } from 'path';
|
||||||
let versionCache: ServerVersionDto | undefined = undefined;
|
let versionCache: ServerVersionDto | undefined = undefined;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads the HedgeDoc version from the root package.json. This is done only once per run.
|
* Reads the HedgeDoc version from the root package.json. This is done only once per run and then cached for further calls
|
||||||
*
|
*
|
||||||
* @returns {Promise<ServerVersionDto>} A Promise that contains the parsed server version.
|
* @returns A Promise that contains the parsed server version.
|
||||||
* @throws {Error} if the package.json couldn't be found or doesn't contain a correct version.
|
* @throws Error if the package.json couldn't be found or doesn't contain a correct version.
|
||||||
*/
|
*/
|
||||||
export async function getServerVersionFromPackageJson(): Promise<ServerVersionDto> {
|
export async function getServerVersionFromPackageJson(): Promise<ServerVersionDto> {
|
||||||
if (!versionCache) {
|
if (!versionCache) {
|
||||||
|
@ -23,6 +23,12 @@ export async function getServerVersionFromPackageJson(): Promise<ServerVersionDt
|
||||||
return versionCache;
|
return versionCache;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the version from the root package.json file.
|
||||||
|
*
|
||||||
|
* @returns A Promise that contains the parsed server version.
|
||||||
|
* @throws Error if the package.json couldn't be found or doesn't contain a correct version.
|
||||||
|
*/
|
||||||
async function parseVersionFromPackageJson(): Promise<ServerVersionDto> {
|
async function parseVersionFromPackageJson(): Promise<ServerVersionDto> {
|
||||||
const rawFileContent: string = await fs.readFile(
|
const rawFileContent: string = await fs.readFile(
|
||||||
joinPath(__dirname, '../../../package.json'),
|
joinPath(__dirname, '../../../package.json'),
|
||||||
|
@ -47,6 +53,11 @@ async function parseVersionFromPackageJson(): Promise<ServerVersionDto> {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears the cached version information
|
||||||
|
*
|
||||||
|
* This function is useful for testing purposes or when the version information needs to be reloaded
|
||||||
|
*/
|
||||||
export function clearCachedVersion(): void {
|
export function clearCachedVersion(): void {
|
||||||
versionCache = undefined;
|
versionCache = undefined;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
@ -10,6 +10,11 @@ import { PrivateApiModule } from '../api/private/private-api.module';
|
||||||
import { PublicApiModule } from '../api/public/public-api.module';
|
import { PublicApiModule } from '../api/public/public-api.module';
|
||||||
import { getServerVersionFromPackageJson } from './server-version';
|
import { getServerVersionFromPackageJson } from './server-version';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up the public API documentation for HedgeDoc.
|
||||||
|
*
|
||||||
|
* @param app The NestJS application instance to set up the Swagger module on.
|
||||||
|
*/
|
||||||
export async function setupPublicApiDocs(app: INestApplication): Promise<void> {
|
export async function setupPublicApiDocs(app: INestApplication): Promise<void> {
|
||||||
const version = await getServerVersionFromPackageJson();
|
const version = await getServerVersionFromPackageJson();
|
||||||
const publicApiOptions = new DocumentBuilder()
|
const publicApiOptions = new DocumentBuilder()
|
||||||
|
@ -26,6 +31,11 @@ export async function setupPublicApiDocs(app: INestApplication): Promise<void> {
|
||||||
SwaggerModule.setup('api/doc/v2', app, publicApi);
|
SwaggerModule.setup('api/doc/v2', app, publicApi);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up the private API documentation for HedgeDoc.
|
||||||
|
*
|
||||||
|
* @param app The NestJS application instance to set up the Swagger module on.
|
||||||
|
*/
|
||||||
export async function setupPrivateApiDocs(
|
export async function setupPrivateApiDocs(
|
||||||
app: INestApplication,
|
app: INestApplication,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue