From 4b5bf870f2bfb75996973817ceaf065e9808c83e Mon Sep 17 00:00:00 2001 From: Philip Molares Date: Fri, 21 Mar 2025 21:14:29 +0100 Subject: [PATCH] feat(commons): add DTOs Moving the DTOs to commons so frontend and backend use the same types. Also introducing zod for validation. Co-authored-by: Erik Michelson Signed-off-by: Erik Michelson Signed-off-by: Philip Molares --- commons/src/dtos/alias/alias-create.dto.ts | 20 ++++++ commons/src/dtos/alias/alias-update.dto.ts | 17 +++++ commons/src/dtos/alias/alias.dto.ts | 21 +++++++ commons/src/dtos/alias/index.ts | 9 +++ .../dtos/api-token/api-token-create.dto.ts | 26 ++++++++ .../api-token/api-token-with-secret.dto.ts | 17 +++++ commons/src/dtos/api-token/api-token.dto.ts | 28 +++++++++ commons/src/dtos/api-token/index.ts | 9 +++ commons/src/dtos/auth/index.ts | 16 +++++ .../src/dtos/auth/ldap-login-response.dto.ts | 15 +++++ commons/src/dtos/auth/ldap-login.dto.ts | 16 +++++ commons/src/dtos/auth/login.dto.ts | 20 ++++++ commons/src/dtos/auth/logout-response.dto.ts | 18 ++++++ .../auth/pending-user-confirmation.dto.ts | 34 ++++++++++ .../src/dtos}/auth/provider-type.enum.ts | 2 +- commons/src/dtos/auth/register.dto.ts | 24 +++++++ commons/src/dtos/auth/update-password.dto.ts | 18 ++++++ .../dtos/auth/username-check-reponse.dto.ts | 18 ++++++ commons/src/dtos/auth/username-check.dto.ts | 17 +++++ commons/src/dtos/edit/edit.dto.ts | 28 +++++++++ commons/src/dtos/edit/index.ts | 7 +++ .../auth-provider-with-custom-name.dto.ts | 31 ++++++++++ .../auth-provider-without-custom-name.dto.ts | 19 ++++++ .../dtos/frontend-config/auth-provider.dto.ts | 18 ++++++ .../src/dtos/frontend-config/branding.dto.ts | 23 +++++++ .../frontend-config/frontend-config.dto.ts | 53 ++++++++++++++++ commons/src/dtos/frontend-config/index.ts | 11 ++++ .../dtos/frontend-config/special-urls.dto.ts | 28 +++++++++ commons/src/dtos/group/group-info.dto.ts | 20 ++++++ commons/src/dtos/group/index.ts | 7 +++ commons/src/dtos/group/special-group.enum.ts | 10 +++ commons/src/dtos/index.ts | 17 +++++ commons/src/dtos/media/index.ts | 6 ++ commons/src/dtos/media/media-upload.dto.ts | 28 +++++++++ commons/src/dtos/monitoring/index.ts | 7 +++ .../src/dtos/monitoring/server-status.dto.ts | 62 +++++++++++++++++++ .../src/dtos/monitoring/server-version.dto.ts | 22 +++++++ commons/src/dtos/note/index.ts | 9 +++ .../src/dtos/note/note-metadata-update.dto.ts | 24 +++++++ commons/src/dtos/note/note-metadata.dto.ts | 56 +++++++++++++++++ commons/src/dtos/note/note.dto.ts | 19 ++++++ .../src/dtos/note/note.media-deletion.dto.ts | 21 +++++++ .../dtos/permissions/change-note-owner.dto.ts | 15 +++++ .../src/dtos/permissions/guest-access.enum.ts | 14 ++--- commons/src/dtos/permissions/index.ts | 14 +++++ .../note-group-permission-entry.dto.ts | 17 +++++ .../note-group-permission-update.dto.ts | 18 ++++++ .../note-permissions-update.dto.ts | 23 +++++++ .../dtos/permissions/note-permissions.dto.ts | 23 +++++++ .../note-user-permission-entry.dto.ts | 18 ++++++ .../note-user-permission-update.dto.ts | 18 ++++++ commons/src/dtos/revision/index.ts | 8 +++ .../dtos/revision/revision-metadata.dto.ts | 41 ++++++++++++ commons/src/dtos/revision/revision.dto.ts | 23 +++++++ .../dtos/user/full-user-info-with-id.dto.ts | 17 +++++ commons/src/dtos/user/full-user-info.dto.ts | 21 +++++++ commons/src/dtos/user/index.ts | 10 +++ commons/src/dtos/user/login-user-info.dto.ts | 21 +++++++ commons/src/dtos/user/update-user-info.dto.ts | 19 ++++++ commons/src/dtos/user/user-info.dto.ts | 20 ++++++ commons/src/index.ts | 4 +- commons/src/permissions/index.ts | 3 +- commons/src/permissions/permissions.spec.ts | 4 +- commons/src/permissions/permissions.ts | 6 +- commons/src/permissions/permissions.types.ts | 31 ---------- 65 files changed, 1211 insertions(+), 48 deletions(-) create mode 100644 commons/src/dtos/alias/alias-create.dto.ts create mode 100644 commons/src/dtos/alias/alias-update.dto.ts create mode 100644 commons/src/dtos/alias/alias.dto.ts create mode 100644 commons/src/dtos/alias/index.ts create mode 100644 commons/src/dtos/api-token/api-token-create.dto.ts create mode 100644 commons/src/dtos/api-token/api-token-with-secret.dto.ts create mode 100644 commons/src/dtos/api-token/api-token.dto.ts create mode 100644 commons/src/dtos/api-token/index.ts create mode 100644 commons/src/dtos/auth/index.ts create mode 100644 commons/src/dtos/auth/ldap-login-response.dto.ts create mode 100644 commons/src/dtos/auth/ldap-login.dto.ts create mode 100644 commons/src/dtos/auth/login.dto.ts create mode 100644 commons/src/dtos/auth/logout-response.dto.ts create mode 100644 commons/src/dtos/auth/pending-user-confirmation.dto.ts rename {backend/src => commons/src/dtos}/auth/provider-type.enum.ts (72%) create mode 100644 commons/src/dtos/auth/register.dto.ts create mode 100644 commons/src/dtos/auth/update-password.dto.ts create mode 100644 commons/src/dtos/auth/username-check-reponse.dto.ts create mode 100644 commons/src/dtos/auth/username-check.dto.ts create mode 100644 commons/src/dtos/edit/edit.dto.ts create mode 100644 commons/src/dtos/edit/index.ts create mode 100644 commons/src/dtos/frontend-config/auth-provider-with-custom-name.dto.ts create mode 100644 commons/src/dtos/frontend-config/auth-provider-without-custom-name.dto.ts create mode 100644 commons/src/dtos/frontend-config/auth-provider.dto.ts create mode 100644 commons/src/dtos/frontend-config/branding.dto.ts create mode 100644 commons/src/dtos/frontend-config/frontend-config.dto.ts create mode 100644 commons/src/dtos/frontend-config/index.ts create mode 100644 commons/src/dtos/frontend-config/special-urls.dto.ts create mode 100644 commons/src/dtos/group/group-info.dto.ts create mode 100644 commons/src/dtos/group/index.ts create mode 100644 commons/src/dtos/group/special-group.enum.ts create mode 100644 commons/src/dtos/index.ts create mode 100644 commons/src/dtos/media/index.ts create mode 100644 commons/src/dtos/media/media-upload.dto.ts create mode 100644 commons/src/dtos/monitoring/index.ts create mode 100644 commons/src/dtos/monitoring/server-status.dto.ts create mode 100644 commons/src/dtos/monitoring/server-version.dto.ts create mode 100644 commons/src/dtos/note/index.ts create mode 100644 commons/src/dtos/note/note-metadata-update.dto.ts create mode 100644 commons/src/dtos/note/note-metadata.dto.ts create mode 100644 commons/src/dtos/note/note.dto.ts create mode 100644 commons/src/dtos/note/note.media-deletion.dto.ts create mode 100644 commons/src/dtos/permissions/change-note-owner.dto.ts rename backend/src/config/guest_access.enum.ts => commons/src/dtos/permissions/guest-access.enum.ts (56%) create mode 100644 commons/src/dtos/permissions/index.ts create mode 100644 commons/src/dtos/permissions/note-group-permission-entry.dto.ts create mode 100644 commons/src/dtos/permissions/note-group-permission-update.dto.ts create mode 100644 commons/src/dtos/permissions/note-permissions-update.dto.ts create mode 100644 commons/src/dtos/permissions/note-permissions.dto.ts create mode 100644 commons/src/dtos/permissions/note-user-permission-entry.dto.ts create mode 100644 commons/src/dtos/permissions/note-user-permission-update.dto.ts create mode 100644 commons/src/dtos/revision/index.ts create mode 100644 commons/src/dtos/revision/revision-metadata.dto.ts create mode 100644 commons/src/dtos/revision/revision.dto.ts create mode 100644 commons/src/dtos/user/full-user-info-with-id.dto.ts create mode 100644 commons/src/dtos/user/full-user-info.dto.ts create mode 100644 commons/src/dtos/user/index.ts create mode 100644 commons/src/dtos/user/login-user-info.dto.ts create mode 100644 commons/src/dtos/user/update-user-info.dto.ts create mode 100644 commons/src/dtos/user/user-info.dto.ts delete mode 100644 commons/src/permissions/permissions.types.ts diff --git a/commons/src/dtos/alias/alias-create.dto.ts b/commons/src/dtos/alias/alias-create.dto.ts new file mode 100644 index 000000000..1f0560256 --- /dev/null +++ b/commons/src/dtos/alias/alias-create.dto.ts @@ -0,0 +1,20 @@ +/* + * SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { z } from 'zod' + +export const AliasCreateSchema = z + .object({ + noteIdOrAlias: z + .string() + .describe( + 'The note id, which identifies the note the alias should be added to', + ), + newAlias: z.string().describe('The new alias'), + }) + .describe('DTO for creating a new alias') + +export type AliasCreateDto = z.infer diff --git a/commons/src/dtos/alias/alias-update.dto.ts b/commons/src/dtos/alias/alias-update.dto.ts new file mode 100644 index 000000000..9235d65fd --- /dev/null +++ b/commons/src/dtos/alias/alias-update.dto.ts @@ -0,0 +1,17 @@ +/* + * SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { z } from 'zod' + +export const AliasUpdateSchema = z + .object({ + primaryAlias: z + .literal(true) + .describe('Whether the alias should become the primary alias or not'), + }) + .describe('DTO for making one alias primary') + +export type AliasUpdateDto = z.infer diff --git a/commons/src/dtos/alias/alias.dto.ts b/commons/src/dtos/alias/alias.dto.ts new file mode 100644 index 000000000..ff9997ba9 --- /dev/null +++ b/commons/src/dtos/alias/alias.dto.ts @@ -0,0 +1,21 @@ +/* + * SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { z } from 'zod' + +export const AliasSchema = z + .object({ + name: z.string().describe('The name of the alias'), + primaryAlias: z.boolean().describe('Is the alias the primary alias or not'), + noteId: z + .string() + .describe('The public id of the note the alias is associated with'), + }) + .describe( + 'The alias of a note. A note can have multiple of these. Only one can be the primary alias.', + ) + +export type AliasDto = z.infer diff --git a/commons/src/dtos/alias/index.ts b/commons/src/dtos/alias/index.ts new file mode 100644 index 000000000..65809d249 --- /dev/null +++ b/commons/src/dtos/alias/index.ts @@ -0,0 +1,9 @@ +/* + * SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export * from './alias.dto.js' +export * from './alias-create.dto.js' +export * from './alias-update.dto.js' diff --git a/commons/src/dtos/api-token/api-token-create.dto.ts b/commons/src/dtos/api-token/api-token-create.dto.ts new file mode 100644 index 000000000..aacf0a78c --- /dev/null +++ b/commons/src/dtos/api-token/api-token-create.dto.ts @@ -0,0 +1,26 @@ +/* + * SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ +import { z } from 'zod' + +const nowPlusTwoYears = (): Date => { + const date = new Date() + date.setFullYear(date.getFullYear() + 2) + return date +} + +export const ApiTokenCreateSchema = z + .object({ + label: z.string().describe('Label for the new token'), + validUntil: z.coerce + .date() + .max(nowPlusTwoYears()) + .describe( + 'Expiry date for the new token. Should be at max two years in the future.', + ), + }) + .describe('DTO for creating a new API access token') + +export type ApiTokenCreateDto = z.infer diff --git a/commons/src/dtos/api-token/api-token-with-secret.dto.ts b/commons/src/dtos/api-token/api-token-with-secret.dto.ts new file mode 100644 index 000000000..8dfd1c97b --- /dev/null +++ b/commons/src/dtos/api-token/api-token-with-secret.dto.ts @@ -0,0 +1,17 @@ +/* + * SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ +import { z } from 'zod' +import { ApiTokenSchema } from './api-token.dto.js' + +export const ApiTokenWithSecretSchema = ApiTokenSchema.merge( + z.object({ + secret: z.string().describe('The secret part of the API token'), + }), +).describe( + 'This is returned once after an api token is created to let the user know what their token is.', +) + +export type ApiTokenWithSecretDto = z.infer diff --git a/commons/src/dtos/api-token/api-token.dto.ts b/commons/src/dtos/api-token/api-token.dto.ts new file mode 100644 index 000000000..172d14410 --- /dev/null +++ b/commons/src/dtos/api-token/api-token.dto.ts @@ -0,0 +1,28 @@ +/* + * SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { z } from 'zod' + +export const ApiTokenSchema = z + .object({ + label: z.string().describe('The label of the token'), + keyId: z.string().describe('The id of the token'), + createdAt: z.string().datetime().describe('When this token was created'), + validUntil: z + .string() + .datetime() + .describe('How long this token is valid fro'), + lastUsedAt: z + .string() + .datetime() + .nullable() + .describe('When this token was last used'), + }) + .describe( + 'Represents an access token for the public API. Each API token is bound to a user account. A user can have multiple API tokens.', + ) + +export type ApiTokenDto = z.infer diff --git a/commons/src/dtos/api-token/index.ts b/commons/src/dtos/api-token/index.ts new file mode 100644 index 000000000..8be0c2e42 --- /dev/null +++ b/commons/src/dtos/api-token/index.ts @@ -0,0 +1,9 @@ +/* + * SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export * from './api-token.dto.js' +export * from './api-token-create.dto.js' +export * from './api-token-with-secret.dto.js' diff --git a/commons/src/dtos/auth/index.ts b/commons/src/dtos/auth/index.ts new file mode 100644 index 000000000..6991c1021 --- /dev/null +++ b/commons/src/dtos/auth/index.ts @@ -0,0 +1,16 @@ +/* + * SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export * from './ldap-login.dto.js' +export * from './ldap-login-response.dto.js' +export * from './login.dto.js' +export * from './logout-response.dto.js' +export * from './pending-user-confirmation.dto.js' +export * from './provider-type.enum.js' +export * from './register.dto.js' +export * from './update-password.dto.js' +export * from './username-check.dto.js' +export * from './username-check-reponse.dto.js' diff --git a/commons/src/dtos/auth/ldap-login-response.dto.ts b/commons/src/dtos/auth/ldap-login-response.dto.ts new file mode 100644 index 000000000..369353816 --- /dev/null +++ b/commons/src/dtos/auth/ldap-login-response.dto.ts @@ -0,0 +1,15 @@ +/* + * SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { z } from 'zod' + +export const LdapLoginResponseSchema = z + .object({ + newUser: z.boolean().describe('If the LDAP user was newly created.'), + }) + .describe('DTO to login via a LDAP server.') + +export type LdapLoginResponseDto = z.infer diff --git a/commons/src/dtos/auth/ldap-login.dto.ts b/commons/src/dtos/auth/ldap-login.dto.ts new file mode 100644 index 000000000..72381197b --- /dev/null +++ b/commons/src/dtos/auth/ldap-login.dto.ts @@ -0,0 +1,16 @@ +/* + * SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { z } from 'zod' + +export const LdapLoginSchema = z + .object({ + username: z.string().describe('The username to log in at the LDAP server'), + password: z.string().describe('The password to log in at the LDAP server'), + }) + .describe('DTO to login via a LDAP server.') + +export type LdapLoginDto = z.infer diff --git a/commons/src/dtos/auth/login.dto.ts b/commons/src/dtos/auth/login.dto.ts new file mode 100644 index 000000000..58097cd1a --- /dev/null +++ b/commons/src/dtos/auth/login.dto.ts @@ -0,0 +1,20 @@ +/* + * SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ +import { z } from 'zod' + +export const LoginSchema = z + .object({ + username: z + .string() + .toLowerCase() + .describe('The username to log in with local authentication'), + password: z + .string() + .describe('The password to log in with local authentication'), + }) + .describe('DTO for the login form of local accounts') + +export type LoginDto = z.infer diff --git a/commons/src/dtos/auth/logout-response.dto.ts b/commons/src/dtos/auth/logout-response.dto.ts new file mode 100644 index 000000000..aa9e4083a --- /dev/null +++ b/commons/src/dtos/auth/logout-response.dto.ts @@ -0,0 +1,18 @@ +/* + * SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { z } from 'zod' + +export const LogoutResponseSchema = z + .object({ + redirect: z + .string() + .url() + .describe('Where the user shall be redirected to after the logout.'), + }) + .describe('Information the user gets after logging out.') + +export type LogoutResponseDto = z.infer diff --git a/commons/src/dtos/auth/pending-user-confirmation.dto.ts b/commons/src/dtos/auth/pending-user-confirmation.dto.ts new file mode 100644 index 000000000..6475faab7 --- /dev/null +++ b/commons/src/dtos/auth/pending-user-confirmation.dto.ts @@ -0,0 +1,34 @@ +/* + * SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { z } from 'zod' + +export const PendingUserConfirmationSchema = z + .object({ + username: z + .string() + .min(3) + .max(64) + .toLowerCase() + .describe('The chosen new username for the pending user'), + displayName: z + .string() + .describe('The new display name for the pending user'), + profilePicture: z + .string() + .url() + .nullable() + .describe( + 'The URL to the chosen profile picture or null to use the auto-generated one', + ), + }) + .describe( + 'DTO for the confirmation of a new user account. When a new user is created through OIDC login, they get asked to choose some details for their new account.', + ) + +export type PendingUserConfirmationDto = z.infer< + typeof PendingUserConfirmationSchema +> diff --git a/backend/src/auth/provider-type.enum.ts b/commons/src/dtos/auth/provider-type.enum.ts similarity index 72% rename from backend/src/auth/provider-type.enum.ts rename to commons/src/dtos/auth/provider-type.enum.ts index 334b9ff67..1ace0aa0e 100644 --- a/backend/src/auth/provider-type.enum.ts +++ b/commons/src/dtos/auth/provider-type.enum.ts @@ -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 */ diff --git a/commons/src/dtos/auth/register.dto.ts b/commons/src/dtos/auth/register.dto.ts new file mode 100644 index 000000000..6ab564745 --- /dev/null +++ b/commons/src/dtos/auth/register.dto.ts @@ -0,0 +1,24 @@ +/* + * SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ +import { z } from 'zod' + +export const RegisterSchema = z + .object({ + username: z + .string() + .min(3) + .max(64) + .toLowerCase() + .describe('The new username for local account registration'), + displayName: z.string().describe('The display name of the new user'), + password: z + .string() + .min(6) + .describe('The new password for the local account'), + }) + .describe('DTO to register a local user account') + +export type RegisterDto = z.infer diff --git a/commons/src/dtos/auth/update-password.dto.ts b/commons/src/dtos/auth/update-password.dto.ts new file mode 100644 index 000000000..9208bf20b --- /dev/null +++ b/commons/src/dtos/auth/update-password.dto.ts @@ -0,0 +1,18 @@ +/* + * SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ +import { z } from 'zod' + +export const UpdatePasswordSchema = z + .object({ + currentPassword: z + .string() + .min(6) + .describe('The current password of the user'), + newPassword: z.string().min(6).describe('The new password of the user'), + }) + .describe('DTO to update the password of a local user account') + +export type UpdatePasswordDto = z.infer diff --git a/commons/src/dtos/auth/username-check-reponse.dto.ts b/commons/src/dtos/auth/username-check-reponse.dto.ts new file mode 100644 index 000000000..953715ba1 --- /dev/null +++ b/commons/src/dtos/auth/username-check-reponse.dto.ts @@ -0,0 +1,18 @@ +/* + * SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ +import { z } from 'zod' + +export const UsernameCheckResponseSchema = z + .object({ + usernameAvailable: z + .boolean() + .describe('Whether the chosen username is available or not'), + }) + .describe('Response to the username check on the register forms') + +export type UsernameCheckResponseDto = z.infer< + typeof UsernameCheckResponseSchema +> diff --git a/commons/src/dtos/auth/username-check.dto.ts b/commons/src/dtos/auth/username-check.dto.ts new file mode 100644 index 000000000..d36b35e6d --- /dev/null +++ b/commons/src/dtos/auth/username-check.dto.ts @@ -0,0 +1,17 @@ +/* + * SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ +import { z } from 'zod' + +export const UsernameCheckSchema = z + .object({ + username: z + .string() + .toLowerCase() + .describe("The username the user want's to register"), + }) + .describe('DTO to check if a username is available') + +export type UsernameCheckDto = z.infer diff --git a/commons/src/dtos/edit/edit.dto.ts b/commons/src/dtos/edit/edit.dto.ts new file mode 100644 index 000000000..06f3e1526 --- /dev/null +++ b/commons/src/dtos/edit/edit.dto.ts @@ -0,0 +1,28 @@ +/* + * SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { z } from 'zod' + +export const EditSchema = z + .object({ + username: z + .string() + .nullable() + .describe('The username who changed this section of the note'), + startPosition: z + .number() + .positive() + .describe('The offset where the change starts in the note'), + endPosition: z + .number() + .positive() + .describe('The offset where the change ends in the note'), + createdAt: z.string().datetime().describe('When this edit happened'), + updatedAt: z.string().datetime().describe('When this edit was updated?'), + }) + .describe('A edit in a note by username from startPosition to endPosition.') + +export type EditDto = z.infer diff --git a/commons/src/dtos/edit/index.ts b/commons/src/dtos/edit/index.ts new file mode 100644 index 000000000..83d5fcd52 --- /dev/null +++ b/commons/src/dtos/edit/index.ts @@ -0,0 +1,7 @@ +/* + * SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export * from './edit.dto.js' diff --git a/commons/src/dtos/frontend-config/auth-provider-with-custom-name.dto.ts b/commons/src/dtos/frontend-config/auth-provider-with-custom-name.dto.ts new file mode 100644 index 000000000..07bb91b2a --- /dev/null +++ b/commons/src/dtos/frontend-config/auth-provider-with-custom-name.dto.ts @@ -0,0 +1,31 @@ +/* + * SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { z } from 'zod' +import { ProviderType } from '../auth/index.js' + +export const AuthProviderWithCustomNameSchema = z + .object({ + type: z + .literal(ProviderType.LDAP) + .or(z.literal(ProviderType.OIDC)) + .describe('The type of the auth provider'), + identifier: z + .string() + .describe('The identifier with which the auth provider can be called'), + providerName: z.string().describe('The name given to the auth provider'), + theme: z + .string() + .nullable() + .describe('The theme to apply for the login button.'), + }) + .describe( + 'The configuration for an auth provider with a custom name. So you can have multiple of the same kind.', + ) + +export type AuthProviderWithCustomNameDto = z.infer< + typeof AuthProviderWithCustomNameSchema +> diff --git a/commons/src/dtos/frontend-config/auth-provider-without-custom-name.dto.ts b/commons/src/dtos/frontend-config/auth-provider-without-custom-name.dto.ts new file mode 100644 index 000000000..e0f021ce2 --- /dev/null +++ b/commons/src/dtos/frontend-config/auth-provider-without-custom-name.dto.ts @@ -0,0 +1,19 @@ +/* + * SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ +import { z } from 'zod' +import { ProviderType } from '../auth/index.js' + +export const AuthProviderWithoutCustomNameSchema = z + .object({ + type: z + .literal(ProviderType.LOCAL) + .describe('The type of the auth provider'), + }) + .describe('Represents the local authentication provider') + +export type AuthProviderWithoutCustomNameDto = z.infer< + typeof AuthProviderWithoutCustomNameSchema +> diff --git a/commons/src/dtos/frontend-config/auth-provider.dto.ts b/commons/src/dtos/frontend-config/auth-provider.dto.ts new file mode 100644 index 000000000..1ded9bf3a --- /dev/null +++ b/commons/src/dtos/frontend-config/auth-provider.dto.ts @@ -0,0 +1,18 @@ +/* + * SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { AuthProviderWithCustomNameSchema } from './auth-provider-with-custom-name.dto.js' +import { AuthProviderWithoutCustomNameSchema } from './auth-provider-without-custom-name.dto.js' +import { z } from 'zod' + +export const AuthProviderSchema = z + .union([ + AuthProviderWithoutCustomNameSchema, + AuthProviderWithCustomNameSchema, + ]) + .describe('A general type for all auth providers') + +export type AuthProviderDto = z.infer diff --git a/commons/src/dtos/frontend-config/branding.dto.ts b/commons/src/dtos/frontend-config/branding.dto.ts new file mode 100644 index 000000000..9d43692ee --- /dev/null +++ b/commons/src/dtos/frontend-config/branding.dto.ts @@ -0,0 +1,23 @@ +/* + * SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { z } from 'zod' + +export const BrandingSchema = z + .object({ + name: z + .string() + .nullable() + .describe('The name to be displayed next to the HedgeDoc logo'), + logo: z + .string() + .url() + .nullable() + .describe('URL to the logo to be displayed next to the HedgeDoc logo'), + }) + .describe('The configuration for branding of the HedgeDoc instance.') + +export type BrandingDto = z.infer diff --git a/commons/src/dtos/frontend-config/frontend-config.dto.ts b/commons/src/dtos/frontend-config/frontend-config.dto.ts new file mode 100644 index 000000000..9d42eed29 --- /dev/null +++ b/commons/src/dtos/frontend-config/frontend-config.dto.ts @@ -0,0 +1,53 @@ +/* + * SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { z } from 'zod' +import { GuestAccess } from '../permissions/index.js' +import { ServerVersionSchema } from '../monitoring/index.js' +import { BrandingSchema } from './branding.dto.js' +import { SpecialUrlSchema } from './special-urls.dto.js' +import { AuthProviderSchema } from './auth-provider.dto.js' + +export const FrontendConfigSchema = z + .object({ + guestAccess: z + .nativeEnum(GuestAccess) + .describe('Maximum access level for guest users'), + allowRegister: z + .boolean() + .describe('Are users allowed to register on this instance?'), + allowProfileEdits: z + .boolean() + .describe('Are users allowed to edit their profile information?'), + allowChooseUsername: z + .boolean() + .describe( + 'Are users allowed to choose their username when signing up via OIDC?', + ), + authProviders: z + .array(AuthProviderSchema) + .describe( + 'Which auth providers are enabled and how are they configured?', + ), + branding: BrandingSchema.describe('Individual branding information'), + useImageProxy: z.boolean().describe('Is an image proxy enabled?'), + specialUrls: SpecialUrlSchema.describe('Links to some special pages'), + version: ServerVersionSchema.describe('The version of HedgeDoc'), + plantUmlServer: z + .string() + .url() + .nullable() + .describe('The PlantUML server that should be used to render.'), + maxDocumentLength: z + .number() + .positive() + .describe('The maximal length of each document'), + }) + .describe( + 'Config properties that are received by the frontend to adjust its own behaviour', + ) + +export type FrontendConfigDto = z.infer diff --git a/commons/src/dtos/frontend-config/index.ts b/commons/src/dtos/frontend-config/index.ts new file mode 100644 index 000000000..a0bc31b71 --- /dev/null +++ b/commons/src/dtos/frontend-config/index.ts @@ -0,0 +1,11 @@ +/* + * SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ +export * from './auth-provider.dto.js' +export * from './auth-provider-with-custom-name.dto.js' +export * from './auth-provider-without-custom-name.dto.js' +export * from './branding.dto.js' +export * from './frontend-config.dto.js' +export * from './special-urls.dto.js' diff --git a/commons/src/dtos/frontend-config/special-urls.dto.ts b/commons/src/dtos/frontend-config/special-urls.dto.ts new file mode 100644 index 000000000..650d9d685 --- /dev/null +++ b/commons/src/dtos/frontend-config/special-urls.dto.ts @@ -0,0 +1,28 @@ +/* + * SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ +import { z } from 'zod' + +export const SpecialUrlSchema = z + .object({ + privacy: z + .string() + .url() + .nullable() + .describe('A link to the privacy notice'), + termsOfUse: z + .string() + .url() + .nullable() + .describe('A link to the privacy notice'), + imprint: z + .string() + .url() + .nullable() + .describe('A link to the imprint notice'), + }) + .describe('The special urls an HedgeDoc instance can link to.') + +export type SpecialUrlDto = z.infer diff --git a/commons/src/dtos/group/group-info.dto.ts b/commons/src/dtos/group/group-info.dto.ts new file mode 100644 index 000000000..c5b41d84d --- /dev/null +++ b/commons/src/dtos/group/group-info.dto.ts @@ -0,0 +1,20 @@ +/* + * SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ +import { z } from 'zod' + +export const GroupInfoSchema = z + .object({ + name: z.string().describe('Name of the group'), + displayName: z + .string() + .describe( + 'Display name of this group. This is used in the UI, when the group is mentioned.', + ), + special: z.boolean().describe('Is this group special?'), + }) + .describe('DTO that contains the information about a group.') + +export type GroupInfoDto = z.infer diff --git a/commons/src/dtos/group/index.ts b/commons/src/dtos/group/index.ts new file mode 100644 index 000000000..c07456261 --- /dev/null +++ b/commons/src/dtos/group/index.ts @@ -0,0 +1,7 @@ +/* + * SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ +export * from './group-info.dto.js' +export * from './special-group.enum.js' diff --git a/commons/src/dtos/group/special-group.enum.ts b/commons/src/dtos/group/special-group.enum.ts new file mode 100644 index 000000000..1fd26d642 --- /dev/null +++ b/commons/src/dtos/group/special-group.enum.ts @@ -0,0 +1,10 @@ +/* + * SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export enum SpecialGroup { + EVERYONE = '_EVERYONE', + LOGGED_IN = '_LOGGED_IN', +} diff --git a/commons/src/dtos/index.ts b/commons/src/dtos/index.ts new file mode 100644 index 000000000..fb5856580 --- /dev/null +++ b/commons/src/dtos/index.ts @@ -0,0 +1,17 @@ +/* + * SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ +export * from './alias/index.js' +export * from './api-token/index.js' +export * from './auth/index.js' +export * from './edit/index.js' +export * from './frontend-config/index.js' +export * from './group/index.js' +export * from './media/index.js' +export * from './monitoring/index.js' +export * from './note/index.js' +export * from './permissions/index.js' +export * from './revision/index.js' +export * from './user/index.js' diff --git a/commons/src/dtos/media/index.ts b/commons/src/dtos/media/index.ts new file mode 100644 index 000000000..5a3c0bc50 --- /dev/null +++ b/commons/src/dtos/media/index.ts @@ -0,0 +1,6 @@ +/* + * SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ +export * from './media-upload.dto.js' diff --git a/commons/src/dtos/media/media-upload.dto.ts b/commons/src/dtos/media/media-upload.dto.ts new file mode 100644 index 000000000..37dedf526 --- /dev/null +++ b/commons/src/dtos/media/media-upload.dto.ts @@ -0,0 +1,28 @@ +/* + * SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { z } from 'zod' + +export const MediaUploadSchema = z + .object({ + uuid: z.string().uuid().describe('The uuid of the media file'), + fileName: z.string().describe('The original filename of the media upload'), + noteId: z + .string() + .nullable() + .describe('The note id to which the uploaded file is linked to'), + createdAt: z + .string() + .datetime() + .describe('The dater when the upload was created'), + username: z + .string() + .nullable() + .describe('The username which uploaded the file'), + }) + .describe('Metadata for an uploaded file') + +export type MediaUploadDto = z.infer diff --git a/commons/src/dtos/monitoring/index.ts b/commons/src/dtos/monitoring/index.ts new file mode 100644 index 000000000..549db7b1b --- /dev/null +++ b/commons/src/dtos/monitoring/index.ts @@ -0,0 +1,7 @@ +/* + * SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ +export * from './server-status.dto.js' +export * from './server-version.dto.js' diff --git a/commons/src/dtos/monitoring/server-status.dto.ts b/commons/src/dtos/monitoring/server-status.dto.ts new file mode 100644 index 000000000..934b62d39 --- /dev/null +++ b/commons/src/dtos/monitoring/server-status.dto.ts @@ -0,0 +1,62 @@ +/* + * SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { z } from 'zod' +import { ServerVersionSchema } from './server-version.dto.js' + +export const ServerStatusSchema = z + .object({ + serverVersion: ServerVersionSchema, + onlineNotes: z + .number() + .positive() + .describe('Number of notes that are currently being worked on'), + onlineUsers: z + .number() + .positive() + .describe('Number of user that are currently working on notes'), + distinctOnlineUsers: z + .number() + .positive() + .describe( + 'Number of user that are currently working on notes. Each user only counts only once.', + ), + notesCount: z + .number() + .positive() + .describe('Number of notes on the instance'), + registeredUsers: z + .number() + .positive() + .describe('Number of user that are currently registered'), + onlineRegisteredUsers: z + .number() + .positive() + .describe('Number of user that are currently registered and online'), + distinctOnlineRegisteredUsers: z + .number() + .positive() + .describe( + 'Number of user that are currently registered and online. Each user only counts only once.', + ), + isConnectionBusy: z + .boolean() + .describe('If the connection is currently busy'), + connectionSocketQueueLength: z + .number() + .positive() + .describe('Number of connections in the queue'), + isDisconnectBusy: z + .boolean() + .describe('If the connection is currently busy'), + disconnectSocketQueueLength: z + .number() + .positive() + .describe('Number of disconnections in the queue'), + }) + .describe('The server status of the HedgeDoc instance.') + +export type ServerStatusDto = z.infer diff --git a/commons/src/dtos/monitoring/server-version.dto.ts b/commons/src/dtos/monitoring/server-version.dto.ts new file mode 100644 index 000000000..c2bddb696 --- /dev/null +++ b/commons/src/dtos/monitoring/server-version.dto.ts @@ -0,0 +1,22 @@ +/* + * SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ +import { z } from 'zod' + +export const ServerVersionSchema = z + .object({ + major: z.number().positive().describe('The major version of the server'), + minor: z.number().positive().describe('The minor version of the server'), + patch: z.number().positive().describe('The patch version of the server'), + preRelease: z + .string() + .optional() + .describe('The pre release text of the server'), + commit: z.string().optional().describe('The commit of the server'), + fullString: z.string().describe('The full version string of the server'), + }) + .describe('The version of the HedgeDoc server.') + +export type ServerVersionDto = z.infer diff --git a/commons/src/dtos/note/index.ts b/commons/src/dtos/note/index.ts new file mode 100644 index 000000000..3b5274426 --- /dev/null +++ b/commons/src/dtos/note/index.ts @@ -0,0 +1,9 @@ +/* + * SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ +export * from './note.dto.js' +export * from './note.media-deletion.dto.js' +export * from './note-metadata.dto.js' +export * from './note-metadata-update.dto.js' diff --git a/commons/src/dtos/note/note-metadata-update.dto.ts b/commons/src/dtos/note/note-metadata-update.dto.ts new file mode 100644 index 000000000..023afe76b --- /dev/null +++ b/commons/src/dtos/note/note-metadata-update.dto.ts @@ -0,0 +1,24 @@ +/* + * SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ +import { z } from 'zod' + +export const NoteMetadataUpdateSchema = z + .object({ + title: z + .string() + .describe( + 'The new title of the note. Can not contain any markup and might be empty', + ), + description: z + .string() + .describe( + 'The new description of the note. Can not contain any markup but might be empty.', + ), + tags: z.array(z.string()).describe('The new tags for this note.'), + }) + .describe('DTO for updating the note metadata') + +export type NoteMetadataUpdate = z.infer diff --git a/commons/src/dtos/note/note-metadata.dto.ts b/commons/src/dtos/note/note-metadata.dto.ts new file mode 100644 index 000000000..c743c615b --- /dev/null +++ b/commons/src/dtos/note/note-metadata.dto.ts @@ -0,0 +1,56 @@ +/* + * SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { z } from 'zod' +import { AliasSchema } from '../alias/index.js' +import { NotePermissionsSchema } from '../permissions/index.js' + +export const NoteMetadataSchema = z + .object({ + id: z.string().describe('The id of the note'), + aliases: z.array(AliasSchema).describe('All aliases of the note'), + primaryAddress: z + .string() + .describe( + 'The primary address/alias of the note. If at least one alias is set, this is the primary alias.', + ), + title: z + .string() + .describe( + 'The title of the note. Does not contain any markup but might be empty.', + ), + description: z + .string() + .describe( + 'The description of the note. Does not contain any markup but might be empty.', + ), + tags: z.array(z.string()).describe('List of tags assigned to this note'), + version: z + .number() + .describe('The HedgeDoc version this note was created in'), + updatedAt: z + .string() + .datetime() + .describe('The timestamp when the note was last updated'), + updateUsername: z + .string() + .nullable() + .describe('The user that last updated the note'), + viewCount: z + .number() + .describe('Counts how many times the note has been viewed'), + createdAt: z + .string() + .datetime() + .describe('Timestamp when the note was created'), + editedBy: z.array(z.string()).describe('List of users who edited the note'), + permissions: NotePermissionsSchema.describe( + 'The permissions of the current note', + ), + }) + .describe('The metadata of a note') + +export type NoteMetadataDto = z.infer diff --git a/commons/src/dtos/note/note.dto.ts b/commons/src/dtos/note/note.dto.ts new file mode 100644 index 000000000..7e6cc7757 --- /dev/null +++ b/commons/src/dtos/note/note.dto.ts @@ -0,0 +1,19 @@ +/* + * SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { z } from 'zod' +import { EditSchema } from '../edit/edit.dto.js' +import { NoteMetadataSchema } from './note-metadata.dto.js' + +export const NoteSchema = z + .object({ + content: z.string().describe('The markdown content of the note'), + metadata: NoteMetadataSchema, + editedByAtPosition: z.array(EditSchema).describe('The edit information '), + }) + .describe('DTO representing a note') + +export type NoteDto = z.infer diff --git a/commons/src/dtos/note/note.media-deletion.dto.ts b/commons/src/dtos/note/note.media-deletion.dto.ts new file mode 100644 index 000000000..df5c034e0 --- /dev/null +++ b/commons/src/dtos/note/note.media-deletion.dto.ts @@ -0,0 +1,21 @@ +/* + * SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { z } from 'zod' + +export const NoteMediaDeletionSchema = z + .object({ + keepMedia: z + .boolean() + .describe( + 'Indicates whether existing media uploads for the note should be kept', + ), + }) + .describe( + 'DTO for deleting a note with the option to remove associated uploads as well', + ) + +export type NoteMediaDeletionDto = z.infer diff --git a/commons/src/dtos/permissions/change-note-owner.dto.ts b/commons/src/dtos/permissions/change-note-owner.dto.ts new file mode 100644 index 000000000..0287eb50b --- /dev/null +++ b/commons/src/dtos/permissions/change-note-owner.dto.ts @@ -0,0 +1,15 @@ +/* + * SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { z } from 'zod' + +export const ChangeNoteOwnerSchema = z + .object({ + owner: z.string().describe('The username of the new owner.'), + }) + .describe('DTO to change the owner of a note.') + +export type ChangeNoteOwnerDto = z.infer diff --git a/backend/src/config/guest_access.enum.ts b/commons/src/dtos/permissions/guest-access.enum.ts similarity index 56% rename from backend/src/config/guest_access.enum.ts rename to commons/src/dtos/permissions/guest-access.enum.ts index f1724dc25..c335a77f7 100644 --- a/backend/src/config/guest_access.enum.ts +++ b/commons/src/dtos/permissions/guest-access.enum.ts @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) + * SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file) * * SPDX-License-Identifier: AGPL-3.0-only */ @@ -11,17 +11,17 @@ export enum GuestAccess { CREATE = 'create', } -export function getGuestAccessOrdinal(guestAccess: GuestAccess): number { +export const getGuestAccessOrdinal = (guestAccess: GuestAccess): number => { switch (guestAccess) { case GuestAccess.DENY: - return 0; + return 0 case GuestAccess.READ: - return 1; + return 1 case GuestAccess.WRITE: - return 2; + return 2 case GuestAccess.CREATE: - return 3; + return 3 default: - throw Error('Unknown permission'); + throw Error('Unknown permission') } } diff --git a/commons/src/dtos/permissions/index.ts b/commons/src/dtos/permissions/index.ts new file mode 100644 index 000000000..8d2eec439 --- /dev/null +++ b/commons/src/dtos/permissions/index.ts @@ -0,0 +1,14 @@ +/* + * SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export * from './change-note-owner.dto.js' +export * from './guest-access.enum.js' +export * from './note-group-permission-entry.dto.js' +export * from './note-group-permission-update.dto.js' +export * from './note-permissions-update.dto.js' +export * from './note-permissions.dto.js' +export * from './note-user-permission-entry.dto.js' +export * from './note-user-permission-update.dto.js' diff --git a/commons/src/dtos/permissions/note-group-permission-entry.dto.ts b/commons/src/dtos/permissions/note-group-permission-entry.dto.ts new file mode 100644 index 000000000..05f3b4d8b --- /dev/null +++ b/commons/src/dtos/permissions/note-group-permission-entry.dto.ts @@ -0,0 +1,17 @@ +/* + * SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ +import { z } from 'zod' + +export const NoteGroupPermissionEntrySchema = z + .object({ + groupName: z.string().describe('The name of the group'), + canEdit: z.boolean().describe('If the group can edit or only read'), + }) + .describe('DTO for the permission a group has.') + +export type NoteGroupPermissionEntryDto = z.infer< + typeof NoteGroupPermissionEntrySchema +> diff --git a/commons/src/dtos/permissions/note-group-permission-update.dto.ts b/commons/src/dtos/permissions/note-group-permission-update.dto.ts new file mode 100644 index 000000000..142726a5c --- /dev/null +++ b/commons/src/dtos/permissions/note-group-permission-update.dto.ts @@ -0,0 +1,18 @@ +/* + * SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { z } from 'zod' + +export const NoteGroupPermissionUpdateSchema = z + .object({ + groupName: z.string().describe('The name of the group'), + canEdit: z.boolean().describe('If the group can edit or only read'), + }) + .describe('DTO to update the permission of a group.') + +export type NoteGroupPermissionUpdateDto = z.infer< + typeof NoteGroupPermissionUpdateSchema +> diff --git a/commons/src/dtos/permissions/note-permissions-update.dto.ts b/commons/src/dtos/permissions/note-permissions-update.dto.ts new file mode 100644 index 000000000..974c3f3b7 --- /dev/null +++ b/commons/src/dtos/permissions/note-permissions-update.dto.ts @@ -0,0 +1,23 @@ +/* + * SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ +import { z } from 'zod' +import { NoteUserPermissionUpdateSchema } from './note-user-permission-update.dto.js' +import { NoteGroupPermissionUpdateSchema } from './note-group-permission-update.dto.js' + +export const NotePermissionsUpdateSchema = z + .object({ + sharedToUsers: z + .array(NoteUserPermissionUpdateSchema) + .describe('List of users the note is shared with'), + sharedToGroups: z + .array(NoteGroupPermissionUpdateSchema) + .describe('List of groups that the note is shared with'), + }) + .describe('DTO to update the permissions of a note.') + +export type NotePermissionsUpdateDto = z.infer< + typeof NotePermissionsUpdateSchema +> diff --git a/commons/src/dtos/permissions/note-permissions.dto.ts b/commons/src/dtos/permissions/note-permissions.dto.ts new file mode 100644 index 000000000..fbad89d59 --- /dev/null +++ b/commons/src/dtos/permissions/note-permissions.dto.ts @@ -0,0 +1,23 @@ +/* + * SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { z } from 'zod' +import { NoteUserPermissionEntrySchema } from './note-user-permission-entry.dto.js' +import { NoteGroupPermissionEntrySchema } from './note-group-permission-entry.dto.js' + +export const NotePermissionsSchema = z + .object({ + owner: z.string().nullable().describe('Username of the owner of the note'), + sharedToUsers: z + .array(NoteUserPermissionEntrySchema) + .describe('List of users the note is shared with'), + sharedToGroups: z + .array(NoteGroupPermissionEntrySchema) + .describe('List of groups that the note is shared with'), + }) + .describe('Represents the permissions of a note') + +export type NotePermissionsDto = z.infer diff --git a/commons/src/dtos/permissions/note-user-permission-entry.dto.ts b/commons/src/dtos/permissions/note-user-permission-entry.dto.ts new file mode 100644 index 000000000..1bdf0c330 --- /dev/null +++ b/commons/src/dtos/permissions/note-user-permission-entry.dto.ts @@ -0,0 +1,18 @@ +/* + * SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { z } from 'zod' + +export const NoteUserPermissionEntrySchema = z + .object({ + username: z.string().describe('The name of the user'), + canEdit: z.boolean().describe('If the group can edit or only read'), + }) + .describe('DTO for the permission a group has.') + +export type NoteUserPermissionEntryDto = z.infer< + typeof NoteUserPermissionEntrySchema +> diff --git a/commons/src/dtos/permissions/note-user-permission-update.dto.ts b/commons/src/dtos/permissions/note-user-permission-update.dto.ts new file mode 100644 index 000000000..c96d7c0aa --- /dev/null +++ b/commons/src/dtos/permissions/note-user-permission-update.dto.ts @@ -0,0 +1,18 @@ +/* + * SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { z } from 'zod' + +export const NoteUserPermissionUpdateSchema = z + .object({ + username: z.string().toLowerCase().describe('The name of the user'), + canEdit: z.boolean().describe('If the group can edit or only read'), + }) + .describe('DTO to update the permission of a user.') + +export type NoteUserPermissionUpdateDto = z.infer< + typeof NoteUserPermissionUpdateSchema +> diff --git a/commons/src/dtos/revision/index.ts b/commons/src/dtos/revision/index.ts new file mode 100644 index 000000000..39174a8ba --- /dev/null +++ b/commons/src/dtos/revision/index.ts @@ -0,0 +1,8 @@ +/* + * SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export * from './revision.dto.js' +export * from './revision-metadata.dto.js' diff --git a/commons/src/dtos/revision/revision-metadata.dto.ts b/commons/src/dtos/revision/revision-metadata.dto.ts new file mode 100644 index 000000000..6383068fd --- /dev/null +++ b/commons/src/dtos/revision/revision-metadata.dto.ts @@ -0,0 +1,41 @@ +/* + * SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ +import { z } from 'zod' + +export const RevisionMetadataSchema = z + .object({ + id: z.number().describe('The id of the revision.'), + createdAt: z.string().datetime().describe('When the revision was created.'), + length: z + .number() + .positive() + .describe('The length of the content of the revision.'), + authorUsernames: z + .array(z.string().toLowerCase()) + .describe( + 'A list of all usernames of the users that worked on the revision.', + ), + anonymousAuthorCount: z + .number() + .positive() + .describe('Number of anonymous users that worked on the revision.'), + title: z + .string() + .describe( + 'The title of the revision. Does not contain any markup but might be empty.', + ), + description: z + .string() + .describe( + 'The description of the revision. Does not contain any markup but might be empty.', + ), + tags: z + .array(z.string()) + .describe('List of tags assigned to this revision'), + }) + .describe('DTO that describes the metadata of a revision.') + +export type RevisionMetadataDto = z.infer diff --git a/commons/src/dtos/revision/revision.dto.ts b/commons/src/dtos/revision/revision.dto.ts new file mode 100644 index 000000000..0ae066fab --- /dev/null +++ b/commons/src/dtos/revision/revision.dto.ts @@ -0,0 +1,23 @@ +/* + * SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { z } from 'zod' +import { EditSchema } from '../edit/edit.dto.js' +import { RevisionMetadataSchema } from './revision-metadata.dto.js' + +export const RevisionSchema = RevisionMetadataSchema.merge( + z.object({ + content: z.string().describe('The content of the revision'), + patch: z.string().describe('The patch or diff to the previous revision'), + edits: z + .array(EditSchema) + .describe('A list of users, who created this revision'), + }), +).describe( + 'A revision is the state of a note content at a specific time. This is used to go back to previous version of a note.', +) + +export type RevisionDto = z.infer diff --git a/commons/src/dtos/user/full-user-info-with-id.dto.ts b/commons/src/dtos/user/full-user-info-with-id.dto.ts new file mode 100644 index 000000000..685d737ea --- /dev/null +++ b/commons/src/dtos/user/full-user-info-with-id.dto.ts @@ -0,0 +1,17 @@ +/* + * SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ +import { z } from 'zod' +import { FullUserInfoSchema } from './full-user-info.dto.js' + +export const FullUserInfoWithIdSchema = FullUserInfoSchema.merge( + z.object({ + id: z.string().describe('The id from the LDAP server'), + }), +).describe( + 'The full user information with id is only used during the LDAP login process', +) + +export type FullUserInfoWithIdDto = z.infer diff --git a/commons/src/dtos/user/full-user-info.dto.ts b/commons/src/dtos/user/full-user-info.dto.ts new file mode 100644 index 000000000..05b209def --- /dev/null +++ b/commons/src/dtos/user/full-user-info.dto.ts @@ -0,0 +1,21 @@ +/* + * SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ +import { z } from 'zod' +import { UserInfoSchema } from './user-info.dto.js' + +export const FullUserInfoSchema = UserInfoSchema.merge( + z.object({ + email: z + .string() + .email() + .nullable() + .describe('The email address of the user if known'), + }), +).describe( + 'The full user information is only presented to the logged in user itself. For privacy reasons the email address is only here', +) + +export type FullUserInfoDto = z.infer diff --git a/commons/src/dtos/user/index.ts b/commons/src/dtos/user/index.ts new file mode 100644 index 000000000..99f8e5a8a --- /dev/null +++ b/commons/src/dtos/user/index.ts @@ -0,0 +1,10 @@ +/* + * SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ +export * from './full-user-info.dto.js' +export * from './full-user-info-with-id.dto.js' +export * from './login-user-info.dto.js' +export * from './update-user-info.dto.js' +export * from './user-info.dto.js' diff --git a/commons/src/dtos/user/login-user-info.dto.ts b/commons/src/dtos/user/login-user-info.dto.ts new file mode 100644 index 000000000..093e0d8c3 --- /dev/null +++ b/commons/src/dtos/user/login-user-info.dto.ts @@ -0,0 +1,21 @@ +/* + * SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { z } from 'zod' +import { ProviderType } from '../auth/index.js' +import { FullUserInfoSchema } from './full-user-info.dto.js' + +export const LoginUserInfoSchema = FullUserInfoSchema.merge( + z.object({ + authProvider: z + .nativeEnum(ProviderType) + .describe('The type of login provider used for the current session'), + }), +).describe( + 'Information about the user and their auth method for the current session', +) + +export type LoginUserInfoDto = z.infer diff --git a/commons/src/dtos/user/update-user-info.dto.ts b/commons/src/dtos/user/update-user-info.dto.ts new file mode 100644 index 000000000..c0cc6cc18 --- /dev/null +++ b/commons/src/dtos/user/update-user-info.dto.ts @@ -0,0 +1,19 @@ +/* + * SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { z } from 'zod' + +export const UpdateUserInfoSchema = z + .object({ + displayName: z + .string() + .nullable() + .describe('The new display name of the user.'), + email: z.string().email().nullable().describe('The new email of the user.'), + }) + .describe('The update of a user profile.') + +export type UpdateUserInfoDto = z.infer diff --git a/commons/src/dtos/user/user-info.dto.ts b/commons/src/dtos/user/user-info.dto.ts new file mode 100644 index 000000000..f385b53c0 --- /dev/null +++ b/commons/src/dtos/user/user-info.dto.ts @@ -0,0 +1,20 @@ +/* + * SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ +import { z } from 'zod' + +export const UserInfoSchema = z + .object({ + username: z.string().describe("The user's username"), + displayName: z.string().describe('The display name of the user'), + photoUrl: z + .string() + .url() + .nullable() + .describe('The URL to the profile picture of the user'), + }) + .describe('Represents the public information about a user') + +export type UserInfoDto = z.infer diff --git a/commons/src/index.ts b/commons/src/index.ts index 58223ea42..7bcd0a023 100644 --- a/commons/src/index.ts +++ b/commons/src/index.ts @@ -1,9 +1,9 @@ /* - * 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 */ - +export * from './dtos/index.js' export * from './frontmatter-extractor/index.js' export * from './message-transporters/index.js' export * from './note-frontmatter/index.js' diff --git a/commons/src/permissions/index.ts b/commons/src/permissions/index.ts index c2b4c88db..84a75adcb 100644 --- a/commons/src/permissions/index.ts +++ b/commons/src/permissions/index.ts @@ -1,8 +1,7 @@ /* - * 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 */ export * from './permissions.js' -export * from './permissions.types.js' diff --git a/commons/src/permissions/permissions.spec.ts b/commons/src/permissions/permissions.spec.ts index 9358bd448..2047b2724 100644 --- a/commons/src/permissions/permissions.spec.ts +++ b/commons/src/permissions/permissions.spec.ts @@ -4,11 +4,11 @@ * SPDX-License-Identifier: AGPL-3.0-only */ import { userCanEdit, userIsOwner } from './permissions.js' -import { NotePermissions, SpecialGroup } from './permissions.types.js' import { describe, expect, it } from '@jest/globals' +import { NotePermissionsDto, SpecialGroup } from '../dtos/index.js' describe('Permissions', () => { - const testPermissions: NotePermissions = { + const testPermissions: NotePermissionsDto = { owner: 'owner', sharedToUsers: [ { diff --git a/commons/src/permissions/permissions.ts b/commons/src/permissions/permissions.ts index 0e8f2b252..23544e9ec 100644 --- a/commons/src/permissions/permissions.ts +++ b/commons/src/permissions/permissions.ts @@ -3,7 +3,7 @@ * * SPDX-License-Identifier: AGPL-3.0-only */ -import { NotePermissions, SpecialGroup } from './permissions.types.js' +import { NotePermissionsDto, SpecialGroup } from '../dtos/index.js' /** * Checks if the given user is the owner of a note. @@ -13,7 +13,7 @@ import { NotePermissions, SpecialGroup } from './permissions.types.js' * @return True if the user is the owner of the note */ export const userIsOwner = ( - permissions: NotePermissions, + permissions: NotePermissionsDto, user?: string, ): boolean => { return !!user && permissions.owner === user @@ -27,7 +27,7 @@ export const userIsOwner = ( * @return True if the user has the permission to edit the note */ export const userCanEdit = ( - permissions: NotePermissions, + permissions: NotePermissionsDto, user?: string, ): boolean => { const isOwner = userIsOwner(permissions, user) diff --git a/commons/src/permissions/permissions.types.ts b/commons/src/permissions/permissions.types.ts deleted file mode 100644 index 8a0d6834b..000000000 --- a/commons/src/permissions/permissions.types.ts +++ /dev/null @@ -1,31 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file) - * - * SPDX-License-Identifier: AGPL-3.0-only - */ - -export interface NotePermissions { - owner: string | null - sharedToUsers: NoteUserPermissionEntry[] - sharedToGroups: NoteGroupPermissionEntry[] -} - -export interface NoteUserPermissionEntry { - username: string - canEdit: boolean -} - -export interface NoteGroupPermissionEntry { - groupName: string - canEdit: boolean -} -export enum AccessLevel { - NONE, - READ_ONLY, - WRITEABLE, -} - -export enum SpecialGroup { - EVERYONE = '_EVERYONE', - LOGGED_IN = '_LOGGED_IN', -}