diff --git a/backend/src/api-token/api-token.dto.ts b/backend/src/api-token/api-token.dto.ts deleted file mode 100644 index 64189fad8..000000000 --- a/backend/src/api-token/api-token.dto.ts +++ /dev/null @@ -1,45 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) - * - * SPDX-License-Identifier: AGPL-3.0-only - */ -import { Type } from 'class-transformer'; -import { IsDate, IsNumber, IsOptional, IsString } from 'class-validator'; - -import { BaseDto } from '../utils/base.dto.'; -import { TimestampMillis } from '../utils/timestamp'; - -export class ApiTokenDto extends BaseDto { - @IsString() - label: string; - - @IsString() - keyId: string; - - @IsDate() - @Type(() => Date) - createdAt: Date; - - @IsDate() - @Type(() => Date) - validUntil: Date; - - @IsDate() - @Type(() => Date) - @IsOptional() - lastUsedAt: Date | null; -} - -export class ApiTokenWithSecretDto extends ApiTokenDto { - @IsString() - secret: string; -} - -export class ApiTokenCreateDto extends BaseDto { - @IsString() - label: string; - - @IsNumber() - @Type(() => Number) - validUntil: TimestampMillis; -} diff --git a/backend/src/api-token/api-token.service.spec.ts b/backend/src/api-token/api-token.service.spec.ts index cac6f8097..a4bea44b4 100644 --- a/backend/src/api-token/api-token.service.spec.ts +++ b/backend/src/api-token/api-token.service.spec.ts @@ -1,9 +1,8 @@ /* - * 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 */ -import { DeepPartial } from '@hedgedoc/commons'; import { ConfigModule } from '@nestjs/config'; import { Test, TestingModule } from '@nestjs/testing'; import { getRepositoryToken } from '@nestjs/typeorm'; @@ -150,7 +149,7 @@ describe('ApiTokenService', () => { const [accessToken, secret] = service.createToken( user, 'TestToken', - undefined, + null, ); expect(() => @@ -158,7 +157,7 @@ describe('ApiTokenService', () => { ).not.toThrow(); }); it('AuthToken has wrong hash', () => { - const [accessToken] = service.createToken(user, 'TestToken', undefined); + const [accessToken] = service.createToken(user, 'TestToken', null); expect(() => service.checkToken('secret', accessToken as ApiToken), ).toThrow(TokenNotValidError); @@ -167,7 +166,7 @@ describe('ApiTokenService', () => { const [accessToken, secret] = service.createToken( user, 'Test', - 1549312452000, + new Date(1549312452000), ); expect(() => service.checkToken(secret, accessToken as ApiToken)).toThrow( TokenNotValidError, @@ -295,18 +294,16 @@ describe('ApiTokenService', () => { jest .spyOn(apiTokenRepo, 'save') .mockImplementationOnce( - async ( - apiTokenSaved: DeepPartial<ApiToken>, - _, - ): Promise<ApiToken> => { + async (apiTokenSaved: ApiToken, _): Promise<ApiToken> => { expect(apiTokenSaved.lastUsedAt).toBeNull(); + apiTokenSaved.createdAt = new Date(1); return apiTokenSaved; }, ); - const token = await service.addToken(user, identifier, 0); + const token = await service.addToken(user, identifier, new Date(0)); expect(token.label).toEqual(identifier); expect( - token.validUntil.getTime() - + new Date(token.validUntil).getTime() - (new Date().getTime() + 2 * 365 * 24 * 60 * 60 * 1000), ).toBeLessThanOrEqual(10000); expect(token.lastUsedAt).toBeNull(); @@ -317,18 +314,17 @@ describe('ApiTokenService', () => { jest .spyOn(apiTokenRepo, 'save') .mockImplementationOnce( - async ( - apiTokenSaved: DeepPartial<ApiToken>, - _, - ): Promise<ApiToken> => { + async (apiTokenSaved: ApiToken, _): Promise<ApiToken> => { expect(apiTokenSaved.lastUsedAt).toBeNull(); + apiTokenSaved.createdAt = new Date(1); return apiTokenSaved; }, ); - const validUntil = new Date().getTime() + 30000; + const validUntil = new Date(); + validUntil.setTime(validUntil.getTime() + 30000); const token = await service.addToken(user, identifier, validUntil); expect(token.label).toEqual(identifier); - expect(token.validUntil.getTime()).toEqual(validUntil); + expect(new Date(token.validUntil)).toEqual(validUntil); expect(token.lastUsedAt).toBeNull(); expect(token.secret.startsWith('hd2.' + token.keyId)).toBeTruthy(); }); @@ -340,7 +336,8 @@ describe('ApiTokenService', () => { inValidToken.length = 201; return inValidToken; }); - const validUntil = new Date().getTime() + 30000; + const validUntil = new Date(); + validUntil.setTime(validUntil.getTime() + 30000); await expect( service.addToken(user, identifier, validUntil), ).rejects.toThrow(TooManyTokensError); @@ -400,17 +397,17 @@ describe('ApiTokenService', () => { expect(tokenDto.keyId).toEqual(apiToken.keyId); expect(tokenDto.lastUsedAt).toBeNull(); expect(tokenDto.label).toEqual(apiToken.label); - expect(tokenDto.validUntil.getTime()).toEqual( + expect(new Date(tokenDto.validUntil).getTime()).toEqual( apiToken.validUntil.getTime(), ); - expect(tokenDto.createdAt.getTime()).toEqual( + expect(new Date(tokenDto.createdAt).getTime()).toEqual( apiToken.createdAt.getTime(), ); }); it('should have lastUsedAt', () => { apiToken.lastUsedAt = new Date(); const tokenDto = service.toAuthTokenDto(apiToken); - expect(tokenDto.lastUsedAt).toEqual(apiToken.lastUsedAt); + expect(tokenDto.lastUsedAt).toEqual(apiToken.lastUsedAt.toISOString()); }); }); }); diff --git a/backend/src/api-token/api-token.service.ts b/backend/src/api-token/api-token.service.ts index adc1b9681..36fbb0f94 100644 --- a/backend/src/api-token/api-token.service.ts +++ b/backend/src/api-token/api-token.service.ts @@ -1,8 +1,9 @@ /* - * 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 */ +import { ApiTokenDto, ApiTokenWithSecretDto } from '@hedgedoc/commons'; import { Injectable } from '@nestjs/common'; import { Cron, Timeout } from '@nestjs/schedule'; import { InjectRepository } from '@nestjs/typeorm'; @@ -17,8 +18,6 @@ import { import { ConsoleLoggerService } from '../logger/console-logger.service'; import { User } from '../users/user.entity'; import { bufferToBase64Url } from '../utils/password'; -import { TimestampMillis } from '../utils/timestamp'; -import { ApiTokenDto, ApiTokenWithSecretDto } from './api-token.dto'; import { ApiToken } from './api-token.entity'; export const AUTH_TOKEN_PREFIX = 'hd2'; @@ -54,19 +53,19 @@ export class ApiTokenService { createToken( user: User, identifier: string, - userDefinedValidUntil: TimestampMillis | undefined, + userDefinedValidUntil: Date | null, ): [Omit<ApiToken, 'id' | 'createdAt'>, string] { const secret = bufferToBase64Url(randomBytes(64)); const keyId = bufferToBase64Url(randomBytes(8)); // More about the choice of SHA-512 in the dev docs const accessTokenHash = createHash('sha512').update(secret).digest('hex'); // Tokens can only be valid for a maximum of 2 years - const maximumTokenValidity = - new Date().getTime() + 2 * 365 * 24 * 60 * 60 * 1000; + const maximumTokenValidity = new Date(); + maximumTokenValidity.setTime( + maximumTokenValidity.getTime() + 2 * 365 * 24 * 60 * 60 * 1000, + ); const isTokenLimitedToMaximumValidity = - !userDefinedValidUntil || - userDefinedValidUntil === 0 || - userDefinedValidUntil > maximumTokenValidity; + !userDefinedValidUntil || userDefinedValidUntil > maximumTokenValidity; const validUntil = isTokenLimitedToMaximumValidity ? maximumTokenValidity : userDefinedValidUntil; @@ -83,7 +82,7 @@ export class ApiTokenService { async addToken( user: User, identifier: string, - validUntil: TimestampMillis | undefined, + validUntil: Date | null, ): Promise<ApiTokenWithSecretDto> { user.apiTokens = this.getTokensByUser(user); @@ -176,13 +175,13 @@ export class ApiTokenService { const tokenDto: ApiTokenDto = { label: authToken.label, keyId: authToken.keyId, - createdAt: authToken.createdAt, - validUntil: authToken.validUntil, + createdAt: authToken.createdAt.toISOString(), + validUntil: authToken.validUntil.toISOString(), lastUsedAt: null, }; if (authToken.lastUsedAt) { - tokenDto.lastUsedAt = new Date(authToken.lastUsedAt); + tokenDto.lastUsedAt = new Date(authToken.lastUsedAt).toISOString(); } return tokenDto; diff --git a/backend/src/api-token/mock-api-token.guard.ts b/backend/src/api-token/mock-api-token.guard.ts index 4658603f9..68e65438b 100644 --- a/backend/src/api-token/mock-api-token.guard.ts +++ b/backend/src/api-token/mock-api-token.guard.ts @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file) + * SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file) * * SPDX-License-Identifier: AGPL-3.0-only */ @@ -23,7 +23,12 @@ export class MockApiTokenGuard { try { this.user = await this.usersService.getUserByUsername('hardcoded'); } catch (e) { - this.user = await this.usersService.createUser('hardcoded', 'Testy'); + this.user = await this.usersService.createUser( + 'hardcoded', + 'Testy', + null, + null, + ); } } req.user = this.user; diff --git a/backend/src/api/private/alias/alias.controller.ts b/backend/src/api/private/alias/alias.controller.ts index 3bf49db5f..8a3c54e34 100644 --- a/backend/src/api/private/alias/alias.controller.ts +++ b/backend/src/api/private/alias/alias.controller.ts @@ -1,8 +1,11 @@ /* - * 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 */ +import { AliasCreateDto } from '@hedgedoc/commons'; +import { AliasUpdateDto } from '@hedgedoc/commons'; +import { AliasDto } from '@hedgedoc/commons'; import { BadRequestException, Body, @@ -18,9 +21,6 @@ import { ApiTags } from '@nestjs/swagger'; import { SessionGuard } from '../../../auth/session.guard'; import { ConsoleLoggerService } from '../../../logger/console-logger.service'; -import { AliasCreateDto } from '../../../notes/alias-create.dto'; -import { AliasUpdateDto } from '../../../notes/alias-update.dto'; -import { AliasDto } from '../../../notes/alias.dto'; import { AliasService } from '../../../notes/alias.service'; import { NotesService } from '../../../notes/notes.service'; import { PermissionsService } from '../../../permissions/permissions.service'; diff --git a/backend/src/api/private/auth/auth.controller.ts b/backend/src/api/private/auth/auth.controller.ts index 945023440..87d9d0698 100644 --- a/backend/src/api/private/auth/auth.controller.ts +++ b/backend/src/api/private/auth/auth.controller.ts @@ -1,8 +1,14 @@ /* - * 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 */ +import { + FullUserInfoDto, + LogoutResponseDto, + PendingUserConfirmationDto, + ProviderType, +} from '@hedgedoc/commons'; import { BadRequestException, Body, @@ -17,11 +23,8 @@ import { ApiTags } from '@nestjs/swagger'; import { IdentityService } from '../../../auth/identity.service'; import { OidcService } from '../../../auth/oidc/oidc.service'; -import { PendingUserConfirmationDto } from '../../../auth/pending-user-confirmation.dto'; -import { ProviderType } from '../../../auth/provider-type.enum'; import { RequestWithSession, SessionGuard } from '../../../auth/session.guard'; import { ConsoleLoggerService } from '../../../logger/console-logger.service'; -import { FullUserInfoDto } from '../../../users/user-info.dto'; import { OpenApi } from '../../utils/openapi.decorator'; @ApiTags('auth') @@ -38,7 +41,7 @@ export class AuthController { @UseGuards(SessionGuard) @Delete('logout') @OpenApi(200, 400, 401) - logout(@Req() request: RequestWithSession): { redirect: string } { + logout(@Req() request: RequestWithSession): LogoutResponseDto { let logoutUrl: string | null = null; if (request.session.authProviderType === ProviderType.OIDC) { logoutUrl = this.oidcService.getLogoutUrl(request); @@ -60,9 +63,7 @@ export class AuthController { @Get('pending-user') @OpenApi(200, 400) - getPendingUserData( - @Req() request: RequestWithSession, - ): Partial<FullUserInfoDto> { + getPendingUserData(@Req() request: RequestWithSession): FullUserInfoDto { if ( !request.session.newUserData || !request.session.authProviderIdentifier || diff --git a/backend/src/api/private/auth/ldap/ldap.controller.ts b/backend/src/api/private/auth/ldap/ldap.controller.ts index e025885a8..09707ee0d 100644 --- a/backend/src/api/private/auth/ldap/ldap.controller.ts +++ b/backend/src/api/private/auth/ldap/ldap.controller.ts @@ -1,8 +1,13 @@ /* - * 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 */ +import { + LdapLoginDto, + LdapLoginResponseDto, + ProviderType, +} from '@hedgedoc/commons'; import { Body, Controller, @@ -14,14 +19,11 @@ import { import { ApiTags } from '@nestjs/swagger'; import { IdentityService } from '../../../../auth/identity.service'; -import { LdapLoginDto } from '../../../../auth/ldap/ldap-login.dto'; import { LdapService } from '../../../../auth/ldap/ldap.service'; -import { ProviderType } from '../../../../auth/provider-type.enum'; import { RequestWithSession } from '../../../../auth/session.guard'; import { NotInDBError } from '../../../../errors/errors'; import { ConsoleLoggerService } from '../../../../logger/console-logger.service'; import { UsersService } from '../../../../users/users.service'; -import { makeUsernameLowercase } from '../../../../utils/username'; import { OpenApi } from '../../../utils/openapi.decorator'; @ApiTags('auth') @@ -43,7 +45,7 @@ export class LdapController { request: RequestWithSession, @Param('ldapIdentifier') ldapIdentifier: string, @Body() loginDto: LdapLoginDto, - ): Promise<{ newUser: boolean }> { + ): Promise<LdapLoginResponseDto> { const ldapConfig = this.ldapService.getLdapConfig(ldapIdentifier); const userInfo = await this.ldapService.getUserInfoFromLdap( ldapConfig, @@ -61,7 +63,7 @@ export class LdapController { ); if (this.identityService.mayUpdateIdentity(ldapIdentifier)) { const user = await this.usersService.getUserByUsername( - makeUsernameLowercase(loginDto.username), + loginDto.username.toLowerCase(), ); await this.usersService.updateUser( user, @@ -70,7 +72,7 @@ export class LdapController { userInfo.photoUrl, ); } - request.session.username = makeUsernameLowercase(loginDto.username); + request.session.username = loginDto.username; return { newUser: false }; } catch (error) { if (error instanceof NotInDBError) { diff --git a/backend/src/api/private/auth/local/local.controller.ts b/backend/src/api/private/auth/local/local.controller.ts index 67821b974..358d85c42 100644 --- a/backend/src/api/private/auth/local/local.controller.ts +++ b/backend/src/api/private/auth/local/local.controller.ts @@ -1,8 +1,14 @@ /* - * 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 */ +import { + LoginDto, + ProviderType, + RegisterDto, + UpdatePasswordDto, +} from '@hedgedoc/commons'; import { Body, Controller, @@ -15,10 +21,6 @@ import { import { ApiTags } from '@nestjs/swagger'; import { LocalService } from '../../../../auth/local/local.service'; -import { LoginDto } from '../../../../auth/local/login.dto'; -import { RegisterDto } from '../../../../auth/local/register.dto'; -import { UpdatePasswordDto } from '../../../../auth/local/update-password.dto'; -import { ProviderType } from '../../../../auth/provider-type.enum'; import { RequestWithSession, SessionGuard, @@ -53,6 +55,8 @@ export class LocalController { const user = await this.usersService.createUser( registerDto.username, registerDto.displayName, + null, + null, ); await this.localIdentityService.createLocalIdentity( user, diff --git a/backend/src/api/private/auth/oidc/oidc.controller.ts b/backend/src/api/private/auth/oidc/oidc.controller.ts index 3e9ef6e5e..a07be8a2c 100644 --- a/backend/src/api/private/auth/oidc/oidc.controller.ts +++ b/backend/src/api/private/auth/oidc/oidc.controller.ts @@ -1,8 +1,9 @@ /* - * 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 */ +import { ProviderType } from '@hedgedoc/commons'; import { Controller, Get, @@ -17,7 +18,6 @@ import { ApiTags } from '@nestjs/swagger'; import { IdentityService } from '../../../../auth/identity.service'; import { OidcService } from '../../../../auth/oidc/oidc.service'; -import { ProviderType } from '../../../../auth/provider-type.enum'; import { RequestWithSession } from '../../../../auth/session.guard'; import { ConsoleLoggerService } from '../../../../logger/console-logger.service'; import { UsersService } from '../../../../users/users.service'; diff --git a/backend/src/api/private/config/config.controller.ts b/backend/src/api/private/config/config.controller.ts index 63bcede0b..3ebb0ca96 100644 --- a/backend/src/api/private/config/config.controller.ts +++ b/backend/src/api/private/config/config.controller.ts @@ -1,12 +1,12 @@ /* - * 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 */ +import { FrontendConfigDto } from '@hedgedoc/commons'; import { Controller, Get } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; -import { FrontendConfigDto } from '../../../frontend-config/frontend-config.dto'; import { FrontendConfigService } from '../../../frontend-config/frontend-config.service'; import { ConsoleLoggerService } from '../../../logger/console-logger.service'; import { OpenApi } from '../../utils/openapi.decorator'; diff --git a/backend/src/api/private/groups/groups.controller.ts b/backend/src/api/private/groups/groups.controller.ts index 7b8631320..98bf6fe40 100644 --- a/backend/src/api/private/groups/groups.controller.ts +++ b/backend/src/api/private/groups/groups.controller.ts @@ -1,13 +1,13 @@ /* - * 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 */ +import { GroupInfoDto } from '@hedgedoc/commons'; import { Controller, Get, Param, UseGuards } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; import { SessionGuard } from '../../../auth/session.guard'; -import { GroupInfoDto } from '../../../groups/group-info.dto'; import { GroupsService } from '../../../groups/groups.service'; import { ConsoleLoggerService } from '../../../logger/console-logger.service'; import { OpenApi } from '../../utils/openapi.decorator'; diff --git a/backend/src/api/private/me/me.controller.ts b/backend/src/api/private/me/me.controller.ts index d5660dd9f..a41243e3c 100644 --- a/backend/src/api/private/me/me.controller.ts +++ b/backend/src/api/private/me/me.controller.ts @@ -1,16 +1,19 @@ /* - * 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 */ -import { Body, Controller, Delete, Get, Post, UseGuards } from '@nestjs/common'; -import { ApiBody, ApiTags } from '@nestjs/swagger'; +import { + LoginUserInfoDto, + MediaUploadDto, + ProviderType, +} from '@hedgedoc/commons'; +import { Body, Controller, Delete, Get, Put, UseGuards } from '@nestjs/common'; +import { ApiTags } from '@nestjs/swagger'; import { SessionGuard } from '../../../auth/session.guard'; import { ConsoleLoggerService } from '../../../logger/console-logger.service'; -import { MediaUploadDto } from '../../../media/media-upload.dto'; import { MediaService } from '../../../media/media.service'; -import { UserLoginInfoDto } from '../../../users/user-info.dto'; import { User } from '../../../users/user.entity'; import { UsersService } from '../../../users/users.service'; import { OpenApi } from '../../utils/openapi.decorator'; @@ -34,9 +37,9 @@ export class MeController { @OpenApi(200) getMe( @RequestUser() user: User, - @SessionAuthProvider() authProvider: string, - ): UserLoginInfoDto { - return this.userService.toUserLoginInfoDto(user, authProvider); + @SessionAuthProvider() authProvider: ProviderType, + ): LoginUserInfoDto { + return this.userService.toLoginUserInfoDto(user, authProvider); } @Get('media') @@ -60,18 +63,9 @@ export class MeController { this.logger.debug(`Deleted ${user.username}`); } - @Post('profile') - @ApiBody({ - schema: { - type: 'object', - properties: { - displayName: { type: 'string', nullable: false }, - }, - required: ['displayName'], - }, - }) + @Put('profile') @OpenApi(200) - async updateDisplayName( + async updateProfile( @RequestUser() user: User, @Body('displayName') newDisplayName: string, ): Promise<void> { diff --git a/backend/src/api/private/media/media.controller.ts b/backend/src/api/private/media/media.controller.ts index fe624bb77..4c36da762 100644 --- a/backend/src/api/private/media/media.controller.ts +++ b/backend/src/api/private/media/media.controller.ts @@ -1,8 +1,9 @@ /* - * 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 */ +import { MediaUploadDto, MediaUploadSchema } from '@hedgedoc/commons'; import { BadRequestException, Controller, @@ -22,7 +23,6 @@ import { Response } from 'express'; import { SessionGuard } from '../../../auth/session.guard'; import { PermissionError } from '../../../errors/errors'; import { ConsoleLoggerService } from '../../../logger/console-logger.service'; -import { MediaUploadDto } from '../../../media/media-upload.dto'; import { MediaService } from '../../../media/media.service'; import { MulterFile } from '../../../media/multer-file.interface'; import { Note } from '../../../notes/note.entity'; @@ -74,7 +74,7 @@ export class MediaController { { code: 201, description: 'The file was uploaded successfully', - dto: MediaUploadDto, + schema: MediaUploadSchema, }, 400, 403, diff --git a/backend/src/api/private/notes/notes.controller.ts b/backend/src/api/private/notes/notes.controller.ts index b650d7b7a..f2f52627b 100644 --- a/backend/src/api/private/notes/notes.controller.ts +++ b/backend/src/api/private/notes/notes.controller.ts @@ -1,8 +1,22 @@ /* - * 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 */ +import { + ChangeNoteOwnerDto, + MediaUploadDto, + NoteDto, + NoteGroupPermissionEntryDto, + NoteGroupPermissionUpdateDto, + NoteMediaDeletionDto, + NoteMetadataDto, + NotePermissionsDto, + NoteUserPermissionEntryDto, + NoteUserPermissionUpdateDto, + RevisionDto, + RevisionMetadataDto, +} from '@hedgedoc/commons'; import { BadRequestException, Body, @@ -22,24 +36,16 @@ import { NotInDBError } from '../../../errors/errors'; import { GroupsService } from '../../../groups/groups.service'; import { HistoryService } from '../../../history/history.service'; import { ConsoleLoggerService } from '../../../logger/console-logger.service'; -import { MediaUploadDto } from '../../../media/media-upload.dto'; import { MediaService } from '../../../media/media.service'; -import { NoteMetadataDto } from '../../../notes/note-metadata.dto'; -import { NotePermissionsDto } from '../../../notes/note-permissions.dto'; -import { NoteDto } from '../../../notes/note.dto'; import { Note } from '../../../notes/note.entity'; -import { NoteMediaDeletionDto } from '../../../notes/note.media-deletion.dto'; import { NotesService } from '../../../notes/notes.service'; import { PermissionsGuard } from '../../../permissions/permissions.guard'; import { PermissionsService } from '../../../permissions/permissions.service'; import { RequirePermission } from '../../../permissions/require-permission.decorator'; import { RequiredPermission } from '../../../permissions/required-permission.enum'; -import { RevisionMetadataDto } from '../../../revisions/revision-metadata.dto'; -import { RevisionDto } from '../../../revisions/revision.dto'; import { RevisionsService } from '../../../revisions/revisions.service'; import { User } from '../../../users/user.entity'; import { UsersService } from '../../../users/users.service'; -import { Username } from '../../../utils/username'; import { GetNoteInterceptor } from '../../utils/get-note.interceptor'; import { MarkdownBody } from '../../utils/markdown-body.decorator'; import { OpenApi } from '../../utils/openapi.decorator'; @@ -197,15 +203,15 @@ export class NotesController { ); } - @Put(':noteIdOrAlias/metadata/permissions/users/:userName') + @Put(':noteIdOrAlias/metadata/permissions/users/:username') @OpenApi(200, 403, 404) @UseInterceptors(GetNoteInterceptor) @RequirePermission(RequiredPermission.OWNER) async setUserPermission( @RequestUser() user: User, @RequestNote() note: Note, - @Param('userName') username: Username, - @Body('canEdit') canEdit: boolean, + @Param('username') username: NoteUserPermissionUpdateDto['username'], + @Body('canEdit') canEdit: NoteUserPermissionUpdateDto['canEdit'], ): Promise<NotePermissionsDto> { const permissionUser = await this.userService.getUserByUsername(username); const returnedNote = await this.permissionService.setUserPermission( @@ -218,11 +224,11 @@ export class NotesController { @UseInterceptors(GetNoteInterceptor) @RequirePermission(RequiredPermission.OWNER) - @Delete(':noteIdOrAlias/metadata/permissions/users/:userName') + @Delete(':noteIdOrAlias/metadata/permissions/users/:username') async removeUserPermission( @RequestUser() user: User, @RequestNote() note: Note, - @Param('userName') username: Username, + @Param('username') username: NoteUserPermissionEntryDto['username'], ): Promise<NotePermissionsDto> { try { const permissionUser = await this.userService.getUserByUsername(username); @@ -247,8 +253,8 @@ export class NotesController { async setGroupPermission( @RequestUser() user: User, @RequestNote() note: Note, - @Param('groupName') groupName: string, - @Body('canEdit') canEdit: boolean, + @Param('groupName') groupName: NoteGroupPermissionUpdateDto['groupName'], + @Body('canEdit') canEdit: NoteGroupPermissionUpdateDto['canEdit'], ): Promise<NotePermissionsDto> { const permissionGroup = await this.groupService.getGroupByName(groupName); const returnedNote = await this.permissionService.setGroupPermission( @@ -266,7 +272,7 @@ export class NotesController { async removeGroupPermission( @RequestUser() user: User, @RequestNote() note: Note, - @Param('groupName') groupName: string, + @Param('groupName') groupName: NoteGroupPermissionEntryDto['groupName'], ): Promise<NotePermissionsDto> { const permissionGroup = await this.groupService.getGroupByName(groupName); const returnedNote = await this.permissionService.removeGroupPermission( @@ -282,9 +288,11 @@ export class NotesController { async changeOwner( @RequestUser() user: User, @RequestNote() note: Note, - @Body('newOwner') newOwner: Username, + @Body() changeNoteOwnerDto: ChangeNoteOwnerDto, ): Promise<NoteDto> { - const owner = await this.userService.getUserByUsername(newOwner); + const owner = await this.userService.getUserByUsername( + changeNoteOwnerDto.owner, + ); return await this.noteService.toNoteDto( await this.permissionService.changeOwner(note, owner), ); diff --git a/backend/src/api/private/tokens/api-tokens.controller.ts b/backend/src/api/private/tokens/api-tokens.controller.ts index e9e22e847..a1fad3997 100644 --- a/backend/src/api/private/tokens/api-tokens.controller.ts +++ b/backend/src/api/private/tokens/api-tokens.controller.ts @@ -1,8 +1,13 @@ /* - * 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 */ +import { + ApiTokenCreateDto, + ApiTokenDto, + ApiTokenWithSecretDto, +} from '@hedgedoc/commons'; import { Body, Controller, @@ -15,11 +20,6 @@ import { } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; -import { - ApiTokenCreateDto, - ApiTokenDto, - ApiTokenWithSecretDto, -} from '../../../api-token/api-token.dto'; import { ApiTokenService } from '../../../api-token/api-token.service'; import { SessionGuard } from '../../../auth/session.guard'; import { ConsoleLoggerService } from '../../../logger/console-logger.service'; diff --git a/backend/src/api/private/users/users.controller.ts b/backend/src/api/private/users/users.controller.ts index 20ce960b9..d8957ba60 100644 --- a/backend/src/api/private/users/users.controller.ts +++ b/backend/src/api/private/users/users.controller.ts @@ -1,19 +1,18 @@ /* - * 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 */ +import { + UserInfoDto, + UsernameCheckDto, + UsernameCheckResponseDto, +} from '@hedgedoc/commons'; import { Body, Controller, Get, HttpCode, Param, Post } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; import { ConsoleLoggerService } from '../../../logger/console-logger.service'; -import { UserInfoDto } from '../../../users/user-info.dto'; -import { - UsernameCheckDto, - UsernameCheckResponseDto, -} from '../../../users/username-check.dto'; import { UsersService } from '../../../users/users.service'; -import { Username } from '../../../utils/username'; import { OpenApi } from '../../utils/openapi.decorator'; @ApiTags('users') @@ -41,7 +40,7 @@ export class UsersController { @Get('profile/:username') @OpenApi(200) - async getUser(@Param('username') username: Username): Promise<UserInfoDto> { + async getUser(@Param('username') username: string): Promise<UserInfoDto> { return this.userService.toUserDto( await this.userService.getUserByUsername(username), ); diff --git a/backend/src/api/public/alias/alias.controller.ts b/backend/src/api/public/alias/alias.controller.ts index 9a18752d9..0b137ec51 100644 --- a/backend/src/api/public/alias/alias.controller.ts +++ b/backend/src/api/public/alias/alias.controller.ts @@ -1,8 +1,14 @@ /* - * 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 */ +import { + AliasCreateDto, + AliasDto, + AliasSchema, + AliasUpdateDto, +} from '@hedgedoc/commons'; import { BadRequestException, Body, @@ -18,9 +24,6 @@ import { ApiSecurity, ApiTags } from '@nestjs/swagger'; import { ApiTokenGuard } from '../../../api-token/api-token.guard'; import { ConsoleLoggerService } from '../../../logger/console-logger.service'; -import { AliasCreateDto } from '../../../notes/alias-create.dto'; -import { AliasUpdateDto } from '../../../notes/alias-update.dto'; -import { AliasDto } from '../../../notes/alias.dto'; import { AliasService } from '../../../notes/alias.service'; import { NotesService } from '../../../notes/notes.service'; import { PermissionsService } from '../../../permissions/permissions.service'; @@ -48,7 +51,7 @@ export class AliasController { { code: 200, description: 'The new alias', - dto: AliasDto, + schema: AliasSchema, }, 403, 404, @@ -75,7 +78,7 @@ export class AliasController { { code: 200, description: 'The updated alias', - dto: AliasDto, + schema: AliasSchema, }, 403, 404, @@ -110,7 +113,7 @@ export class AliasController { ) async removeAlias( @RequestUser() user: User, - @Param('alias') alias: string, + @Param('alias') alias: AliasDto['name'], ): Promise<void> { const note = await this.noteService.getNoteByIdOrAlias(alias); if (!(await this.permissionsService.isOwner(user, note))) { diff --git a/backend/src/api/public/me/me.controller.ts b/backend/src/api/public/me/me.controller.ts index 44394d035..5fc4514d5 100644 --- a/backend/src/api/public/me/me.controller.ts +++ b/backend/src/api/public/me/me.controller.ts @@ -1,8 +1,16 @@ /* - * 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 */ +import { + FullUserInfoDto, + FullUserInfoSchema, + MediaUploadDto, + MediaUploadSchema, + NoteMetadataDto, + NoteMetadataSchema, +} from '@hedgedoc/commons'; import { Body, Controller, @@ -19,12 +27,9 @@ import { HistoryEntryUpdateDto } from '../../../history/history-entry-update.dto import { HistoryEntryDto } from '../../../history/history-entry.dto'; import { HistoryService } from '../../../history/history.service'; import { ConsoleLoggerService } from '../../../logger/console-logger.service'; -import { MediaUploadDto } from '../../../media/media-upload.dto'; import { MediaService } from '../../../media/media.service'; -import { NoteMetadataDto } from '../../../notes/note-metadata.dto'; import { Note } from '../../../notes/note.entity'; import { NotesService } from '../../../notes/notes.service'; -import { FullUserInfoDto } from '../../../users/user-info.dto'; import { User } from '../../../users/user.entity'; import { UsersService } from '../../../users/users.service'; import { GetNoteInterceptor } from '../../utils/get-note.interceptor'; @@ -52,7 +57,7 @@ export class MeController { @OpenApi({ code: 200, description: 'The user information', - dto: FullUserInfoDto, + schema: FullUserInfoSchema, }) getMe(@RequestUser() user: User): FullUserInfoDto { return this.usersService.toFullUserDto(user); @@ -63,7 +68,6 @@ export class MeController { code: 200, description: 'The history entries of the user', isArray: true, - dto: HistoryEntryDto, }) async getUserHistory(@RequestUser() user: User): Promise<HistoryEntryDto[]> { const foundEntries = await this.historyService.getEntriesByUser(user); @@ -78,7 +82,6 @@ export class MeController { { code: 200, description: 'The history entry of the user which points to the note', - dto: HistoryEntryDto, }, 404, ) @@ -96,7 +99,6 @@ export class MeController { { code: 200, description: 'The updated history entry', - dto: HistoryEntryDto, }, 404, ) @@ -125,7 +127,7 @@ export class MeController { code: 200, description: 'Metadata of all notes of the user', isArray: true, - dto: NoteMetadataDto, + schema: NoteMetadataSchema, }) async getMyNotes(@RequestUser() user: User): Promise<NoteMetadataDto[]> { const notes = this.notesService.getUserNotes(user); @@ -139,7 +141,7 @@ export class MeController { code: 200, description: 'All media uploads of the user', isArray: true, - dto: MediaUploadDto, + schema: MediaUploadSchema, }) async getMyMedia(@RequestUser() user: User): Promise<MediaUploadDto[]> { const media = await this.mediaService.listUploadsByUser(user); diff --git a/backend/src/api/public/media/media.controller.ts b/backend/src/api/public/media/media.controller.ts index dbd2f1d27..8e63cbc21 100644 --- a/backend/src/api/public/media/media.controller.ts +++ b/backend/src/api/public/media/media.controller.ts @@ -1,8 +1,9 @@ /* - * 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 */ +import { MediaUploadDto, MediaUploadSchema } from '@hedgedoc/commons'; import { BadRequestException, Controller, @@ -28,7 +29,6 @@ import { Response } from 'express'; import { ApiTokenGuard } from '../../../api-token/api-token.guard'; import { PermissionError } from '../../../errors/errors'; import { ConsoleLoggerService } from '../../../logger/console-logger.service'; -import { MediaUploadDto } from '../../../media/media-upload.dto'; import { MediaService } from '../../../media/media.service'; import { MulterFile } from '../../../media/multer-file.interface'; import { Note } from '../../../notes/note.entity'; @@ -77,7 +77,7 @@ export class MediaController { { code: 201, description: 'The file was uploaded successfully', - dto: MediaUploadDto, + schema: MediaUploadSchema, }, 400, 403, diff --git a/backend/src/api/public/monitoring/monitoring.controller.ts b/backend/src/api/public/monitoring/monitoring.controller.ts index 73001268a..df91653f5 100644 --- a/backend/src/api/public/monitoring/monitoring.controller.ts +++ b/backend/src/api/public/monitoring/monitoring.controller.ts @@ -1,14 +1,14 @@ /* - * 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 */ +import { ServerStatusDto, ServerStatusSchema } from '@hedgedoc/commons'; import { Controller, Get, UseGuards } from '@nestjs/common'; import { ApiSecurity, ApiTags } from '@nestjs/swagger'; import { ApiTokenGuard } from '../../../api-token/api-token.guard'; import { MonitoringService } from '../../../monitoring/monitoring.service'; -import { ServerStatusDto } from '../../../monitoring/server-status.dto'; import { OpenApi } from '../../utils/openapi.decorator'; @UseGuards(ApiTokenGuard) @@ -24,7 +24,7 @@ export class MonitoringController { { code: 200, description: 'The server info', - dto: ServerStatusDto, + schema: ServerStatusSchema, }, 403, ) diff --git a/backend/src/api/public/notes/notes.controller.ts b/backend/src/api/public/notes/notes.controller.ts index 9a993faf5..644e1224f 100644 --- a/backend/src/api/public/notes/notes.controller.ts +++ b/backend/src/api/public/notes/notes.controller.ts @@ -1,8 +1,24 @@ /* - * 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 */ +import { + MediaUploadDto, + MediaUploadSchema, + NoteDto, + NoteMediaDeletionDto, + NoteMetadataDto, + NoteMetadataSchema, + NotePermissionsDto, + NotePermissionsSchema, + NotePermissionsUpdateDto, + NoteSchema, + RevisionDto, + RevisionMetadataDto, + RevisionMetadataSchema, + RevisionSchema, +} from '@hedgedoc/commons'; import { BadRequestException, Body, @@ -22,27 +38,16 @@ import { NotInDBError } from '../../../errors/errors'; import { GroupsService } from '../../../groups/groups.service'; import { HistoryService } from '../../../history/history.service'; import { ConsoleLoggerService } from '../../../logger/console-logger.service'; -import { MediaUploadDto } from '../../../media/media-upload.dto'; import { MediaService } from '../../../media/media.service'; -import { NoteMetadataDto } from '../../../notes/note-metadata.dto'; -import { - NotePermissionsDto, - NotePermissionsUpdateDto, -} from '../../../notes/note-permissions.dto'; -import { NoteDto } from '../../../notes/note.dto'; import { Note } from '../../../notes/note.entity'; -import { NoteMediaDeletionDto } from '../../../notes/note.media-deletion.dto'; import { NotesService } from '../../../notes/notes.service'; import { PermissionsGuard } from '../../../permissions/permissions.guard'; import { PermissionsService } from '../../../permissions/permissions.service'; import { RequirePermission } from '../../../permissions/require-permission.decorator'; import { RequiredPermission } from '../../../permissions/required-permission.enum'; -import { RevisionMetadataDto } from '../../../revisions/revision-metadata.dto'; -import { RevisionDto } from '../../../revisions/revision.dto'; import { RevisionsService } from '../../../revisions/revisions.service'; import { User } from '../../../users/user.entity'; import { UsersService } from '../../../users/users.service'; -import { Username } from '../../../utils/username'; import { GetNoteInterceptor } from '../../utils/get-note.interceptor'; import { MarkdownBody } from '../../utils/markdown-body.decorator'; import { OpenApi } from '../../utils/openapi.decorator'; @@ -88,7 +93,7 @@ export class NotesController { { code: 200, description: 'Get information about the newly created note', - dto: NoteDto, + schema: NoteSchema, }, 403, 404, @@ -107,7 +112,7 @@ export class NotesController { { code: 201, description: 'Get information about the newly created note', - dto: NoteDto, + schema: NoteSchema, }, 400, 403, @@ -155,7 +160,7 @@ export class NotesController { { code: 200, description: 'The new, changed note', - dto: NoteDto, + schema: NoteSchema, }, 403, 404, @@ -197,7 +202,7 @@ export class NotesController { { code: 200, description: 'The metadata of the note', - dto: NoteMetadataDto, + schema: NoteMetadataSchema, }, 403, 404, @@ -216,7 +221,7 @@ export class NotesController { { code: 200, description: 'The updated permissions of the note', - dto: NotePermissionsDto, + schema: NotePermissionsSchema, }, 403, 404, @@ -238,7 +243,7 @@ export class NotesController { { code: 200, description: 'Get the permissions for a note', - dto: NotePermissionsDto, + schema: NotePermissionsSchema, }, 403, 404, @@ -257,7 +262,7 @@ export class NotesController { { code: 200, description: 'Set the permissions for a user on a note', - dto: NotePermissionsDto, + schema: NotePermissionsSchema, }, 403, 404, @@ -265,7 +270,7 @@ export class NotesController { async setUserPermission( @RequestUser() user: User, @RequestNote() note: Note, - @Param('userName') username: Username, + @Param('userName') username: string, @Body('canEdit') canEdit: boolean, ): Promise<NotePermissionsDto> { const permissionUser = await this.userService.getUserByUsername(username); @@ -284,7 +289,7 @@ export class NotesController { { code: 200, description: 'Remove the permission for a user on a note', - dto: NotePermissionsDto, + schema: NotePermissionsSchema, }, 403, 404, @@ -292,7 +297,7 @@ export class NotesController { async removeUserPermission( @RequestUser() user: User, @RequestNote() note: Note, - @Param('userName') username: Username, + @Param('userName') username: string, ): Promise<NotePermissionsDto> { try { const permissionUser = await this.userService.getUserByUsername(username); @@ -318,7 +323,7 @@ export class NotesController { { code: 200, description: 'Set the permissions for a group on a note', - dto: NotePermissionsDto, + schema: NotePermissionsSchema, }, 403, 404, @@ -345,7 +350,7 @@ export class NotesController { { code: 200, description: 'Remove the permission for a group on a note', - dto: NotePermissionsDto, + schema: NotePermissionsSchema, }, 403, 404, @@ -370,7 +375,7 @@ export class NotesController { { code: 200, description: 'Changes the owner of the note', - dto: NoteDto, + schema: NoteSchema, }, 403, 404, @@ -378,7 +383,7 @@ export class NotesController { async changeOwner( @RequestUser() user: User, @RequestNote() note: Note, - @Body('newOwner') newOwner: Username, + @Body('newOwner') newOwner: string, ): Promise<NoteDto> { const owner = await this.userService.getUserByUsername(newOwner); return await this.noteService.toNoteDto( @@ -394,7 +399,7 @@ export class NotesController { code: 200, description: 'Revisions of the note', isArray: true, - dto: RevisionMetadataDto, + schema: RevisionMetadataSchema, }, 403, 404, @@ -418,7 +423,7 @@ export class NotesController { { code: 200, description: 'Revision of the note for the given id or alias', - dto: RevisionDto, + schema: RevisionSchema, }, 403, 404, @@ -440,7 +445,7 @@ export class NotesController { code: 200, description: 'All media uploads of the note', isArray: true, - dto: MediaUploadDto, + schema: MediaUploadSchema, }) async getNotesMedia( @RequestUser() user: User, diff --git a/backend/src/api/utils/openapi.decorator.ts b/backend/src/api/utils/openapi.decorator.ts index ceec342b8..07859b98c 100644 --- a/backend/src/api/utils/openapi.decorator.ts +++ b/backend/src/api/utils/openapi.decorator.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 */ @@ -14,10 +14,12 @@ import { ApiNotFoundResponse, ApiOkResponse, ApiProduces, + ApiResponseNoStatusOptions, ApiUnauthorizedResponse, } from '@nestjs/swagger'; +import { zodToOpenAPI } from 'nestjs-zod'; +import { ZodSchema } from 'zod'; -import { BaseDto } from '../../utils/base.dto.'; import { badRequestDescription, conflictDescription, @@ -57,7 +59,7 @@ export interface HttpStatusCodeWithExtraInformation { code: HttpStatusCodes; description?: string; isArray?: boolean; - dto?: BaseDto; + schema?: ZodSchema; mimeType?: string; } @@ -89,7 +91,8 @@ export const OpenApi = ( let code: HttpStatusCodes = 200; let description: string | undefined = undefined; let isArray: boolean | undefined = undefined; - let dto: BaseDto | undefined = undefined; + let schema: ZodSchema | undefined = undefined; + if (typeof entry == 'number') { code = entry; } else { @@ -97,7 +100,7 @@ export const OpenApi = ( code = entry.code; description = entry.description; isArray = entry.isArray; - dto = entry.dto; + schema = entry.schema; if (entry.mimeType) { decoratorsToApply.push( ApiProduces(entry.mimeType), @@ -105,22 +108,32 @@ export const OpenApi = ( ); } } + + let defaultResponseObject: ApiResponseNoStatusOptions = { + description: description ?? createdDescription, + isArray: isArray, + }; + if (schema) { + defaultResponseObject = { + ...defaultResponseObject, + schema: zodToOpenAPI(schema), + }; + } + switch (code) { case 200: decoratorsToApply.push( ApiOkResponse({ + ...defaultResponseObject, description: description ?? okDescription, - isArray: isArray, - type: dto ? (): BaseDto => dto : undefined, }), ); break; case 201: decoratorsToApply.push( ApiCreatedResponse({ + ...defaultResponseObject, description: description ?? createdDescription, - isArray: isArray, - type: dto ? (): BaseDto => dto : undefined, }), HttpCode(201), ); diff --git a/backend/src/auth/identity.entity.ts b/backend/src/auth/identity.entity.ts index 3564aa46e..0bdcdf3f5 100644 --- a/backend/src/auth/identity.entity.ts +++ b/backend/src/auth/identity.entity.ts @@ -1,8 +1,9 @@ /* - * 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 */ +import { ProviderType } from '@hedgedoc/commons'; import { Column, CreateDateColumn, @@ -13,7 +14,6 @@ import { } from 'typeorm'; import { User } from '../users/user.entity'; -import { ProviderType } from './provider-type.enum'; /** * The identity represents a single way for a user to login. diff --git a/backend/src/auth/identity.service.ts b/backend/src/auth/identity.service.ts index 855103cf0..2af021447 100644 --- a/backend/src/auth/identity.service.ts +++ b/backend/src/auth/identity.service.ts @@ -1,8 +1,13 @@ /* - * 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 */ +import { + FullUserInfoDto, + PendingUserConfirmationDto, + ProviderType, +} from '@hedgedoc/commons'; import { Inject, Injectable, @@ -14,12 +19,9 @@ import { DataSource, Repository } from 'typeorm'; import AuthConfiguration, { AuthConfig } from '../config/auth.config'; import { NotInDBError } from '../errors/errors'; import { ConsoleLoggerService } from '../logger/console-logger.service'; -import { FullUserInfoDto } from '../users/user-info.dto'; import { User } from '../users/user.entity'; import { UsersService } from '../users/users.service'; import { Identity } from './identity.entity'; -import { PendingUserConfirmationDto } from './pending-user-confirmation.dto'; -import { ProviderType } from './provider-type.enum'; @Injectable() export class IdentityService { diff --git a/backend/src/auth/ldap/ldap-login.dto.ts b/backend/src/auth/ldap/ldap-login.dto.ts deleted file mode 100644 index 9c28af41d..000000000 --- a/backend/src/auth/ldap/ldap-login.dto.ts +++ /dev/null @@ -1,13 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file) - * - * SPDX-License-Identifier: AGPL-3.0-only - */ -import { IsString } from 'class-validator'; - -export class LdapLoginDto { - @IsString() - username: string; // This is not of type Username, because LDAP server may use mixed case usernames - @IsString() - password: string; -} diff --git a/backend/src/auth/ldap/ldap.service.ts b/backend/src/auth/ldap/ldap.service.ts index 64adbfebe..23f48a3f3 100644 --- a/backend/src/auth/ldap/ldap.service.ts +++ b/backend/src/auth/ldap/ldap.service.ts @@ -1,8 +1,9 @@ /* - * 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 */ +import { FullUserInfoWithIdDto } from '@hedgedoc/commons'; import { Inject, Injectable, @@ -18,8 +19,6 @@ import authConfiguration, { LDAPConfig, } from '../../config/auth.config'; import { ConsoleLoggerService } from '../../logger/console-logger.service'; -import { FullUserInfoWithIdDto } from '../../users/user-info.dto'; -import { Username } from '../../utils/username'; const LDAP_ERROR_MAP: Record<string, string> = { /* eslint-disable @typescript-eslint/naming-convention */ @@ -96,7 +95,7 @@ export class LdapService { return reject(new UnauthorizedException(LDAP_ERROR_MAP['default'])); } - let email: string | undefined = undefined; + let email: string | null = null; if (userInfo['mail']) { if (Array.isArray(userInfo['mail'])) { email = userInfo['mail'][0] as string; @@ -107,10 +106,10 @@ export class LdapService { return resolve({ email, - username: username as Username, + username: username, id: userInfo[ldapConfig.userIdField], displayName: userInfo[ldapConfig.displayNameField] ?? username, - photoUrl: undefined, // TODO LDAP stores images as binaries, + photoUrl: null, // TODO LDAP stores images as binaries, // we need to convert them into a data-URL or alike }); }, diff --git a/backend/src/auth/local/local.service.ts b/backend/src/auth/local/local.service.ts index a2bd482b0..a654a210f 100644 --- a/backend/src/auth/local/local.service.ts +++ b/backend/src/auth/local/local.service.ts @@ -1,8 +1,9 @@ /* - * 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 */ +import { ProviderType } from '@hedgedoc/commons'; import { Inject, Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { @@ -32,7 +33,6 @@ import { User } from '../../users/user.entity'; import { checkPassword, hashPassword } from '../../utils/password'; import { Identity } from '../identity.entity'; import { IdentityService } from '../identity.service'; -import { ProviderType } from '../provider-type.enum'; @Injectable() export class LocalService { diff --git a/backend/src/auth/local/login.dto.ts b/backend/src/auth/local/login.dto.ts deleted file mode 100644 index cb3ed66ad..000000000 --- a/backend/src/auth/local/login.dto.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file) - * - * SPDX-License-Identifier: AGPL-3.0-only - */ -import { Type } from 'class-transformer'; -import { IsLowercase, IsString } from 'class-validator'; - -import { Username } from '../../utils/username'; - -export class LoginDto { - @Type(() => String) - @IsString() - @IsLowercase() - username: Username; - @IsString() - password: string; -} diff --git a/backend/src/auth/local/register.dto.ts b/backend/src/auth/local/register.dto.ts deleted file mode 100644 index a6eec0351..000000000 --- a/backend/src/auth/local/register.dto.ts +++ /dev/null @@ -1,22 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file) - * - * SPDX-License-Identifier: AGPL-3.0-only - */ -import { Type } from 'class-transformer'; -import { IsLowercase, IsString } from 'class-validator'; - -import { Username } from '../../utils/username'; - -export class RegisterDto { - @Type(() => String) - @IsString() - @IsLowercase() - username: Username; - - @IsString() - displayName: string; - - @IsString() - password: string; -} diff --git a/backend/src/auth/local/update-password.dto.ts b/backend/src/auth/local/update-password.dto.ts deleted file mode 100644 index 8f3edc276..000000000 --- a/backend/src/auth/local/update-password.dto.ts +++ /dev/null @@ -1,13 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file) - * - * SPDX-License-Identifier: AGPL-3.0-only - */ -import { IsString } from 'class-validator'; - -export class UpdatePasswordDto { - @IsString() - currentPassword: string; - @IsString() - newPassword: string; -} diff --git a/backend/src/auth/oidc/oidc.service.ts b/backend/src/auth/oidc/oidc.service.ts index e3b8c579e..49e1d19b0 100644 --- a/backend/src/auth/oidc/oidc.service.ts +++ b/backend/src/auth/oidc/oidc.service.ts @@ -1,8 +1,9 @@ /* - * 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 */ +import { FullUserInfoDto, ProviderType } from '@hedgedoc/commons'; import { ForbiddenException, Inject, @@ -20,10 +21,8 @@ import authConfiguration, { } from '../../config/auth.config'; import { NotInDBError } from '../../errors/errors'; import { ConsoleLoggerService } from '../../logger/console-logger.service'; -import { FullUserInfoDto } from '../../users/user-info.dto'; import { Identity } from '../identity.entity'; import { IdentityService } from '../identity.service'; -import { ProviderType } from '../provider-type.enum'; import { RequestWithSession } from '../session.guard'; interface OidcClientConfigEntry { @@ -227,8 +226,8 @@ export class OidcService { const newUserData = { username, displayName, - photoUrl, - email, + photoUrl: photoUrl ?? null, + email: email ?? null, }; request.session.providerUserId = userId; request.session.newUserData = newUserData; diff --git a/backend/src/auth/pending-user-confirmation.dto.ts b/backend/src/auth/pending-user-confirmation.dto.ts deleted file mode 100644 index d4a3f82da..000000000 --- a/backend/src/auth/pending-user-confirmation.dto.ts +++ /dev/null @@ -1,20 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file) - * - * SPDX-License-Identifier: AGPL-3.0-only - */ -import { IsOptional, IsString } from 'class-validator'; - -import { BaseDto } from '../utils/base.dto.'; - -export class PendingUserConfirmationDto extends BaseDto { - @IsString() - username: string; - - @IsString() - displayName: string; - - @IsOptional() - @IsString() - profilePicture: string | undefined; -} diff --git a/backend/src/auth/session.guard.ts b/backend/src/auth/session.guard.ts index 402036952..b366e01ba 100644 --- a/backend/src/auth/session.guard.ts +++ b/backend/src/auth/session.guard.ts @@ -1,8 +1,10 @@ /* - * 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 */ +import { ProviderType } from '@hedgedoc/commons'; +import { GuestAccess } from '@hedgedoc/commons'; import { CanActivate, ExecutionContext, @@ -13,13 +15,11 @@ import { import { Request } from 'express'; import { CompleteRequest } from '../api/utils/request.type'; -import { GuestAccess } from '../config/guest_access.enum'; import noteConfiguration, { NoteConfig } from '../config/note.config'; import { NotInDBError } from '../errors/errors'; import { ConsoleLoggerService } from '../logger/console-logger.service'; import { SessionState } from '../sessions/session.service'; import { UsersService } from '../users/users.service'; -import { ProviderType } from './provider-type.enum'; export type RequestWithSession = Request & { session: SessionState; diff --git a/backend/src/config/customization.config.ts b/backend/src/config/customization.config.ts index 09f00234e..74805dc12 100644 --- a/backend/src/config/customization.config.ts +++ b/backend/src/config/customization.config.ts @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file) + * SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file) * * SPDX-License-Identifier: AGPL-3.0-only */ @@ -10,24 +10,24 @@ import { buildErrorMessage } from './utils'; export interface CustomizationConfig { branding: { - customName: string; - customLogo: string; + customName: string | null; + customLogo: string | null; }; specialUrls: { - privacy: string; - termsOfUse: string; - imprint: string; + privacy: string | null; + termsOfUse: string | null; + imprint: string | null; }; } const schema = Joi.object({ branding: Joi.object({ - customName: Joi.string().optional().label('HD_CUSTOM_NAME'), + customName: Joi.string().allow(null).label('HD_CUSTOM_NAME'), customLogo: Joi.string() .uri({ scheme: [/https?/], }) - .optional() + .allow(null) .label('HD_CUSTOM_LOGO'), }), specialUrls: Joi.object({ @@ -35,19 +35,19 @@ const schema = Joi.object({ .uri({ scheme: /https?/, }) - .optional() + .allow(null) .label('HD_PRIVACY_URL'), termsOfUse: Joi.string() .uri({ scheme: /https?/, }) - .optional() + .allow(null) .label('HD_TERMS_OF_USE_URL'), imprint: Joi.string() .uri({ scheme: /https?/, }) - .optional() + .allow(null) .label('HD_IMPRINT_URL'), }), }); @@ -56,13 +56,13 @@ export default registerAs('customizationConfig', () => { const customizationConfig = schema.validate( { branding: { - customName: process.env.HD_CUSTOM_NAME, - customLogo: process.env.HD_CUSTOM_LOGO, + customName: process.env.HD_CUSTOM_NAME ?? null, + customLogo: process.env.HD_CUSTOM_LOGO ?? null, }, specialUrls: { - privacy: process.env.HD_PRIVACY_URL, - termsOfUse: process.env.HD_TERMS_OF_USE_URL, - imprint: process.env.HD_IMPRINT_URL, + privacy: process.env.HD_PRIVACY_URL ?? null, + termsOfUse: process.env.HD_TERMS_OF_USE_URL ?? null, + imprint: process.env.HD_IMPRINT_URL ?? null, }, }, { diff --git a/backend/src/config/external-services.config.ts b/backend/src/config/external-services.config.ts index 22d428c37..532e2f7e2 100644 --- a/backend/src/config/external-services.config.ts +++ b/backend/src/config/external-services.config.ts @@ -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 */ @@ -9,7 +9,7 @@ import * as Joi from 'joi'; import { buildErrorMessage } from './utils'; export interface ExternalServicesConfig { - plantUmlServer: string; + plantUmlServer: string | null; imageProxy: string; } @@ -18,7 +18,7 @@ const schema = Joi.object({ .uri({ scheme: /https?/, }) - .optional() + .allow(null) .label('HD_PLANTUML_SERVER'), imageProxy: Joi.string() .uri({ @@ -36,7 +36,7 @@ export default registerAs('externalServicesConfig', () => { } const externalConfig = schema.validate( { - plantUmlServer: process.env.HD_PLANTUML_SERVER, + plantUmlServer: process.env.HD_PLANTUML_SERVER ?? null, imageProxy: process.env.HD_IMAGE_PROXY, }, { diff --git a/backend/src/config/mock/note.config.mock.ts b/backend/src/config/mock/note.config.mock.ts index 45d7e1b7d..e0e3e0e65 100644 --- a/backend/src/config/mock/note.config.mock.ts +++ b/backend/src/config/mock/note.config.mock.ts @@ -1,13 +1,13 @@ /* - * 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 */ +import { GuestAccess } from '@hedgedoc/commons'; import { ConfigFactoryKeyHost, registerAs } from '@nestjs/config'; import { ConfigFactory } from '@nestjs/config/dist/interfaces'; import { DefaultAccessLevel } from '../default-access-level.enum'; -import { GuestAccess } from '../guest_access.enum'; import { NoteConfig } from '../note.config'; export function createDefaultMockNoteConfig(): NoteConfig { diff --git a/backend/src/config/note.config.spec.ts b/backend/src/config/note.config.spec.ts index 2f4930976..38f2d0572 100644 --- a/backend/src/config/note.config.spec.ts +++ b/backend/src/config/note.config.spec.ts @@ -1,12 +1,12 @@ /* - * 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 */ +import { GuestAccess } from '@hedgedoc/commons'; import mockedEnv from 'mocked-env'; import { DefaultAccessLevel } from './default-access-level.enum'; -import { GuestAccess } from './guest_access.enum'; import noteConfig from './note.config'; describe('noteConfig', () => { diff --git a/backend/src/config/note.config.ts b/backend/src/config/note.config.ts index c7615e437..58126835a 100644 --- a/backend/src/config/note.config.ts +++ b/backend/src/config/note.config.ts @@ -1,8 +1,9 @@ /* - * 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 */ +import { GuestAccess } from '@hedgedoc/commons'; import { registerAs } from '@nestjs/config'; import * as Joi from 'joi'; @@ -10,7 +11,6 @@ import { DefaultAccessLevel, getDefaultAccessLevelOrdinal, } from './default-access-level.enum'; -import { GuestAccess } from './guest_access.enum'; import { buildErrorMessage, parseOptionalNumber, toArrayConfig } from './utils'; export interface NoteConfig { diff --git a/backend/src/frontend-config/frontend-config.dto.ts b/backend/src/frontend-config/frontend-config.dto.ts deleted file mode 100644 index 4805084d9..000000000 --- a/backend/src/frontend-config/frontend-config.dto.ts +++ /dev/null @@ -1,194 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file) - * - * SPDX-License-Identifier: AGPL-3.0-only - */ -import { Type } from 'class-transformer'; -import { - IsArray, - IsBoolean, - IsNumber, - IsOptional, - IsString, - IsUrl, - ValidateNested, -} from 'class-validator'; -import { URL } from 'url'; - -import { ProviderType } from '../auth/provider-type.enum'; -import { GuestAccess } from '../config/guest_access.enum'; -import { ServerVersion } from '../monitoring/server-status.dto'; -import { BaseDto } from '../utils/base.dto.'; - -export type AuthProviderTypeWithCustomName = - | ProviderType.LDAP - | ProviderType.OIDC; - -export type AuthProviderTypeWithoutCustomName = ProviderType.LOCAL; - -export class AuthProviderWithoutCustomNameDto extends BaseDto { - /** - * The type of the auth provider. - */ - @IsString() - @Type(() => String) - type: AuthProviderTypeWithoutCustomName; -} - -export class AuthProviderWithCustomNameDto extends BaseDto { - /** - * The type of the auth provider. - */ - @IsString() - @Type(() => String) - type: AuthProviderTypeWithCustomName; - - /** - * The identifier with which the auth provider can be called - * @example gitlab-fsorg - */ - @IsString() - identifier: string; - - /** - * The name given to the auth provider - * @example GitLab fachschaften.org - */ - @IsString() - providerName: string; - - /** - * The theme to apply for the login button. - * @example gitlab - */ - @IsOptional() - @IsString() - theme?: string; -} - -export type AuthProviderDto = - | AuthProviderWithCustomNameDto - | AuthProviderWithoutCustomNameDto; - -export class BrandingDto extends BaseDto { - /** - * The name to be displayed next to the HedgeDoc logo - * @example ACME Corp - */ - @IsString() - @IsOptional() - name?: string; - - /** - * The logo to be displayed next to the HedgeDoc logo - * @example https://md.example.com/logo.png - */ - @IsUrl() - @IsOptional() - @Type(() => URL) - logo?: URL; -} - -export class SpecialUrlsDto extends BaseDto { - /** - * A link to the privacy notice - * @example https://md.example.com/n/privacy - */ - @IsUrl() - @IsOptional() - @Type(() => URL) - privacy?: URL; - - /** - * A link to the terms of use - * @example https://md.example.com/n/termsOfUse - */ - @IsUrl() - @IsOptional() - @Type(() => URL) - termsOfUse?: URL; - - /** - * A link to the imprint - * @example https://md.example.com/n/imprint - */ - @IsUrl() - @IsOptional() - @Type(() => URL) - imprint?: URL; -} - -export class FrontendConfigDto extends BaseDto { - /** - * Maximum access level for guest users - */ - @IsString() - guestAccess: GuestAccess; - - /** - * Are users allowed to register on this instance? - */ - @IsBoolean() - allowRegister: boolean; - - /** - * Are users allowed to edit their profile information? - */ - @IsBoolean() - allowProfileEdits: boolean; - - /** - * Are users allowed to choose their username when signing up via OIDC? - */ - @IsBoolean() - allowChooseUsername: boolean; - - /** - * Which auth providers are enabled and how are they configured? - */ - // eslint-disable-next-line @darraghor/nestjs-typed/validated-non-primitive-property-needs-type-decorator - @IsArray() - @ValidateNested({ each: true }) - authProviders: AuthProviderDto[]; - - /** - * Individual branding information - */ - @ValidateNested() - @Type(() => BrandingDto) - branding: BrandingDto; - - /** - * Is an image proxy enabled? - */ - @IsBoolean() - useImageProxy: boolean; - - /** - * Links to some special pages - */ - @ValidateNested() - @Type(() => SpecialUrlsDto) - specialUrls: SpecialUrlsDto; - - /** - * The version of HedgeDoc - */ - @ValidateNested() - @Type(() => ServerVersion) - version: ServerVersion; - - /** - * The plantUML server that should be used to render. - */ - @IsUrl() - @IsOptional() - @Type(() => URL) - plantUmlServer?: URL; - - /** - * The maximal length of each document - */ - @IsNumber() - maxDocumentLength: number; -} diff --git a/backend/src/frontend-config/frontend-config.service.spec.ts b/backend/src/frontend-config/frontend-config.service.spec.ts index dec298439..01bb5a34c 100644 --- a/backend/src/frontend-config/frontend-config.service.spec.ts +++ b/backend/src/frontend-config/frontend-config.service.spec.ts @@ -1,19 +1,18 @@ /* - * 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 */ +import { GuestAccess, ProviderType } from '@hedgedoc/commons'; import { ConfigModule, registerAs } from '@nestjs/config'; import { Test, TestingModule } from '@nestjs/testing'; import { URL } from 'url'; -import { ProviderType } from '../auth/provider-type.enum'; import { AppConfig } from '../config/app.config'; import { AuthConfig } from '../config/auth.config'; import { CustomizationConfig } from '../config/customization.config'; import { DefaultAccessLevel } from '../config/default-access-level.enum'; import { ExternalServicesConfig } from '../config/external-services.config'; -import { GuestAccess } from '../config/guest_access.enum'; import { Loglevel } from '../config/loglevel.enum'; import { NoteConfig } from '../config/note.config'; import { LoggerModule } from '../logger/logger.module'; @@ -174,14 +173,11 @@ describe('FrontendConfigService', () => { const customName = 'Test Branding Name'; let index = 1; - for (const customLogo of [undefined, 'https://example.com/logo.png']) { - for (const privacyLink of [undefined, 'https://example.com/privacy']) { - for (const termsOfUseLink of [undefined, 'https://example.com/terms']) { - for (const imprintLink of [undefined, 'https://example.com/imprint']) { - for (const plantUmlServer of [ - undefined, - 'https://plantuml.example.com', - ]) { + for (const customLogo of [null, 'https://example.com/logo.png']) { + for (const privacyLink of [null, 'https://example.com/privacy']) { + for (const termsOfUseLink of [null, 'https://example.com/terms']) { + for (const imprintLink of [null, 'https://example.com/imprint']) { + for (const plantUmlServer of [null, 'https://plantuml.example.com']) { it(`combination #${index} works`, async () => { const appConfig: AppConfig = { baseUrl: domain, @@ -255,20 +251,24 @@ describe('FrontendConfigService', () => { expect(config.guestAccess).toEqual(noteConfig.guestAccess); expect(config.branding.name).toEqual(customName); expect(config.branding.logo).toEqual( - customLogo ? new URL(customLogo) : undefined, + customLogo !== null ? new URL(customLogo).toString() : null, ); expect(config.maxDocumentLength).toEqual(maxDocumentLength); expect(config.plantUmlServer).toEqual( - plantUmlServer ? new URL(plantUmlServer) : undefined, + plantUmlServer !== null + ? new URL(plantUmlServer).toString() + : null, ); expect(config.specialUrls.imprint).toEqual( - imprintLink ? new URL(imprintLink) : undefined, + imprintLink !== null ? new URL(imprintLink).toString() : null, ); expect(config.specialUrls.privacy).toEqual( - privacyLink ? new URL(privacyLink) : undefined, + privacyLink !== null ? new URL(privacyLink).toString() : null, ); expect(config.specialUrls.termsOfUse).toEqual( - termsOfUseLink ? new URL(termsOfUseLink) : undefined, + termsOfUseLink !== null + ? new URL(termsOfUseLink).toString() + : null, ); expect(config.useImageProxy).toEqual(!!imageProxy); expect(config.version).toEqual( diff --git a/backend/src/frontend-config/frontend-config.service.ts b/backend/src/frontend-config/frontend-config.service.ts index 5242c4ed9..033853cbb 100644 --- a/backend/src/frontend-config/frontend-config.service.ts +++ b/backend/src/frontend-config/frontend-config.service.ts @@ -1,12 +1,18 @@ /* - * 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 */ +import { + BrandingDto, + FrontendConfigDto, + ProviderType, + SpecialUrlDto, +} from '@hedgedoc/commons'; +import { AuthProviderDto } from '@hedgedoc/commons'; import { Inject, Injectable } from '@nestjs/common'; import { URL } from 'url'; -import { ProviderType } from '../auth/provider-type.enum'; import appConfiguration, { AppConfig } from '../config/app.config'; import authConfiguration, { AuthConfig } from '../config/auth.config'; import customizationConfiguration, { @@ -18,12 +24,6 @@ import externalServicesConfiguration, { import noteConfiguration, { NoteConfig } from '../config/note.config'; import { ConsoleLoggerService } from '../logger/console-logger.service'; import { getServerVersionFromPackageJson } from '../utils/serverVersion'; -import { - AuthProviderDto, - BrandingDto, - FrontendConfigDto, - SpecialUrlsDto, -} from './frontend-config.dto'; @Injectable() export class FrontendConfigService { @@ -53,8 +53,8 @@ export class FrontendConfigService { branding: this.getBranding(), maxDocumentLength: this.noteConfig.maxDocumentLength, plantUmlServer: this.externalServicesConfig.plantUmlServer - ? new URL(this.externalServicesConfig.plantUmlServer) - : undefined, + ? new URL(this.externalServicesConfig.plantUmlServer).toString() + : null, specialUrls: this.getSpecialUrls(), useImageProxy: !!this.externalServicesConfig.imageProxy, version: await getServerVersionFromPackageJson(), @@ -73,6 +73,7 @@ export class FrontendConfigService { type: ProviderType.LDAP, providerName: ldapEntry.providerName, identifier: ldapEntry.identifier, + theme: null, }); }); this.authConfig.oidc.forEach((openidConnectEntry) => { @@ -80,7 +81,7 @@ export class FrontendConfigService { type: ProviderType.OIDC, providerName: openidConnectEntry.providerName, identifier: openidConnectEntry.identifier, - theme: openidConnectEntry.theme, + theme: openidConnectEntry.theme ?? null, }); }); return providers; @@ -89,23 +90,23 @@ export class FrontendConfigService { private getBranding(): BrandingDto { return { logo: this.customizationConfig.branding.customLogo - ? new URL(this.customizationConfig.branding.customLogo) - : undefined, + ? new URL(this.customizationConfig.branding.customLogo).toString() + : null, name: this.customizationConfig.branding.customName, }; } - private getSpecialUrls(): SpecialUrlsDto { + private getSpecialUrls(): SpecialUrlDto { return { imprint: this.customizationConfig.specialUrls.imprint - ? new URL(this.customizationConfig.specialUrls.imprint) - : undefined, + ? new URL(this.customizationConfig.specialUrls.imprint).toString() + : null, privacy: this.customizationConfig.specialUrls.privacy - ? new URL(this.customizationConfig.specialUrls.privacy) - : undefined, + ? new URL(this.customizationConfig.specialUrls.privacy).toString() + : null, termsOfUse: this.customizationConfig.specialUrls.termsOfUse - ? new URL(this.customizationConfig.specialUrls.termsOfUse) - : undefined, + ? new URL(this.customizationConfig.specialUrls.termsOfUse).toString() + : null, }; } } diff --git a/backend/src/groups/group-info.dto.ts b/backend/src/groups/group-info.dto.ts deleted file mode 100644 index f2ea39672..000000000 --- a/backend/src/groups/group-info.dto.ts +++ /dev/null @@ -1,36 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) - * - * SPDX-License-Identifier: AGPL-3.0-only - */ -import { ApiProperty } from '@nestjs/swagger'; -import { IsBoolean, IsString } from 'class-validator'; - -import { BaseDto } from '../utils/base.dto.'; - -export class GroupInfoDto extends BaseDto { - /** - * Name of the group - * @example "superheroes" - */ - @IsString() - @ApiProperty() - name: string; - - /** - * Display name of this group - * @example "Superheroes" - */ - @IsString() - @ApiProperty() - displayName: string; - - /** - * True if this group must be specially handled - * Used for e.g. "everybody", "all logged in users" - * @example false - */ - @IsBoolean() - @ApiProperty() - special: boolean; -} diff --git a/backend/src/groups/groups.service.ts b/backend/src/groups/groups.service.ts index 5b15dea9b..08c1d3751 100644 --- a/backend/src/groups/groups.service.ts +++ b/backend/src/groups/groups.service.ts @@ -1,15 +1,15 @@ /* - * 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 */ +import { GroupInfoDto } from '@hedgedoc/commons'; import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { AlreadyInDBError, NotInDBError } from '../errors/errors'; import { ConsoleLoggerService } from '../logger/console-logger.service'; -import { GroupInfoDto } from './group-info.dto'; import { Group } from './group.entity'; import { SpecialGroup } from './groups.special'; diff --git a/backend/src/media/media-upload.dto.ts b/backend/src/media/media-upload.dto.ts deleted file mode 100644 index 37387ec4d..000000000 --- a/backend/src/media/media-upload.dto.ts +++ /dev/null @@ -1,57 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file) - * - * SPDX-License-Identifier: AGPL-3.0-only - */ -import { ApiProperty } from '@nestjs/swagger'; -import { Type } from 'class-transformer'; -import { IsDate, IsLowercase, IsOptional, IsString } from 'class-validator'; - -import { BaseDto } from '../utils/base.dto.'; -import { Username } from '../utils/username'; - -export class MediaUploadDto extends BaseDto { - /** - * The uuid of the media file. - * @example "7697582e-0020-4188-9758-2e00207188ca" - */ - @IsString() - @ApiProperty() - uuid: string; - - /** - * The original filename of the media upload. - * @example "example.png" - */ - @IsString() - @ApiProperty() - fileName: string; - - /** - * The publicId of the note to which the uploaded file is linked to. - * @example "b604x5885k9k01bq7tsmawvnp0" - */ - @IsString() - @IsOptional() - @ApiProperty() - noteId: string | null; - - /** - * The date when the upload objects was created. - * @example "2020-12-01 12:23:34" - */ - @IsDate() - @Type(() => Date) - @ApiProperty() - createdAt: Date; - - /** - * The username of the user which uploaded the media file. - * @example "testuser5" - */ - @IsString() - @IsLowercase() - @IsOptional() - @ApiProperty() - username: Username | null; -} diff --git a/backend/src/media/media.service.ts b/backend/src/media/media.service.ts index ef3d7efcf..0a8396a2a 100644 --- a/backend/src/media/media.service.ts +++ b/backend/src/media/media.service.ts @@ -1,8 +1,9 @@ /* - * 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 */ +import { MediaUploadDto } from '@hedgedoc/commons'; import { Inject, Injectable } from '@nestjs/common'; import { ModuleRef } from '@nestjs/core'; import { InjectRepository } from '@nestjs/typeorm'; @@ -22,7 +23,6 @@ import { ImgurBackend } from './backends/imgur-backend'; import { S3Backend } from './backends/s3-backend'; import { WebdavBackend } from './backends/webdav-backend'; import { MediaBackend } from './media-backend.interface'; -import { MediaUploadDto } from './media-upload.dto'; import { MediaUpload } from './media-upload.entity'; @Injectable() @@ -268,7 +268,7 @@ export class MediaService { uuid: mediaUpload.uuid, fileName: mediaUpload.fileName, noteId: (await mediaUpload.note)?.publicId ?? null, - createdAt: mediaUpload.createdAt, + createdAt: mediaUpload.createdAt.toISOString(), username: user?.username ?? null, }; } diff --git a/backend/src/monitoring/monitoring.service.ts b/backend/src/monitoring/monitoring.service.ts index ff169dcb7..dcf097623 100644 --- a/backend/src/monitoring/monitoring.service.ts +++ b/backend/src/monitoring/monitoring.service.ts @@ -1,12 +1,12 @@ /* - * SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file) + * SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file) * * SPDX-License-Identifier: AGPL-3.0-only */ +import { ServerStatusDto } from '@hedgedoc/commons'; import { Injectable } from '@nestjs/common'; import { getServerVersionFromPackageJson } from '../utils/serverVersion'; -import { ServerStatusDto } from './server-status.dto'; @Injectable() export class MonitoringService { diff --git a/backend/src/monitoring/server-status.dto.ts b/backend/src/monitoring/server-status.dto.ts deleted file mode 100644 index af20ec836..000000000 --- a/backend/src/monitoring/server-status.dto.ts +++ /dev/null @@ -1,50 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) - * - * SPDX-License-Identifier: AGPL-3.0-only - */ -import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; - -import { BaseDto } from '../utils/base.dto.'; - -export class ServerVersion { - @ApiProperty() - major: number; - @ApiProperty() - minor: number; - @ApiProperty() - patch: number; - @ApiPropertyOptional() - preRelease?: string; - @ApiPropertyOptional() - commit?: string; - @ApiProperty() - fullString: string; -} - -export class ServerStatusDto extends BaseDto { - @ApiProperty() - serverVersion: ServerVersion; - @ApiProperty() - onlineNotes: number; - @ApiProperty() - onlineUsers: number; - @ApiProperty() - distinctOnlineUsers: number; - @ApiProperty() - notesCount: number; - @ApiProperty() - registeredUsers: number; - @ApiProperty() - onlineRegisteredUsers: number; - @ApiProperty() - distinctOnlineRegisteredUsers: number; - @ApiProperty() - isConnectionBusy: boolean; - @ApiProperty() - connectionSocketQueueLength: number; - @ApiProperty() - isDisconnectBusy: boolean; - @ApiProperty() - disconnectSocketQueueLength: number; -} diff --git a/backend/src/notes/__snapshots__/notes.service.spec.ts.snap b/backend/src/notes/__snapshots__/notes.service.spec.ts.snap index d0d375454..f8c4c6036 100644 --- a/backend/src/notes/__snapshots__/notes.service.spec.ts.snap +++ b/backend/src/notes/__snapshots__/notes.service.spec.ts.snap @@ -12,7 +12,7 @@ exports[`NotesService toNoteDto works 1`] = ` "primaryAlias": true, }, ], - "createdAt": undefined, + "createdAt": "2019-02-04T20:34:12.000Z", "description": "mockDescription", "editedBy": [ "hardcoded", @@ -39,7 +39,7 @@ exports[`NotesService toNoteDto works 1`] = ` ], "title": "mockTitle", "updateUsername": "hardcoded", - "updatedAt": 2019-02-04T20:34:12.000Z, + "updatedAt": "2019-02-04T20:34:12.000Z", "version": undefined, "viewCount": 1337, }, @@ -55,7 +55,7 @@ exports[`NotesService toNoteMetadataDto works 1`] = ` "primaryAlias": true, }, ], - "createdAt": undefined, + "createdAt": "2019-02-04T20:34:12.000Z", "description": "mockDescription", "editedBy": [ "hardcoded", @@ -82,7 +82,7 @@ exports[`NotesService toNoteMetadataDto works 1`] = ` ], "title": "mockTitle", "updateUsername": "hardcoded", - "updatedAt": 2019-02-04T20:34:12.000Z, + "updatedAt": "2019-02-04T20:34:12.000Z", "version": undefined, "viewCount": 1337, } diff --git a/backend/src/notes/alias-create.dto.ts b/backend/src/notes/alias-create.dto.ts deleted file mode 100644 index 6c04f4604..000000000 --- a/backend/src/notes/alias-create.dto.ts +++ /dev/null @@ -1,25 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) - * - * SPDX-License-Identifier: AGPL-3.0-only - */ -import { ApiProperty } from '@nestjs/swagger'; -import { IsString } from 'class-validator'; - -import { BaseDto } from '../utils/base.dto.'; - -export class AliasCreateDto extends BaseDto { - /** - * The note id or alias, which identifies the note the alias should be added to - */ - @IsString() - @ApiProperty() - noteIdOrAlias: string; - - /** - * The new alias - */ - @IsString() - @ApiProperty() - newAlias: string; -} diff --git a/backend/src/notes/alias-update.dto.ts b/backend/src/notes/alias-update.dto.ts deleted file mode 100644 index 6ce7bf330..000000000 --- a/backend/src/notes/alias-update.dto.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) - * - * SPDX-License-Identifier: AGPL-3.0-only - */ -import { ApiProperty } from '@nestjs/swagger'; -import { IsBoolean } from 'class-validator'; - -import { BaseDto } from '../utils/base.dto.'; - -export class AliasUpdateDto extends BaseDto { - /** - * Whether the alias should become the primary alias or not - */ - @IsBoolean() - @ApiProperty() - primaryAlias: boolean; -} diff --git a/backend/src/notes/alias.dto.ts b/backend/src/notes/alias.dto.ts deleted file mode 100644 index 1f5c2af29..000000000 --- a/backend/src/notes/alias.dto.ts +++ /dev/null @@ -1,32 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) - * - * SPDX-License-Identifier: AGPL-3.0-only - */ -import { ApiProperty } from '@nestjs/swagger'; -import { IsBoolean, IsString } from 'class-validator'; - -import { BaseDto } from '../utils/base.dto.'; - -export class AliasDto extends BaseDto { - /** - * The name of the alias - */ - @IsString() - @ApiProperty() - name: string; - - /** - * Is the alias the primary alias or not - */ - @IsBoolean() - @ApiProperty() - primaryAlias: boolean; - - /** - * The public id of the note the alias is associated with - */ - @IsString() - @ApiProperty() - noteId: string; -} diff --git a/backend/src/notes/alias.service.ts b/backend/src/notes/alias.service.ts index 2b2a7e055..85bcdb963 100644 --- a/backend/src/notes/alias.service.ts +++ b/backend/src/notes/alias.service.ts @@ -1,8 +1,9 @@ /* - * 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 */ +import { AliasDto } from '@hedgedoc/commons'; import { forwardRef, Inject, Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; @@ -12,7 +13,6 @@ import { PrimaryAliasDeletionForbiddenError, } from '../errors/errors'; import { ConsoleLoggerService } from '../logger/console-logger.service'; -import { AliasDto } from './alias.dto'; import { Alias } from './alias.entity'; import { Note } from './note.entity'; import { NotesService } from './notes.service'; diff --git a/backend/src/notes/note-metadata.dto.ts b/backend/src/notes/note-metadata.dto.ts deleted file mode 100644 index cbb6a5e78..000000000 --- a/backend/src/notes/note-metadata.dto.ts +++ /dev/null @@ -1,158 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) - * - * SPDX-License-Identifier: AGPL-3.0-only - */ -import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { Type } from 'class-transformer'; -import { - IsArray, - IsDate, - IsNumber, - IsOptional, - IsString, - ValidateNested, -} from 'class-validator'; - -import { BaseDto } from '../utils/base.dto.'; -import { AliasDto } from './alias.dto'; -import { NotePermissionsDto } from './note-permissions.dto'; - -export class NoteMetadataDto extends BaseDto { - /** - * ID of the note - */ - @IsString() - @ApiProperty() - id: string; - - /** - * All aliases of the note (including the primaryAlias) - */ - @Type(() => AliasDto) - @IsArray() - @ValidateNested({ each: true }) - @ApiProperty({ isArray: true, type: AliasDto }) - aliases: AliasDto[]; - - /** - * The primary adress of the note - * If at least one alias is set, this is the primary alias - * If no alias is set, this is the note's ID - */ - @IsString() - @ApiProperty() - primaryAddress: string; - - /** - * Title of the note - * Does not contain any markup but might be empty - * @example "Shopping List" - */ - @IsString() - @ApiProperty() - title: string; - - /** - * Description of the note - * Does not contain any markup but might be empty - * @example Everything I want to buy - */ - @IsString() - @ApiProperty() - description: string; - - /** - * List of tags assigned to this note - * @example "['shopping', 'personal'] - */ - @IsArray() - @IsString({ each: true }) - @ApiProperty({ isArray: true, type: String }) - tags: string[]; - - @IsNumber() - @ApiProperty() - version: number; - - /** - * Datestring of the last time this note was updated - * @example "2020-12-01 12:23:34" - */ - @IsDate() - @Type(() => Date) - @ApiProperty() - updatedAt: Date; - - /** - * User that last edited the note - */ - // eslint-disable-next-line @darraghor/nestjs-typed/api-property-matches-property-optionality - @ApiPropertyOptional() - @IsString() - @IsOptional() - updateUsername: string | null; - - /** - * Counts how many times the published note has been viewed - * @example 42 - */ - @IsNumber() - @ApiProperty() - viewCount: number; - - /** - * Datestring of the time this note was created - * @example "2020-12-01 12:23:34" - */ - @IsDate() - @Type(() => Date) - @ApiProperty() - createdAt: Date; - - /** - * List of usernames that edited the note - * @example "['john.smith', 'jane.smith']" - */ - @IsArray() - @IsString({ each: true }) - @ApiProperty({ isArray: true, type: String }) - editedBy: string[]; - - /** - * Permissions currently in effect for the note - */ - @ValidateNested() - @Type(() => NotePermissionsDto) - @ApiProperty({ type: NotePermissionsDto }) - permissions: NotePermissionsDto; -} - -export class NoteMetadataUpdateDto { - /** - * New title of the note - * Can not contain any markup and might be empty - * @example "Shopping List" - */ - @IsString() - @ApiProperty() - title: string; - - /** - * New description of the note - * Can not contain any markup but might be empty - * @example Everything I want to buy - */ - @IsString() - @ApiProperty() - description: string; - - /** - * New list of tags assigned to this note - * @example "['shopping', 'personal'] - */ - @IsArray() - @IsString({ each: true }) - @ApiProperty({ isArray: true, type: String }) - tags: string[]; -} diff --git a/backend/src/notes/note-permissions.dto.ts b/backend/src/notes/note-permissions.dto.ts deleted file mode 100644 index b462d79df..000000000 --- a/backend/src/notes/note-permissions.dto.ts +++ /dev/null @@ -1,143 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file) - * - * SPDX-License-Identifier: AGPL-3.0-only - */ -import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { Type } from 'class-transformer'; -import { - IsArray, - IsBoolean, - IsLowercase, - IsOptional, - IsString, - ValidateNested, -} from 'class-validator'; - -import { BaseDto } from '../utils/base.dto.'; -import { Username } from '../utils/username'; - -export class NoteUserPermissionEntryDto extends BaseDto { - /** - * Username of the User this permission applies to - */ - @Type(() => String) - @IsString() - @IsLowercase() - @ApiProperty() - username: Username; - - /** - * True if the user is allowed to edit the note - * @example false - */ - @IsBoolean() - @ApiProperty() - canEdit: boolean; -} - -export class NoteUserPermissionUpdateDto { - /** - * Username of the user this permission should apply to - * @example "john.smith" - */ - @Type(() => String) - @IsString() - @IsLowercase() - @ApiProperty() - username: Username; - - /** - * True if the user should be allowed to edit the note - * @example false - */ - @IsBoolean() - @ApiProperty() - canEdit: boolean; -} - -export class NoteGroupPermissionEntryDto { - /** - * Name of the Group this permission applies to - */ - @IsString() - @ApiProperty() - groupName: string; - - /** - * True if the group members are allowed to edit the note - * @example false - */ - @IsBoolean() - @ApiProperty() - canEdit: boolean; -} - -export class NoteGroupPermissionUpdateDto { - /** - * Name of the group this permission should apply to - * @example "superheroes" - */ - @IsString() - @ApiProperty() - groupName: string; - - /** - * True if the group members should be allowed to edit the note - * @example false - */ - @IsBoolean() - @ApiProperty() - canEdit: boolean; -} - -export class NotePermissionsDto { - /** - * Username of the User this permission applies to - */ - // nestjs-typed does not detect '| null' types as optional - // eslint-disable-next-line @darraghor/nestjs-typed/api-property-matches-property-optionality - @Type(() => String) - @IsString() - @ApiPropertyOptional() - @IsOptional() - owner: string | null; - - /** - * List of users the note is shared with - */ - @ValidateNested({ each: true }) - @IsArray() - @Type(() => NoteUserPermissionEntryDto) - @ApiProperty({ isArray: true, type: NoteUserPermissionEntryDto }) - sharedToUsers: NoteUserPermissionEntryDto[]; - - /** - * List of groups the note is shared with - */ - @ValidateNested({ each: true }) - @IsArray() - @Type(() => NoteGroupPermissionEntryDto) - @ApiProperty({ isArray: true, type: NoteGroupPermissionEntryDto }) - sharedToGroups: NoteGroupPermissionEntryDto[]; -} - -export class NotePermissionsUpdateDto { - /** - * List of users the note should be shared with - */ - @IsArray() - @ValidateNested({ each: true }) - @Type(() => NoteUserPermissionUpdateDto) - @ApiProperty({ isArray: true, type: NoteUserPermissionUpdateDto }) - sharedToUsers: NoteUserPermissionUpdateDto[]; - - /** - * List of groups the note should be shared with - */ - @IsArray() - @ValidateNested({ each: true }) - @Type(() => NoteGroupPermissionUpdateDto) - @ApiProperty({ isArray: true, type: NoteGroupPermissionUpdateDto }) - sharedToGroups: NoteGroupPermissionUpdateDto[]; -} diff --git a/backend/src/notes/note.dto.ts b/backend/src/notes/note.dto.ts deleted file mode 100644 index deff740f4..000000000 --- a/backend/src/notes/note.dto.ts +++ /dev/null @@ -1,39 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) - * - * SPDX-License-Identifier: AGPL-3.0-only - */ -import { ApiProperty } from '@nestjs/swagger'; -import { Type } from 'class-transformer'; -import { IsArray, IsString, ValidateNested } from 'class-validator'; - -import { EditDto } from '../revisions/edit.dto'; -import { BaseDto } from '../utils/base.dto.'; -import { NoteMetadataDto } from './note-metadata.dto'; - -export class NoteDto extends BaseDto { - /** - * Markdown content of the note - * @example "# I am a heading" - */ - @IsString() - @ApiProperty() - content: string; - - /** - * Metadata of the note - */ - @Type(() => NoteMetadataDto) - @ValidateNested() - @ApiProperty({ type: NoteMetadataDto }) - metadata: NoteMetadataDto; - - /** - * Edit information of this note - */ - @IsArray() - @ValidateNested({ each: true }) - @Type(() => EditDto) - @ApiProperty({ isArray: true, type: EditDto }) - editedByAtPosition: EditDto[]; -} diff --git a/backend/src/notes/note.media-deletion.dto.ts b/backend/src/notes/note.media-deletion.dto.ts deleted file mode 100644 index 1d8eaa184..000000000 --- a/backend/src/notes/note.media-deletion.dto.ts +++ /dev/null @@ -1,20 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) - * - * SPDX-License-Identifier: AGPL-3.0-only - */ -import { ApiProperty } from '@nestjs/swagger'; -import { IsBoolean } from 'class-validator'; - -import { BaseDto } from '../utils/base.dto.'; - -export class NoteMediaDeletionDto extends BaseDto { - /** - * Should the associated mediaUploads be keept - * @default false - * @example false - */ - @IsBoolean() - @ApiProperty() - keepMedia: boolean; -} diff --git a/backend/src/notes/notes.service.spec.ts b/backend/src/notes/notes.service.spec.ts index 7159430a7..f71ad0337 100644 --- a/backend/src/notes/notes.service.spec.ts +++ b/backend/src/notes/notes.service.spec.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 */ @@ -271,6 +271,7 @@ describe('NotesService', () => { const note = Mock.of<Note>({ revisions: Promise.resolve([revision]), aliases: Promise.resolve([]), + createdAt: new Date(1549312452000), }); mockRevisionService(note, revision); diff --git a/backend/src/notes/notes.service.ts b/backend/src/notes/notes.service.ts index 0bc8e3691..fdd366d7c 100644 --- a/backend/src/notes/notes.service.ts +++ b/backend/src/notes/notes.service.ts @@ -1,8 +1,13 @@ /* - * 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 */ +import { + NoteDto, + NoteMetadataDto, + NotePermissionsDto, +} from '@hedgedoc/commons'; import { Optional } from '@mrdrogdrog/optional'; import { forwardRef, Inject, Injectable } from '@nestjs/common'; import { EventEmitter2 } from '@nestjs/event-emitter'; @@ -30,9 +35,6 @@ import { User } from '../users/user.entity'; import { UsersService } from '../users/users.service'; import { Alias } from './alias.entity'; import { AliasService } from './alias.service'; -import { NoteMetadataDto } from './note-metadata.dto'; -import { NotePermissionsDto } from './note-permissions.dto'; -import { NoteDto } from './note.dto'; import { Note } from './note.entity'; import { Tag } from './tag.entity'; import { getPrimaryAlias } from './utils'; @@ -427,11 +429,11 @@ export class NotesService { title: latestRevision.title, description: latestRevision.description, tags: (await latestRevision.tags).map((tag) => tag.name), - createdAt: note.createdAt, + createdAt: note.createdAt.toISOString(), editedBy: (await this.getAuthorUsers(note)).map((user) => user.username), permissions: await this.toNotePermissionsDto(note), version: note.version, - updatedAt: latestRevision.createdAt, + updatedAt: latestRevision.createdAt.toISOString(), updateUsername: updateUser ? updateUser.username : null, viewCount: note.viewCount, }; diff --git a/backend/src/permissions/permissions.service.spec.ts b/backend/src/permissions/permissions.service.spec.ts index 5e20804d5..c07138bc9 100644 --- a/backend/src/permissions/permissions.service.spec.ts +++ b/backend/src/permissions/permissions.service.spec.ts @@ -1,8 +1,13 @@ /* - * 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 */ +import { + GuestAccess, + NoteGroupPermissionUpdateDto, + NoteUserPermissionUpdateDto, +} from '@hedgedoc/commons'; import { ConfigModule } from '@nestjs/config'; import { EventEmitter2, EventEmitterModule } from '@nestjs/event-emitter'; import { Test, TestingModule } from '@nestjs/testing'; @@ -14,7 +19,6 @@ import { ApiToken } from '../api-token/api-token.entity'; import { Identity } from '../auth/identity.entity'; import { Author } from '../authors/author.entity'; import { DefaultAccessLevel } from '../config/default-access-level.enum'; -import { GuestAccess } from '../config/guest_access.enum'; import appConfigMock from '../config/mock/app.config.mock'; import authConfigMock from '../config/mock/auth.config.mock'; import databaseConfigMock from '../config/mock/database.config.mock'; @@ -31,10 +35,6 @@ import { GroupsService } from '../groups/groups.service'; import { LoggerModule } from '../logger/logger.module'; import { MediaUpload } from '../media/media-upload.entity'; import { Alias } from '../notes/alias.entity'; -import { - NoteGroupPermissionUpdateDto, - NoteUserPermissionUpdateDto, -} from '../notes/note-permissions.dto'; import { Note } from '../notes/note.entity'; import { NotesModule } from '../notes/notes.module'; import { Tag } from '../notes/tag.entity'; @@ -461,12 +461,15 @@ describe('PermissionsService', () => { }); describe('updateNotePermissions', () => { - const userPermissionUpdate = new NoteUserPermissionUpdateDto(); - userPermissionUpdate.username = 'hardcoded'; - userPermissionUpdate.canEdit = true; - const groupPermissionUpdate = new NoteGroupPermissionUpdateDto(); - groupPermissionUpdate.groupName = 'testGroup'; - groupPermissionUpdate.canEdit = false; + const userPermissionUpdate: NoteUserPermissionUpdateDto = { + username: 'hardcoded', + canEdit: true, + }; + + const groupPermissionUpdate: NoteGroupPermissionUpdateDto = { + groupName: 'testGroup', + canEdit: false, + }; const user = User.create(userPermissionUpdate.username, 'Testy') as User; const group = Group.create( groupPermissionUpdate.groupName, diff --git a/backend/src/permissions/permissions.service.ts b/backend/src/permissions/permissions.service.ts index f2f7a79a5..386872a32 100644 --- a/backend/src/permissions/permissions.service.ts +++ b/backend/src/permissions/permissions.service.ts @@ -1,14 +1,14 @@ /* - * 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 */ +import { GuestAccess, NotePermissionsUpdateDto } from '@hedgedoc/commons'; import { Inject, Injectable } from '@nestjs/common'; import { EventEmitter2 } from '@nestjs/event-emitter'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; -import { GuestAccess } from '../config/guest_access.enum'; import noteConfiguration, { NoteConfig } from '../config/note.config'; import { PermissionsUpdateInconsistentError } from '../errors/errors'; import { NoteEvent, NoteEventMap } from '../events'; @@ -16,7 +16,6 @@ import { Group } from '../groups/group.entity'; import { GroupsService } from '../groups/groups.service'; import { ConsoleLoggerService } from '../logger/console-logger.service'; import { MediaUpload } from '../media/media-upload.entity'; -import { NotePermissionsUpdateDto } from '../notes/note-permissions.dto'; import { Note } from '../notes/note.entity'; import { User } from '../users/user.entity'; import { UsersService } from '../users/users.service'; diff --git a/backend/src/permissions/utils/convert-guest-access-to-note-permission.spec.ts b/backend/src/permissions/utils/convert-guest-access-to-note-permission.spec.ts index 04bef2ef7..8dc4eec7d 100644 --- a/backend/src/permissions/utils/convert-guest-access-to-note-permission.spec.ts +++ b/backend/src/permissions/utils/convert-guest-access-to-note-permission.spec.ts @@ -1,9 +1,10 @@ /* - * 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 */ -import { GuestAccess } from '../../config/guest_access.enum'; +import { GuestAccess } from '@hedgedoc/commons'; + import { NotePermission } from '../note-permission.enum'; import { convertGuestAccessToNotePermission } from './convert-guest-access-to-note-permission'; diff --git a/backend/src/permissions/utils/convert-guest-access-to-note-permission.ts b/backend/src/permissions/utils/convert-guest-access-to-note-permission.ts index 39dc7dd35..f016bf01d 100644 --- a/backend/src/permissions/utils/convert-guest-access-to-note-permission.ts +++ b/backend/src/permissions/utils/convert-guest-access-to-note-permission.ts @@ -1,9 +1,10 @@ /* - * 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 */ -import { GuestAccess } from '../../config/guest_access.enum'; +import { GuestAccess } from '@hedgedoc/commons'; + import { NotePermission } from '../note-permission.enum'; /** diff --git a/backend/src/realtime/realtime-note/realtime-connection.spec.ts b/backend/src/realtime/realtime-note/realtime-connection.spec.ts index 42a93aaec..486d548c2 100644 --- a/backend/src/realtime/realtime-note/realtime-connection.spec.ts +++ b/backend/src/realtime/realtime-note/realtime-connection.spec.ts @@ -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 */ @@ -13,7 +13,6 @@ import { Mock } from 'ts-mockery'; import { Note } from '../../notes/note.entity'; import { User } from '../../users/user.entity'; -import { Username } from '../../utils/username'; import * as NameRandomizerModule from './random-word-lists/name-randomizer'; import { RealtimeConnection } from './realtime-connection'; import { RealtimeNote } from './realtime-note'; @@ -40,7 +39,7 @@ describe('websocket connection', () => { let mockedUser: User; let mockedMessageTransporter: MessageTransporter; - const mockedUserName: Username = 'mocked-user-name'; + const mockedUserName: string = 'mocked-user-name'; const mockedDisplayName = 'mockedDisplayName'; beforeEach(() => { diff --git a/backend/src/realtime/realtime-note/realtime-user-status-adapter.ts b/backend/src/realtime/realtime-note/realtime-user-status-adapter.ts index 7e9b38e31..e0ea326fb 100644 --- a/backend/src/realtime/realtime-note/realtime-user-status-adapter.ts +++ b/backend/src/realtime/realtime-note/realtime-user-status-adapter.ts @@ -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 */ @@ -11,8 +11,6 @@ import { } from '@hedgedoc/commons'; import { Listener } from 'eventemitter2'; -import { Username } from '../../utils/username'; - export type OtherAdapterCollector = () => RealtimeUserStatusAdapter[]; /** @@ -22,7 +20,7 @@ export class RealtimeUserStatusAdapter { private readonly realtimeUser: RealtimeUser; constructor( - private readonly username: Username | null, + private readonly username: string | null, private readonly displayName: string, private collectOtherAdapters: OtherAdapterCollector, private messageTransporter: MessageTransporter, diff --git a/backend/src/realtime/realtime-note/test-utils/mock-connection.ts b/backend/src/realtime/realtime-note/test-utils/mock-connection.ts index 7d5da6db4..f32badb14 100644 --- a/backend/src/realtime/realtime-note/test-utils/mock-connection.ts +++ b/backend/src/realtime/realtime-note/test-utils/mock-connection.ts @@ -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 */ @@ -10,7 +10,6 @@ import { import { Mock } from 'ts-mockery'; import { User } from '../../../users/user.entity'; -import { Username } from '../../../utils/username'; import { RealtimeConnection } from '../realtime-connection'; import { RealtimeNote } from '../realtime-note'; import { RealtimeUserStatusAdapter } from '../realtime-user-status-adapter'; @@ -22,13 +21,13 @@ enum RealtimeUserState { WITH_READONLY, } -const MOCK_FALLBACK_USERNAME: Username = 'mock'; +const MOCK_FALLBACK_USERNAME: string = 'mock'; /** * Creates a mocked {@link RealtimeConnection realtime connection}. */ export class MockConnectionBuilder { - private username: Username | null; + private username: string | null; private displayName: string | undefined; private includeRealtimeUserStatus: RealtimeUserState = RealtimeUserState.WITHOUT; @@ -51,7 +50,7 @@ export class MockConnectionBuilder { * * @param username the username of the mocked user. If this value is omitted then the builder will user a {@link MOCK_FALLBACK_USERNAME fallback}. */ - public withLoggedInUser(username?: Username): this { + public withLoggedInUser(username?: string): this { const newUsername = username ?? MOCK_FALLBACK_USERNAME; this.username = newUsername; this.displayName = newUsername; diff --git a/backend/src/realtime/websocket/websocket.gateway.spec.ts b/backend/src/realtime/websocket/websocket.gateway.spec.ts index 2fda7879c..fbfed1266 100644 --- a/backend/src/realtime/websocket/websocket.gateway.spec.ts +++ b/backend/src/realtime/websocket/websocket.gateway.spec.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 */ @@ -41,7 +41,6 @@ import { SessionService } from '../../sessions/session.service'; import { User } from '../../users/user.entity'; import { UsersModule } from '../../users/users.module'; import { UsersService } from '../../users/users.service'; -import { Username } from '../../utils/username'; import * as websocketConnectionModule from '../realtime-note/realtime-connection'; import { RealtimeConnection } from '../realtime-note/realtime-connection'; import { RealtimeNote } from '../realtime-note/realtime-note'; @@ -166,7 +165,7 @@ describe('Websocket gateway', () => { ), ); - const mockUsername: Username = 'mock-username'; + const mockUsername: string = 'mock-username'; jest .spyOn(sessionService, 'fetchUsernameForSessionId') .mockImplementation((sessionId: string) => diff --git a/backend/src/realtime/websocket/websocket.gateway.ts b/backend/src/realtime/websocket/websocket.gateway.ts index ad0466d46..5968f9c9b 100644 --- a/backend/src/realtime/websocket/websocket.gateway.ts +++ b/backend/src/realtime/websocket/websocket.gateway.ts @@ -6,7 +6,6 @@ import { DisconnectReason, MessageTransporter, - NotePermissions, userCanEdit, } from '@hedgedoc/commons'; import { OnGatewayConnection, WebSocketGateway } from '@nestjs/websockets'; @@ -92,10 +91,7 @@ export class WebsocketGateway implements OnGatewayConnection { ); const permissions = await this.noteService.toNotePermissionsDto(note); - const acceptEdits: boolean = userCanEdit( - permissions as NotePermissions, - user?.username, - ); + const acceptEdits: boolean = userCanEdit(permissions, user?.username); const connection = new RealtimeConnection( websocketTransporter, diff --git a/backend/src/revisions/__snapshots__/revisions.service.spec.ts.snap b/backend/src/revisions/__snapshots__/revisions.service.spec.ts.snap index 815b9ac71..961fa9b4b 100644 --- a/backend/src/revisions/__snapshots__/revisions.service.spec.ts.snap +++ b/backend/src/revisions/__snapshots__/revisions.service.spec.ts.snap @@ -77,14 +77,14 @@ exports[`RevisionsService toRevisionDto converts a revision 1`] = ` "mockusername", ], "content": "mockContent", - "createdAt": 2020-05-20T09:58:00.000Z, + "createdAt": "2020-05-20T09:58:00.000Z", "description": "mockDescription", "edits": [ { - "createdAt": 2020-03-04T22:32:00.000Z, - "endPos": 93, - "startPos": 34, - "updatedAt": 2021-02-10T12:23:00.000Z, + "createdAt": "2020-03-04T22:32:00.000Z", + "endPosition": 93, + "startPosition": 34, + "updatedAt": "2021-02-10T12:23:00.000Z", "username": "mockusername", }, ], @@ -104,7 +104,7 @@ exports[`RevisionsService toRevisionMetadataDto converts a revision 1`] = ` "authorUsernames": [ "mockusername", ], - "createdAt": 2020-05-20T09:58:00.000Z, + "createdAt": "2020-05-20T09:58:00.000Z", "description": "mockDescription", "id": 3246, "length": 1854, diff --git a/backend/src/revisions/edit.dto.ts b/backend/src/revisions/edit.dto.ts deleted file mode 100644 index fcc6facdf..000000000 --- a/backend/src/revisions/edit.dto.ts +++ /dev/null @@ -1,62 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) - * - * SPDX-License-Identifier: AGPL-3.0-only - */ -import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { Type } from 'class-transformer'; -import { IsDate, IsNumber, IsOptional, IsString, Min } from 'class-validator'; - -import { UserInfoDto } from '../users/user-info.dto'; -import { BaseDto } from '../utils/base.dto.'; - -export class EditDto extends BaseDto { - /** - * Username of the user who authored this section - * Is `null` if the user is anonymous - * @example "john.smith" - */ - // nestjs-typed does not detect '| null' types as optional - // eslint-disable-next-line @darraghor/nestjs-typed/api-property-matches-property-optionality - @IsString() - @IsOptional() - @ApiPropertyOptional() - username: UserInfoDto['username'] | null; - - /** - * Character index of the start of this section - * @example 102 - */ - @IsNumber() - @Min(0) - @ApiProperty() - startPos: number; - - /** - * Character index of the end of this section - * Must be greater than {@link startPos} - * @example 154 - */ - @IsNumber() - @Min(0) - @ApiProperty() - endPos: number; - - /** - * Datestring of the time this section was created - * @example "2020-12-01 12:23:34" - */ - @IsDate() - @Type(() => Date) - @ApiProperty() - createdAt: Date; - - /** - * Datestring of the last time this section was edited - * @example "2020-12-01 12:23:34" - */ - @IsDate() - @Type(() => Date) - @ApiProperty() - updatedAt: Date; -} diff --git a/backend/src/revisions/edit.service.ts b/backend/src/revisions/edit.service.ts index 4cdf731fa..99882473d 100644 --- a/backend/src/revisions/edit.service.ts +++ b/backend/src/revisions/edit.service.ts @@ -1,11 +1,11 @@ /* - * 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 */ +import { EditDto } from '@hedgedoc/commons'; import { Injectable } from '@nestjs/common'; -import { EditDto } from './edit.dto'; import { Edit } from './edit.entity'; @Injectable() @@ -15,10 +15,10 @@ export class EditService { return { username: authorUser ? authorUser.username : null, - startPos: edit.startPos, - endPos: edit.endPos, - createdAt: edit.createdAt, - updatedAt: edit.updatedAt, + startPosition: edit.startPos, + endPosition: edit.endPos, + createdAt: edit.createdAt.toISOString(), + updatedAt: edit.updatedAt.toISOString(), }; } } diff --git a/backend/src/revisions/revision-metadata.dto.ts b/backend/src/revisions/revision-metadata.dto.ts deleted file mode 100644 index 347edf470..000000000 --- a/backend/src/revisions/revision-metadata.dto.ts +++ /dev/null @@ -1,81 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file) - * - * SPDX-License-Identifier: AGPL-3.0-only - */ -import { ApiProperty } from '@nestjs/swagger'; -import { Type } from 'class-transformer'; -import { IsArray, IsDate, IsNumber, IsString } from 'class-validator'; - -import { BaseDto } from '../utils/base.dto.'; -import { Revision } from './revision.entity'; - -export class RevisionMetadataDto extends BaseDto { - /** - * ID of this revision - * @example 13 - */ - @Type(() => Number) - @IsNumber() - @ApiProperty() - id: Revision['id']; - - /** - * Datestring of the time this revision was created - * @example "2020-12-01 12:23:34" - */ - @IsDate() - @Type(() => Date) - @ApiProperty() - createdAt: Date; - - /** - * Number of characters in this revision - * @example 142 - */ - @IsNumber() - @ApiProperty() - length: number; - - /** - * List of the usernames that have contributed to this revision - * Does not include anonymous users - */ - @IsString() - @ApiProperty({ isArray: true, type: String }) - authorUsernames: string[]; - - /** - * Count of anonymous users that have contributed to this revision - */ - @IsNumber() - @ApiProperty() - anonymousAuthorCount: number; - - /** - * Title of the note - * Does not contain any markup but might be empty - * @example "Shopping List" - */ - @IsString() - @ApiProperty() - title: string; - - /** - * Description of the note - * Does not contain any markup but might be empty - * @example Everything I want to buy - */ - @IsString() - @ApiProperty() - description: string; - - /** - * List of tags assigned to this note - * @example "['shopping', 'personal'] - */ - @IsArray() - @IsString({ each: true }) - @ApiProperty({ isArray: true, type: String }) - tags: string[]; -} diff --git a/backend/src/revisions/revision.dto.ts b/backend/src/revisions/revision.dto.ts deleted file mode 100644 index 53269b8be..000000000 --- a/backend/src/revisions/revision.dto.ts +++ /dev/null @@ -1,36 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) - * - * SPDX-License-Identifier: AGPL-3.0-only - */ -import { ApiProperty } from '@nestjs/swagger'; -import { Type } from 'class-transformer'; -import { IsString, ValidateNested } from 'class-validator'; - -import { EditDto } from './edit.dto'; -import { RevisionMetadataDto } from './revision-metadata.dto'; - -export class RevisionDto extends RevisionMetadataDto { - /** - * Markdown content of the revision - * @example "# I am a heading" - */ - @IsString() - @ApiProperty() - content: string; - - /** - * Patch from the preceding revision to this one - */ - @IsString() - @ApiProperty() - patch: string; - - /** - * All edit objects which are used in the revision. - */ - @Type(() => EditDto) - @ValidateNested({ each: true }) - @ApiProperty({ isArray: true, type: EditDto }) - edits: EditDto[]; -} diff --git a/backend/src/revisions/revisions.service.ts b/backend/src/revisions/revisions.service.ts index fc0cac81d..c00a9446f 100644 --- a/backend/src/revisions/revisions.service.ts +++ b/backend/src/revisions/revisions.service.ts @@ -1,8 +1,9 @@ /* - * 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 */ +import { RevisionDto, RevisionMetadataDto } from '@hedgedoc/commons'; import { Inject, Injectable } from '@nestjs/common'; import { Cron, Timeout } from '@nestjs/schedule'; import { InjectRepository } from '@nestjs/typeorm'; @@ -15,8 +16,6 @@ import { ConsoleLoggerService } from '../logger/console-logger.service'; import { Note } from '../notes/note.entity'; import { Tag } from '../notes/tag.entity'; import { EditService } from './edit.service'; -import { RevisionMetadataDto } from './revision-metadata.dto'; -import { RevisionDto } from './revision.dto'; import { Revision } from './revision.entity'; import { extractRevisionMetadataFromContent } from './utils/extract-revision-metadata-from-content'; @@ -136,7 +135,7 @@ export class RevisionsService { return { id: revision.id, length: revision.length, - createdAt: revision.createdAt, + createdAt: revision.createdAt.toISOString(), authorUsernames: revisionUserInfo.usernames, anonymousAuthorCount: revisionUserInfo.anonymousUserCount, title: revision.title, @@ -151,7 +150,7 @@ export class RevisionsService { id: revision.id, content: revision.content, length: revision.length, - createdAt: revision.createdAt, + createdAt: revision.createdAt.toISOString(), title: revision.title, tags: (await revision.tags).map((tag) => tag.name), description: revision.description, diff --git a/backend/src/seed.ts b/backend/src/seed.ts index d5ec98c07..eb1c831de 100644 --- a/backend/src/seed.ts +++ b/backend/src/seed.ts @@ -1,13 +1,13 @@ /* - * 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 */ +import { ProviderType } from '@hedgedoc/commons'; import { DataSource } from 'typeorm'; import { ApiToken } from './api-token/api-token.entity'; import { Identity } from './auth/identity.entity'; -import { ProviderType } from './auth/provider-type.enum'; import { Author } from './authors/author.entity'; import { Group } from './groups/group.entity'; import { HistoryEntry } from './history/history-entry.entity'; diff --git a/backend/src/sessions/session.service.ts b/backend/src/sessions/session.service.ts index 252342023..6ac7884c9 100644 --- a/backend/src/sessions/session.service.ts +++ b/backend/src/sessions/session.service.ts @@ -1,8 +1,9 @@ /* - * 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 */ +import { FullUserInfoDto, ProviderType } from '@hedgedoc/commons'; import { Optional } from '@mrdrogdrog/optional'; import { Inject, Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; @@ -12,16 +13,13 @@ import { unsign } from 'cookie-signature'; import { IncomingMessage } from 'http'; import { Repository } from 'typeorm'; -import { ProviderType } from '../auth/provider-type.enum'; import authConfiguration, { AuthConfig } from '../config/auth.config'; import { DatabaseType } from '../config/database-type.enum'; import databaseConfiguration, { DatabaseConfig, } from '../config/database.config'; import { ConsoleLoggerService } from '../logger/console-logger.service'; -import { FullUserInfoDto } from '../users/user-info.dto'; import { HEDGEDOC_SESSION } from '../utils/session'; -import { Username } from '../utils/username'; import { Session } from './session.entity'; export interface SessionState { @@ -29,7 +27,7 @@ export interface SessionState { cookie: unknown; /** Contains the username if logged in completely, is undefined when not being logged in */ - username?: Username; + username?: string; /** The auth provider that is used for the current login or pending login */ authProviderType?: ProviderType; @@ -87,7 +85,7 @@ export class SessionService { * @param sessionId The session id for which the owning user should be found * @return A Promise that either resolves with the username or rejects with an error */ - fetchUsernameForSessionId(sessionId: string): Promise<Username | undefined> { + fetchUsernameForSessionId(sessionId: string): Promise<string | undefined> { return new Promise((resolve, reject) => { this.logger.debug( `Fetching username for sessionId ${sessionId}`, @@ -102,7 +100,7 @@ export class SessionService { 'fetchUsernameForSessionId', ); if (error) return reject(error); - return resolve(result?.username as Username); + return resolve(result?.username); }, ); }); diff --git a/backend/src/users/user-info.dto.ts b/backend/src/users/user-info.dto.ts deleted file mode 100644 index 7246c69ce..000000000 --- a/backend/src/users/user-info.dto.ts +++ /dev/null @@ -1,77 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file) - * - * SPDX-License-Identifier: AGPL-3.0-only - */ -import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { Type } from 'class-transformer'; -import { IsLowercase, IsOptional, IsString } from 'class-validator'; - -import { BaseDto } from '../utils/base.dto.'; -import { Username } from '../utils/username'; - -export class UserInfoDto extends BaseDto { - /** - * The username - * @example "john.smith" - */ - @Type(() => String) - @IsString() - @IsLowercase() - @ApiProperty() - username: Username; - - /** - * The display name - * @example "John Smith" - */ - @IsString() - @ApiProperty() - displayName: string; - - /** - * URL of the profile picture - * @example "https://hedgedoc.example.com/uploads/johnsmith.png" - */ - @ApiPropertyOptional({ - format: 'uri', - }) - @IsOptional() - @IsString() - photoUrl?: string; -} - -/** - * This DTO contains all attributes of the standard UserInfoDto - * in addition to the email address. - */ -export class FullUserInfoDto extends UserInfoDto { - /** - * Email address of the user - * @example "john.smith@example.com" - */ - @ApiPropertyOptional({ - format: 'email', - }) - @IsOptional() - @IsString() - email?: string; -} - -export class FullUserInfoWithIdDto extends FullUserInfoDto { - /** - * The user's ID - * @example 42 - */ - @IsString() - id: string; -} - -export class UserLoginInfoDto extends UserInfoDto { - /** - * Identifier of the auth provider that was used to log in - */ - @ApiProperty() - @IsString() - authProvider: string; -} diff --git a/backend/src/users/user.entity.ts b/backend/src/users/user.entity.ts index 1003a995e..4378174b7 100644 --- a/backend/src/users/user.entity.ts +++ b/backend/src/users/user.entity.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 */ @@ -20,7 +20,6 @@ import { Group } from '../groups/group.entity'; import { HistoryEntry } from '../history/history-entry.entity'; import { MediaUpload } from '../media/media-upload.entity'; import { Note } from '../notes/note.entity'; -import { Username } from '../utils/username'; @Entity() export class User { @@ -30,7 +29,7 @@ export class User { @Column({ unique: true, }) - username: Username; + username: string; @Column() displayName: string; @@ -78,7 +77,7 @@ export class User { private constructor() {} public static create( - username: Username, + username: string, displayName: string, email?: string, photoUrl?: string, diff --git a/backend/src/users/username-check.dto.ts b/backend/src/users/username-check.dto.ts deleted file mode 100644 index 8709aea0b..000000000 --- a/backend/src/users/username-check.dto.ts +++ /dev/null @@ -1,21 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file) - * - * SPDX-License-Identifier: AGPL-3.0-only - */ -import { IsBoolean, IsLowercase, IsString } from 'class-validator'; - -import { BaseDto } from '../utils/base.dto.'; -import { Username } from '../utils/username'; - -export class UsernameCheckDto extends BaseDto { - // eslint-disable-next-line @darraghor/nestjs-typed/validated-non-primitive-property-needs-type-decorator - @IsString() - @IsLowercase() - username: Username; -} - -export class UsernameCheckResponseDto extends BaseDto { - @IsBoolean() - usernameAvailable: boolean; -} diff --git a/backend/src/users/users.service.spec.ts b/backend/src/users/users.service.spec.ts index ecd807b25..54b88b8ed 100644 --- a/backend/src/users/users.service.spec.ts +++ b/backend/src/users/users.service.spec.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 */ @@ -54,7 +54,7 @@ describe('UsersService', () => { .mockImplementationOnce(async (user: User): Promise<User> => user); }); it('successfully creates a user', async () => { - const user = await service.createUser(username, displayname); + const user = await service.createUser(username, displayname, null, null); expect(user.username).toEqual(username); expect(user.displayName).toEqual(displayname); }); @@ -64,11 +64,11 @@ describe('UsersService', () => { throw new Error(); }); // create first user with username - await service.createUser(username, displayname); + await service.createUser(username, displayname, null, null); // attempt to create second user with username - await expect(service.createUser(username, displayname)).rejects.toThrow( - AlreadyInDBError, - ); + await expect( + service.createUser(username, displayname, null, null), + ).rejects.toThrow(AlreadyInDBError); }); }); @@ -134,7 +134,7 @@ describe('UsersService', () => { expect(photoUrl).toEqual(photo); }); it('works if a user no photoUrl', () => { - user.photo = undefined; + user.photo = null; const photoUrl = service.getPhotoUrl(user); expect(photoUrl).toEqual(''); }); diff --git a/backend/src/users/users.service.ts b/backend/src/users/users.service.ts index 950af5fc0..8dd7b4c9b 100644 --- a/backend/src/users/users.service.ts +++ b/backend/src/users/users.service.ts @@ -1,9 +1,15 @@ /* - * 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 */ -import { REGEX_USERNAME } from '@hedgedoc/commons'; +import { + FullUserInfoDto, + LoginUserInfoDto, + ProviderType, + REGEX_USERNAME, + UserInfoDto, +} from '@hedgedoc/commons'; import { BadRequestException, Inject, Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; @@ -11,12 +17,6 @@ import { Repository } from 'typeorm'; import AuthConfiguration, { AuthConfig } from '../config/auth.config'; import { AlreadyInDBError, NotInDBError } from '../errors/errors'; import { ConsoleLoggerService } from '../logger/console-logger.service'; -import { Username } from '../utils/username'; -import { - FullUserInfoDto, - UserInfoDto, - UserLoginInfoDto, -} from './user-info.dto'; import { UserRelationEnum } from './user-relation.enum'; import { User } from './user.entity'; @@ -34,7 +34,7 @@ export class UsersService { /** * @async * Create a new user with a given username and displayName - * @param {Username} username - the username the new user shall have + * @param {string} username - the username the new user shall have * @param {string} displayName - the display name the new user shall have * @param {string} [email] - the email the new user shall have * @param {string} [photoUrl] - the photoUrl the new user shall have @@ -43,17 +43,22 @@ export class UsersService { * @throws {AlreadyInDBError} the username is already taken. */ async createUser( - username: Username, + username: string, displayName: string, - email?: string, - photoUrl?: string, + email: string | null, + photoUrl: string | null, ): Promise<User> { if (!REGEX_USERNAME.test(username)) { throw new BadRequestException( `The username '${username}' is not a valid username.`, ); } - const user = User.create(username, displayName, email, photoUrl); + const user = User.create( + username, + displayName, + email || undefined, + photoUrl || undefined, + ); try { return await this.userRepository.save(user); } catch { @@ -123,7 +128,7 @@ export class UsersService { * @param username - the username to check * @return {boolean} true if the user exists, false otherwise */ - async checkIfUserExists(username: Username): Promise<boolean> { + async checkIfUserExists(username: string): Promise<boolean> { const user = await this.userRepository.findOne({ where: { username: username }, }); @@ -133,12 +138,12 @@ export class UsersService { /** * @async * Get the user specified by the username - * @param {Username} username the username by which the user is specified + * @param {string} username the username by which the user is specified * @param {UserRelationEnum[]} [withRelations=[]] if the returned user object should contain certain relations * @return {User} the specified user */ async getUserByUsername( - username: Username, + username: string, withRelations: UserRelationEnum[] = [], ): Promise<User> { const user = await this.userRepository.findOne({ @@ -191,7 +196,7 @@ export class UsersService { }; } - toUserLoginInfoDto(user: User, authProvider: string): UserLoginInfoDto { - return { ...this.toUserDto(user), authProvider }; + toLoginUserInfoDto(user: User, authProvider: ProviderType): LoginUserInfoDto { + return { ...this.toFullUserDto(user), authProvider }; } } diff --git a/backend/src/utils/serverVersion.ts b/backend/src/utils/serverVersion.ts index d5b25b828..bbc0cfe59 100644 --- a/backend/src/utils/serverVersion.ts +++ b/backend/src/utils/serverVersion.ts @@ -1,30 +1,29 @@ /* - * SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file) + * SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file) * * SPDX-License-Identifier: AGPL-3.0-only */ +import { ServerVersionDto } from '@hedgedoc/commons'; import { Optional } from '@mrdrogdrog/optional'; import { promises as fs } from 'fs'; import { join as joinPath } from 'path'; -import { ServerVersion } from '../monitoring/server-status.dto'; - -let versionCache: ServerVersion | undefined = undefined; +let versionCache: ServerVersionDto | undefined = undefined; /** * Reads the HedgeDoc version from the root package.json. This is done only once per run. * - * @return {Promise<ServerVersion>} A Promise that contains the parsed server version. + * @return {Promise<ServerVersionDto>} 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. */ -export async function getServerVersionFromPackageJson(): Promise<ServerVersion> { +export async function getServerVersionFromPackageJson(): Promise<ServerVersionDto> { if (!versionCache) { versionCache = await parseVersionFromPackageJson(); } return versionCache; } -async function parseVersionFromPackageJson(): Promise<ServerVersion> { +async function parseVersionFromPackageJson(): Promise<ServerVersionDto> { const rawFileContent: string = await fs.readFile( joinPath(__dirname, '../../../package.json'), { encoding: 'utf8' }, diff --git a/backend/src/utils/timestamp.ts b/backend/src/utils/timestamp.ts deleted file mode 100644 index 4e427aab4..000000000 --- a/backend/src/utils/timestamp.ts +++ /dev/null @@ -1,7 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file) - * - * SPDX-License-Identifier: AGPL-3.0-only - */ - -export type TimestampMillis = number; diff --git a/backend/src/utils/username.ts b/backend/src/utils/username.ts deleted file mode 100644 index 4ea20fc70..000000000 --- a/backend/src/utils/username.ts +++ /dev/null @@ -1,11 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file) - * - * SPDX-License-Identifier: AGPL-3.0-only - */ - -export type Username = Lowercase<string>; - -export function makeUsernameLowercase(username: string): Username { - return username.toLowerCase() as Username; -} diff --git a/backend/test/private-api/alias.e2e-spec.ts b/backend/test/private-api/alias.e2e-spec.ts index e16e559dc..946724fef 100644 --- a/backend/test/private-api/alias.e2e-spec.ts +++ b/backend/test/private-api/alias.e2e-spec.ts @@ -1,12 +1,12 @@ /* - * 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 */ +import { AliasCreateDto, AliasUpdateDto } from '@hedgedoc/commons'; import request from 'supertest'; -import { AliasCreateDto } from '../../src/notes/alias-create.dto'; -import { AliasUpdateDto } from '../../src/notes/alias-update.dto'; +import { Note } from '../../src/notes/note.entity'; import { User } from '../../src/users/user.entity'; import { password1, @@ -183,11 +183,12 @@ describe('Alias', () => { }); }); it('if the property primaryAlias is false', async () => { - changeAliasDto.primaryAlias = false; await agent1 .put(`/api/private/alias/${newAlias}`) .set('Content-Type', 'application/json') - .send(changeAliasDto) + .send({ + primaryAlias: false, + }) .expect(400); }); it('if the user is not an owner', async () => { @@ -204,7 +205,7 @@ describe('Alias', () => { describe('DELETE /alias/{alias}', () => { const testAlias = 'aliasTest3'; const newAlias = 'normalAlias3'; - let note; + let note: Note; beforeEach(async () => { note = await testSetup.notesService.createNote( diff --git a/backend/test/private-api/auth.e2e-spec.ts b/backend/test/private-api/auth.e2e-spec.ts index c2ec53463..76c244c87 100644 --- a/backend/test/private-api/auth.e2e-spec.ts +++ b/backend/test/private-api/auth.e2e-spec.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 */ @@ -8,21 +8,18 @@ @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access */ +import { LoginDto, RegisterDto, UpdatePasswordDto } from '@hedgedoc/commons'; import request from 'supertest'; -import { LoginDto } from '../../src/auth/local/login.dto'; -import { RegisterDto } from '../../src/auth/local/register.dto'; -import { UpdatePasswordDto } from '../../src/auth/local/update-password.dto'; import { NotInDBError } from '../../src/errors/errors'; import { UserRelationEnum } from '../../src/users/user-relation.enum'; import { checkPassword } from '../../src/utils/password'; -import { Username } from '../../src/utils/username'; import { TestSetup, TestSetupBuilder } from '../test-setup'; describe('Auth', () => { let testSetup: TestSetup; - let username: Username; + let username: string; let displayName: string; let password: string; @@ -75,6 +72,8 @@ describe('Auth', () => { const conflictingUser = await testSetup.userService.createUser( conflictingUserName, displayName, + null, + null, ); const registrationDto: RegisterDto = { displayName: displayName, diff --git a/backend/test/private-api/groups.e2e-spec.ts b/backend/test/private-api/groups.e2e-spec.ts index e8093be8a..e03f234e0 100644 --- a/backend/test/private-api/groups.e2e-spec.ts +++ b/backend/test/private-api/groups.e2e-spec.ts @@ -1,12 +1,11 @@ /* - * 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 */ +import { GuestAccess, LoginDto } from '@hedgedoc/commons'; import request from 'supertest'; -import { LoginDto } from '../../src/auth/local/login.dto'; -import { GuestAccess } from '../../src/config/guest_access.enum'; import { createDefaultMockNoteConfig } from '../../src/config/mock/note.config.mock'; import { NoteConfig } from '../../src/config/note.config'; import { diff --git a/backend/test/private-api/history.e2e-spec.ts b/backend/test/private-api/history.e2e-spec.ts index 4d0a1a9b3..d15f72890 100644 --- a/backend/test/private-api/history.e2e-spec.ts +++ b/backend/test/private-api/history.e2e-spec.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 */ @@ -41,7 +41,7 @@ describe('History', () => { historyService = moduleRef.get(HistoryService); const userService = moduleRef.get(UsersService); localIdentityService = moduleRef.get(LocalService); - user = await userService.createUser(username, 'Testy'); + user = await userService.createUser(username, 'Testy', null, null); await localIdentityService.createLocalIdentity(user, password); const notesService = moduleRef.get(NotesService); note = await notesService.createNote(content, user, 'note'); diff --git a/backend/test/private-api/me.e2e-spec.ts b/backend/test/private-api/me.e2e-spec.ts index 36994bc5f..16fc93abe 100644 --- a/backend/test/private-api/me.e2e-spec.ts +++ b/backend/test/private-api/me.e2e-spec.ts @@ -1,14 +1,14 @@ /* - * 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 */ +import { LoginUserInfoDto, ProviderType } from '@hedgedoc/commons'; import { promises as fs } from 'fs'; import request from 'supertest'; import { NotInDBError } from '../../src/errors/errors'; import { Note } from '../../src/notes/note.entity'; -import { UserLoginInfoDto } from '../../src/users/user-info.dto'; import { User } from '../../src/users/user.entity'; import { TestSetup, TestSetupBuilder } from '../test-setup'; @@ -32,7 +32,12 @@ describe('Me', () => { const password = 'AHardcodedStrongP@ssword123'; await testSetup.app.init(); - user = await testSetup.userService.createUser(username, 'Testy'); + user = await testSetup.userService.createUser( + username, + 'Testy', + null, + null, + ); await testSetup.localIdentityService.createLocalIdentity(user, password); content = 'This is a test note.'; @@ -51,12 +56,15 @@ describe('Me', () => { }); it('GET /me', async () => { - const userInfo = testSetup.userService.toUserLoginInfoDto(user, 'local'); + const userInfo = testSetup.userService.toLoginUserInfoDto( + user, + ProviderType.LOCAL, + ); const response = await agent .get('/api/private/me') .expect('Content-Type', /json/) .expect(200); - const gotUser = response.body as UserLoginInfoDto; + const gotUser = response.body as LoginUserInfoDto; expect(gotUser).toEqual(userInfo); }); @@ -126,15 +134,15 @@ describe('Me', () => { await fs.rmdir(uploadPath); }); - it('POST /me/profile', async () => { + it('PUT /me/profile', async () => { const newDisplayName = 'Another name'; expect(user.displayName).not.toEqual(newDisplayName); await agent - .post('/api/private/me/profile') + .put('/api/private/me/profile') .send({ displayName: newDisplayName, }) - .expect(201); + .expect(200); const dbUser = await testSetup.userService.getUserByUsername('hardcoded'); expect(dbUser.displayName).toEqual(newDisplayName); }); diff --git a/backend/test/private-api/notes.e2e-spec.ts b/backend/test/private-api/notes.e2e-spec.ts index 25fc933a4..b9637311c 100644 --- a/backend/test/private-api/notes.e2e-spec.ts +++ b/backend/test/private-api/notes.e2e-spec.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 */ @@ -39,9 +39,19 @@ describe('Notes', () => { const password2 = 'AHardcodedStrongP@ssword12'; const groupname1 = 'groupname1'; - user1 = await testSetup.userService.createUser(username1, 'Testy'); + user1 = await testSetup.userService.createUser( + username1, + 'Testy', + null, + null, + ); await testSetup.localIdentityService.createLocalIdentity(user1, password1); - user2 = await testSetup.userService.createUser(username2, 'Max Mustermann'); + user2 = await testSetup.userService.createUser( + username2, + 'Max Mustermann', + null, + null, + ); await testSetup.localIdentityService.createLocalIdentity(user2, password2); group1 = await testSetup.groupService.createGroup(groupname1, 'Group 1'); @@ -668,7 +678,7 @@ describe('Notes', () => { .put(`/api/private/notes/${alias}/metadata/permissions/owner`) .expect('Content-Type', /json/) .expect(200) - .send({ newOwner: user2.username }); + .send({ owner: user2.username }); expect(response.body.metadata.permissions.owner).toBe(user2.username); }); }); diff --git a/backend/test/private-api/register-and-login.e2e-spec.ts b/backend/test/private-api/register-and-login.e2e-spec.ts index 7c967353c..b9a562d29 100644 --- a/backend/test/private-api/register-and-login.e2e-spec.ts +++ b/backend/test/private-api/register-and-login.e2e-spec.ts @@ -1,12 +1,11 @@ /* - * 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 */ +import { LoginDto, RegisterDto } from '@hedgedoc/commons'; import request from 'supertest'; -import { LoginDto } from '../../src/auth/local/login.dto'; -import { RegisterDto } from '../../src/auth/local/register.dto'; import { TestSetup, TestSetupBuilder } from '../test-setup'; describe('Register and Login', () => { diff --git a/backend/test/public-api/alias.e2e-spec.ts b/backend/test/public-api/alias.e2e-spec.ts index 6c4d33fe3..5ad23073e 100644 --- a/backend/test/public-api/alias.e2e-spec.ts +++ b/backend/test/public-api/alias.e2e-spec.ts @@ -1,11 +1,11 @@ /* - * 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 */ +import { AliasUpdateDto } from '@hedgedoc/commons'; import request from 'supertest'; -import { AliasUpdateDto } from '../../src/notes/alias-update.dto'; import { TestSetup, TestSetupBuilder } from '../test-setup'; describe('Alias', () => { @@ -166,13 +166,13 @@ describe('Alias', () => { .expect(401); }); it('if the property primaryAlias is false', async () => { - changeAliasDto.primaryAlias = false; - await request(testSetup.app.getHttpServer()) .put(`/api/v2/alias/${testAlias}`) .set('Authorization', `Bearer ${testSetup.authTokens[0].secret}`) .set('Content-Type', 'application/json') - .send(changeAliasDto) + .send({ + primaryAlias: false, + }) .expect(400); }); }); diff --git a/backend/test/public-api/me.e2e-spec.ts b/backend/test/public-api/me.e2e-spec.ts index d2f5a9c12..6e87d48b2 100644 --- a/backend/test/public-api/me.e2e-spec.ts +++ b/backend/test/public-api/me.e2e-spec.ts @@ -1,15 +1,15 @@ /* - * 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 */ +import { NoteMetadataDto } from '@hedgedoc/commons'; import { promises as fs } from 'fs'; import { join } from 'path'; +import { HistoryEntryDto } from 'src/history/history-entry.dto'; import request from 'supertest'; import { HistoryEntryUpdateDto } from '../../src/history/history-entry-update.dto'; -import { HistoryEntryDto } from '../../src/history/history-entry.dto'; -import { NoteMetadataDto } from '../../src/notes/note-metadata.dto'; import { User } from '../../src/users/user.entity'; import { TestSetup, TestSetupBuilder } from '../test-setup'; @@ -25,7 +25,12 @@ describe('Me', () => { uploadPath = testSetup.configService.get('mediaConfig').backend.filesystem.uploadPath; - user = await testSetup.userService.createUser('hardcoded', 'Testy'); + user = await testSetup.userService.createUser( + 'hardcoded', + 'Testy', + null, + null, + ); await testSetup.app.init(); }); diff --git a/backend/test/public-api/notes.e2e-spec.ts b/backend/test/public-api/notes.e2e-spec.ts index 107116de0..9fcdf6676 100644 --- a/backend/test/public-api/notes.e2e-spec.ts +++ b/backend/test/public-api/notes.e2e-spec.ts @@ -1,14 +1,14 @@ /* - * 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 */ +import { NotePermissionsUpdateDto } from '@hedgedoc/commons'; import { promises as fs } from 'fs'; import { join } from 'path'; import request from 'supertest'; import { NotInDBError } from '../../src/errors/errors'; -import { NotePermissionsUpdateDto } from '../../src/notes/note-permissions.dto'; import { TestSetup, TestSetupBuilder } from '../test-setup'; describe('Notes', () => { @@ -219,14 +219,15 @@ describe('Notes', () => { testSetup.users[0], 'deleteTest3', ); - const updateNotePermission = new NotePermissionsUpdateDto(); - updateNotePermission.sharedToUsers = [ - { - username: testSetup.users[0].username, - canEdit: true, - }, - ]; - updateNotePermission.sharedToGroups = []; + const updateNotePermission: NotePermissionsUpdateDto = { + sharedToUsers: [ + { + username: testSetup.users[0].username, + canEdit: true, + }, + ], + sharedToGroups: [], + }; await testSetup.permissionsService.updateNotePermissions( note, updateNotePermission, diff --git a/backend/test/test-setup.ts b/backend/test/test-setup.ts index 441eb30de..59270823f 100644 --- a/backend/test/test-setup.ts +++ b/backend/test/test-setup.ts @@ -1,8 +1,9 @@ /* - * 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 */ +import { ApiTokenWithSecretDto } from '@hedgedoc/commons'; import { ConfigModule, ConfigService } from '@nestjs/config'; import { RouterModule, Routes } from '@nestjs/core'; import { EventEmitterModule } from '@nestjs/event-emitter'; @@ -11,7 +12,6 @@ import { Test, TestingModule, TestingModuleBuilder } from '@nestjs/testing'; import { TypeOrmModule, TypeOrmModuleOptions } from '@nestjs/typeorm'; import { Connection, createConnection } from 'typeorm'; -import { ApiTokenWithSecretDto } from '../src/api-token/api-token.dto'; import { ApiTokenGuard } from '../src/api-token/api-token.guard'; import { ApiTokenModule } from '../src/api-token/api-token.module'; import { ApiTokenService } from '../src/api-token/api-token.service'; @@ -389,13 +389,28 @@ export class TestSetupBuilder { this.setupPostCompile.push(async () => { // Create users this.testSetup.users.push( - await this.testSetup.userService.createUser(username1, 'Test User 1'), + await this.testSetup.userService.createUser( + username1, + 'Test User 1', + null, + null, + ), ); this.testSetup.users.push( - await this.testSetup.userService.createUser(username2, 'Test User 2'), + await this.testSetup.userService.createUser( + username2, + 'Test User 2', + null, + null, + ), ); this.testSetup.users.push( - await this.testSetup.userService.createUser(username3, 'Test User 3'), + await this.testSetup.userService.createUser( + username3, + 'Test User 3', + null, + null, + ), ); // Create identities for login @@ -415,10 +430,12 @@ export class TestSetupBuilder { // create auth tokens this.testSetup.authTokens = await Promise.all( this.testSetup.users.map(async (user) => { + const validUntil = new Date(); + validUntil.setTime(validUntil.getTime() + 60 * 60 * 1000); return await this.testSetup.publicAuthTokenService.addToken( user, 'test', - new Date().getTime() + 60 * 60 * 1000, + validUntil, ); }), );