From 0dba3970236c72f13d04bbb46d6f7e51923c9b05 Mon Sep 17 00:00:00 2001 From: kim Date: Thu, 26 Feb 2026 16:33:33 +0100 Subject: [PATCH 1/3] refactor: apply tsconfig strict true --- src/config/env.ts | 2 + src/services/auth/plugins/captcha/captcha.ts | 2 + .../plugins/magicLink/magicLink.controller.ts | 2 + .../auth/plugins/passport/preHandlers.ts | 2 + .../passport/strategies/emailChange.ts | 5 +- .../plugins/passport/strategies/jwtApps.ts | 3 +- .../strategies/jwtChallengeVerifier.ts | 5 +- src/services/file/file.service.test.ts | 6 +- src/services/file/file.service.ts | 4 +- src/services/file/repositories/local.ts | 2 +- src/services/item/item.repository.spec.ts | 56 ++++++++----------- .../plugins/action/itemAction.controller.ts | 17 +----- .../item/plugins/app/appItem.controller.ts | 3 +- .../app/appSetting/appSetting.controller.ts | 7 ++- .../file/appSetting.file.controller.ts | 16 ++++-- .../plugins/file/appSetting.file.service.ts | 16 ++++-- .../item/plugins/app/chatBot/utils.ts | 19 ++++--- .../item/plugins/embeddedLink/link.service.ts | 4 +- .../item/plugins/file/itemFile.controller.ts | 7 ++- .../item/plugins/file/itemFile.service.ts | 2 +- .../item/plugins/html/h5p/h5p.controller.ts | 5 +- .../item/plugins/html/h5p/h5p.service.ts | 5 +- .../moderators/itemValidationModerator.ts | 9 ++- .../plugins/shortLink/shortlink.repository.ts | 26 ++++++--- .../plugins/action/memberAction.controller.ts | 17 +----- .../thumbnail/thumbnail.service.spec.ts | 2 + src/services/websockets/test/ws-app.ts | 9 ++- src/services/websockets/ws-service.ts | 5 +- tsconfig.json | 17 ++---- 29 files changed, 138 insertions(+), 137 deletions(-) diff --git a/src/config/env.ts b/src/config/env.ts index 3734aa9c75..d188d55802 100644 --- a/src/config/env.ts +++ b/src/config/env.ts @@ -14,6 +14,8 @@ const once = (fn) => { return function (...args) { if (called) return; called = true; + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore return fn.apply(this, args); }; }; diff --git a/src/services/auth/plugins/captcha/captcha.ts b/src/services/auth/plugins/captcha/captcha.ts index 353125df44..a6ed37351c 100644 --- a/src/services/auth/plugins/captcha/captcha.ts +++ b/src/services/auth/plugins/captcha/captcha.ts @@ -25,6 +25,8 @@ export default function captchaPreHandler( action: RecaptchaActionType, options?: { shouldFail: boolean }, ): RouteHandlerMethod { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore return async (request: FastifyRequest<{ Body: { captcha: string } }>, _reply: FastifyReply) => { const { captcha } = request.body; return await validateCaptcha(request, captcha, action, options); diff --git a/src/services/auth/plugins/magicLink/magicLink.controller.ts b/src/services/auth/plugins/magicLink/magicLink.controller.ts index 39535fb8f1..e65fe17318 100644 --- a/src/services/auth/plugins/magicLink/magicLink.controller.ts +++ b/src/services/auth/plugins/magicLink/magicLink.controller.ts @@ -82,6 +82,8 @@ const plugin: FastifyPluginAsyncTypebox = async (fastify) => { schema: auth, preHandler: fastifyPassport.authenticate( PassportStrategy.WebMagicLink, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore async (request, reply, err, user?: PassportUser, info?: PassportInfo) => { // This function is called after the strategy has been executed. // It is necessary, so we match the behavior of the original implementation. diff --git a/src/services/auth/plugins/passport/preHandlers.ts b/src/services/auth/plugins/passport/preHandlers.ts index b045ff8090..70b4926cb6 100644 --- a/src/services/auth/plugins/passport/preHandlers.ts +++ b/src/services/auth/plugins/passport/preHandlers.ts @@ -79,6 +79,8 @@ export const guestAuthenticateAppsJWT = fastifyPassport.authenticate( export function matchOne( ...strategies: RessourceAuthorizationStrategy[] ): RouteHandlerMethod { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error return async (req: FastifyRequest) => { if (!strategies.some((strategy) => strategy.test(req))) { // If none of the strategies pass, throw an error. diff --git a/src/services/auth/plugins/passport/strategies/emailChange.ts b/src/services/auth/plugins/passport/strategies/emailChange.ts index e36bdb47ed..ec04783c94 100644 --- a/src/services/auth/plugins/passport/strategies/emailChange.ts +++ b/src/services/auth/plugins/passport/strategies/emailChange.ts @@ -47,9 +47,10 @@ export default ( false, ); } - } catch (err) { + } catch (err: unknown) { // Exception occurred while fetching member - return done(options?.propagateError ? err : new UnauthorizedMember(), false); + const error = err instanceof Error ? err : new Error(String(err)); + return done(options?.propagateError ? error : new UnauthorizedMember(), false); } }, ), diff --git a/src/services/auth/plugins/passport/strategies/jwtApps.ts b/src/services/auth/plugins/passport/strategies/jwtApps.ts index 12a2408ba5..229d1433a2 100644 --- a/src/services/auth/plugins/passport/strategies/jwtApps.ts +++ b/src/services/auth/plugins/passport/strategies/jwtApps.ts @@ -57,7 +57,8 @@ export default ( // Exception occurred while fetching item // itemRepository.getOneOrThrow() can fail for many reasons like the item was not found, database error, etc. // To avoid leaking information, we prefer to return UnauthorizedMember error. - return done(options?.propagateError ? err : new UnauthorizedMember(), false); + const error = err instanceof Error ? err : new Error(String(err)); + return done(options?.propagateError ? error : new UnauthorizedMember(), false); } }, ), diff --git a/src/services/auth/plugins/passport/strategies/jwtChallengeVerifier.ts b/src/services/auth/plugins/passport/strategies/jwtChallengeVerifier.ts index a976fa3424..4c4b1471e0 100644 --- a/src/services/auth/plugins/passport/strategies/jwtChallengeVerifier.ts +++ b/src/services/auth/plugins/passport/strategies/jwtChallengeVerifier.ts @@ -57,9 +57,10 @@ export default ( false, ); } - } catch (err) { + } catch (err: unknown) { // Exception occurred while fetching member - return done(spreadException ? err : new UnauthorizedMember(), false); + const error = err instanceof Error ? err : new Error(String(err)); + return done(spreadException ? error : new UnauthorizedMember(), false); } }, ), diff --git a/src/services/file/file.service.test.ts b/src/services/file/file.service.test.ts index 2fcdc45af8..63acd1e26f 100644 --- a/src/services/file/file.service.test.ts +++ b/src/services/file/file.service.test.ts @@ -276,19 +276,19 @@ describe('FileService', () => { const copyMock = jest .spyOn(s3Repository, 'copyFile') .mockImplementation(async () => 'string'); - await s3FileService.copy(member, copyPayload); + await s3FileService.copy(member.id, copyPayload); expect(copyMock).toHaveBeenCalled(); }); it('empty originalPath throws', async () => { await expect( - s3FileService.copy(member, { ...copyPayload, originalPath: '' }), + s3FileService.copy(member.id, { ...copyPayload, originalPath: '' }), ).rejects.toMatchObject(new CopyFileInvalidPathError(expect.anything())); }); it('empty newFilePath throws', async () => { await expect( - s3FileService.copy(member, { ...copyPayload, newFilePath: '' }), + s3FileService.copy(member.id, { ...copyPayload, newFilePath: '' }), ).rejects.toMatchObject(new CopyFileInvalidPathError(expect.anything())); }); }); diff --git a/src/services/file/file.service.ts b/src/services/file/file.service.ts index b32c4817e6..2fdf0559a0 100644 --- a/src/services/file/file.service.ts +++ b/src/services/file/file.service.ts @@ -161,7 +161,7 @@ class FileService { } async copy( - member: MinimalMember, + memberId: MinimalMember['id'], data: { newId?: string; newFilePath: string; @@ -180,7 +180,7 @@ class FileService { return this.repository.copyFile({ newId, - memberId: member.id, + memberId, originalPath, newFilePath, mimetype, diff --git a/src/services/file/repositories/local.ts b/src/services/file/repositories/local.ts index 8b7e4821aa..e6e31a6efd 100644 --- a/src/services/file/repositories/local.ts +++ b/src/services/file/repositories/local.ts @@ -69,7 +69,7 @@ export class LocalFileRepository implements FileRepository { try { await access(this.buildFullPath(filepath)); } catch (e) { - if (e.code === 'ENOENT') { + if (e !== null && typeof e === 'object' && 'code' in e && e.code === 'ENOENT') { throw new LocalFileNotFound({ filepath }); } throw e; diff --git a/src/services/item/item.repository.spec.ts b/src/services/item/item.repository.spec.ts index ff0fa77f2e..80249a4930 100644 --- a/src/services/item/item.repository.spec.ts +++ b/src/services/item/item.repository.spec.ts @@ -810,15 +810,13 @@ describe('Item Repository', () => { const itemCreatorIdsInDB = insertedItems.map((i) => i.creatorId); const itemPathsInDb = insertedItems.map((i) => i.path); - expect(itemNamesInDB.sort(alphabeticalOrder)).toEqual( - insertedItemNames.sort(alphabeticalOrder), + expect(itemNamesInDB.toSorted(alphabeticalOrder)).toEqual( + insertedItemNames.toSorted(alphabeticalOrder), ); - expect(itemTypesInDB.sort(alphabeticalOrder)).toEqual( - insertedItemTypes.sort(alphabeticalOrder), - ); - expect(itemCreatorIdsInDB.sort(alphabeticalOrder)).toEqual( - insertedItemCreatorIds.sort(alphabeticalOrder), + expect(itemTypesInDB.toSorted(alphabeticalOrder)).toEqual( + insertedItemTypes.toSorted(alphabeticalOrder), ); + expect(itemCreatorIdsInDB).toEqual(insertedItemCreatorIds); expect(itemPathsInDb.every((path) => !path.includes('.'))).toBeTruthy(); }); it('post many with parent item', async () => { @@ -848,17 +846,15 @@ describe('Item Repository', () => { const itemCreatorIdsInDB = insertedItems.map((i) => i.creatorId); const itemPathsInDB = insertedItems.map((i) => i.path); - expect(itemNamesInDB.sort(alphabeticalOrder)).toEqual( - insertedItemNames.sort(alphabeticalOrder), - ); - expect(itemTypesInDB.sort(alphabeticalOrder)).toEqual( - insertedItemTypes.sort(alphabeticalOrder), + expect(itemNamesInDB.toSorted(alphabeticalOrder)).toEqual( + insertedItemNames.toSorted(alphabeticalOrder), ); - expect(itemCreatorIdsInDB.sort(alphabeticalOrder)).toEqual( - insertedItemCreatorIds.sort(alphabeticalOrder), + expect(itemTypesInDB.toSorted(alphabeticalOrder)).toEqual( + insertedItemTypes.toSorted(alphabeticalOrder), ); - expect(itemPathsInDB.sort(alphabeticalOrder)).toEqual( - insertedItemPaths.sort(alphabeticalOrder), + expect(itemCreatorIdsInDB).toEqual(insertedItemCreatorIds); + expect(itemPathsInDB.toSorted(alphabeticalOrder)).toEqual( + insertedItemPaths.toSorted(alphabeticalOrder), ); expect(itemPathsInDB.every((path) => path.includes(`${parentItem.path}.`))).toBeTruthy(); }); @@ -891,15 +887,13 @@ describe('Item Repository', () => { const itemCreatorIdsInDB = insertedItems.map((i) => i.creatorId); const itemPathsInDb = insertedItems.map((i) => i.path); - expect(itemNamesInDB.sort(alphabeticalOrder)).toEqual( - insertedItemNames.sort(alphabeticalOrder), + expect(itemNamesInDB.toSorted(alphabeticalOrder)).toEqual( + insertedItemNames.toSorted(alphabeticalOrder), ); - expect(itemTypesInDB.sort(alphabeticalOrder)).toEqual( - insertedItemTypes.sort(alphabeticalOrder), - ); - expect(itemCreatorIdsInDB.sort(alphabeticalOrder)).toEqual( - insertedItemCreatorIds.sort(alphabeticalOrder), + expect(itemTypesInDB.toSorted(alphabeticalOrder)).toEqual( + insertedItemTypes.toSorted(alphabeticalOrder), ); + expect(itemCreatorIdsInDB).toEqual(insertedItemCreatorIds); expect(itemPathsInDb.every((path) => !path.includes('.'))).toBeTruthy(); }); it('post many with parent item', async () => { @@ -933,17 +927,15 @@ describe('Item Repository', () => { const itemCreatorIdsInDB = insertedItems.map((i) => i.creatorId); const itemPathsInDB = insertedItems.map((i) => i.path); - expect(itemNamesInDB.sort(alphabeticalOrder)).toEqual( - insertedItemNames.sort(alphabeticalOrder), - ); - expect(itemTypesInDB.sort(alphabeticalOrder)).toEqual( - insertedItemTypes.sort(alphabeticalOrder), + expect(itemNamesInDB.toSorted(alphabeticalOrder)).toEqual( + insertedItemNames.toSorted(alphabeticalOrder), ); - expect(itemCreatorIdsInDB.sort(alphabeticalOrder)).toEqual( - insertedItemCreatorIds.sort(alphabeticalOrder), + expect(itemTypesInDB.toSorted(alphabeticalOrder)).toEqual( + insertedItemTypes.toSorted(alphabeticalOrder), ); - expect(itemPathsInDB.sort(alphabeticalOrder)).toEqual( - insertedItemPaths.sort(alphabeticalOrder), + expect(itemCreatorIdsInDB).toEqual(insertedItemCreatorIds); + expect(itemPathsInDB.toSorted(alphabeticalOrder)).toEqual( + insertedItemPaths.toSorted(alphabeticalOrder), ); expect(itemPathsInDB.every((path) => path.includes(`${parentItem.path}.`))).toBeTruthy(); }); diff --git a/src/services/item/plugins/action/itemAction.controller.ts b/src/services/item/plugins/action/itemAction.controller.ts index 7805855a5d..17bef82307 100644 --- a/src/services/item/plugins/action/itemAction.controller.ts +++ b/src/services/item/plugins/action/itemAction.controller.ts @@ -3,8 +3,6 @@ import { StatusCodes } from 'http-status-codes'; import type { FastifyPluginAsyncTypebox } from '@fastify/type-provider-typebox'; import fp from 'fastify-plugin'; -import { type FileItemType } from '@graasp/sdk'; - import { resolveDependency } from '../../../../di/utils'; import { db } from '../../../../drizzle/db'; import { asDefined } from '../../../../utils/assertions'; @@ -13,10 +11,6 @@ import { ActionService } from '../../../action/action.service'; import { isAuthenticated, matchOne, optionalIsAuthenticated } from '../../../auth/plugins/passport'; import { assertIsMember } from '../../../authentication'; import { AuthorizedItemService } from '../../../authorizedItem.service'; -import type { - LocalFileConfiguration, - S3FileConfiguration, -} from '../../../file/interfaces/configuration'; import { validatedMemberAccountRole } from '../../../member/strategies/validatedMemberAccountRole'; import { ItemOpFeedbackErrorEvent, @@ -34,16 +28,7 @@ import { import { ItemActionService } from './itemAction.service'; import { ActionRequestExportService } from './requestExport/itemAction.requestExport.service'; -export interface GraaspActionsOptions { - shouldSave?: boolean; - fileItemType: FileItemType; - fileConfigurations: { - s3: S3FileConfiguration; - local: LocalFileConfiguration; - }; -} - -const plugin: FastifyPluginAsyncTypebox = async (fastify) => { +const plugin: FastifyPluginAsyncTypebox = async (fastify) => { const { websockets } = fastify; const authorizedItemService = resolveDependency(AuthorizedItemService); diff --git a/src/services/item/plugins/app/appItem.controller.ts b/src/services/item/plugins/app/appItem.controller.ts index c4bc080e91..c235009f01 100644 --- a/src/services/item/plugins/app/appItem.controller.ts +++ b/src/services/item/plugins/app/appItem.controller.ts @@ -9,9 +9,8 @@ import { validatedMemberAccountRole } from '../../../member/strategies/validated import { ItemActionService } from '../action/itemAction.service'; import { createApp, updateApp } from './app.schemas'; import { AppItemService } from './appItemService'; -import type { AppsPluginOptions } from './types'; -export const plugin: FastifyPluginAsyncTypebox = async (fastify) => { +export const plugin: FastifyPluginAsyncTypebox = async (fastify) => { // service for item app api const appItemService = resolveDependency(AppItemService); const itemActionService = resolveDependency(ItemActionService); diff --git a/src/services/item/plugins/app/appSetting/appSetting.controller.ts b/src/services/item/plugins/app/appSetting/appSetting.controller.ts index 407febf8dd..0c97f581b4 100644 --- a/src/services/item/plugins/app/appSetting/appSetting.controller.ts +++ b/src/services/item/plugins/app/appSetting/appSetting.controller.ts @@ -2,7 +2,8 @@ import type { FastifyPluginAsyncTypebox } from '@fastify/type-provider-typebox'; import { resolveDependency } from '../../../../../di/utils'; import { type DBConnection, db } from '../../../../../drizzle/db'; -import type { AuthenticatedUser } from '../../../../../types'; +import { MinimalItemForInsert } from '../../../../../drizzle/types'; +import type { MaybeUser } from '../../../../../types'; import { asDefined } from '../../../../../utils/assertions'; import { authenticateAppsJWT, @@ -34,9 +35,9 @@ const plugin: FastifyPluginAsyncTypebox = async (fastify) => { // copy app settings and related files on item copy const hook = async ( - actor: AuthenticatedUser, + actor: MaybeUser, dbConnection: DBConnection, - { original, copy }: { original: ItemRaw; copy: ItemRaw }, + { original, copy }: { original: ItemRaw; copy: MinimalItemForInsert }, ) => { if (original.type !== 'app' || copy.type !== 'app') return; diff --git a/src/services/item/plugins/app/appSetting/plugins/file/appSetting.file.controller.ts b/src/services/item/plugins/app/appSetting/plugins/file/appSetting.file.controller.ts index 6575f23867..96d0751fa1 100644 --- a/src/services/item/plugins/app/appSetting/plugins/file/appSetting.file.controller.ts +++ b/src/services/item/plugins/app/appSetting/plugins/file/appSetting.file.controller.ts @@ -3,8 +3,8 @@ import type { FastifyPluginAsyncTypebox } from '@fastify/type-provider-typebox'; import { resolveDependency } from '../../../../../../../di/utils'; import { type DBConnection, db } from '../../../../../../../drizzle/db'; -import { type AppSettingRaw, type AppSettingWithItem } from '../../../../../../../drizzle/types'; -import type { AuthenticatedUser, MinimalMember } from '../../../../../../../types'; +import { type AppSettingRaw } from '../../../../../../../drizzle/types'; +import type { MaybeUser } from '../../../../../../../types'; import { asDefined } from '../../../../../../../utils/assertions'; import { authenticateAppsJWT, @@ -44,7 +44,7 @@ const basePlugin: FastifyPluginAsyncTypebox = async (fa // register post delete handler to remove the file object after item delete const deleteHook = async ( - actor: AuthenticatedUser, + actor: MaybeUser, dbConnection: DBConnection, { appSetting }: { appSetting: AppSettingRaw; itemId: string }, ) => { @@ -54,21 +54,25 @@ const basePlugin: FastifyPluginAsyncTypebox = async (fa // app setting copy hook const hook = async ( - actor: MinimalMember, + actor: MaybeUser, dbConnection: DBConnection, { appSettings, }: { - appSettings: AppSettingWithItem[]; + appSettings: AppSettingRaw[]; originalItemId: string; copyItemId: string; }, ) => { + if (!actor) { + return; + } + // copy file only if content is a file const isFileSetting = (a) => a.data['file']; const toCopy = appSettings.filter(isFileSetting); if (toCopy.length) { - await appSettingFileService.copyMany(db, actor, toCopy); + await appSettingFileService.copyMany(db, actor.id, toCopy); } }; appSettingService.hooks.setPostHook('copyMany', hook); diff --git a/src/services/item/plugins/app/appSetting/plugins/file/appSetting.file.service.ts b/src/services/item/plugins/app/appSetting/plugins/file/appSetting.file.service.ts index 23c244207f..51b5536c52 100644 --- a/src/services/item/plugins/app/appSetting/plugins/file/appSetting.file.service.ts +++ b/src/services/item/plugins/app/appSetting/plugins/file/appSetting.file.service.ts @@ -7,8 +7,8 @@ import type { MultipartFile } from '@fastify/multipart'; import { type FileItemProperties, MAX_ITEM_NAME_LENGTH, type UUID } from '@graasp/sdk'; import { type DBConnection } from '../../../../../../../drizzle/db'; -import type { AppSettingRaw, AppSettingWithItem } from '../../../../../../../drizzle/types'; -import type { AuthenticatedUser, MinimalMember } from '../../../../../../../types'; +import type { AppSettingRaw } from '../../../../../../../drizzle/types'; +import type { AuthenticatedUser, MaybeUser, MinimalMember } from '../../../../../../../types'; import FileService from '../../../../../../file/file.service'; import type { ItemRaw } from '../../../../../item'; import { AppSettingRepository } from '../../appSetting.repository'; @@ -115,14 +115,18 @@ class AppSettingFileService { return result; } - async copyMany(dbConnection: DBConnection, actor: MinimalMember, toCopy: AppSettingWithItem[]) { + async copyMany( + dbConnection: DBConnection, + actorId: MinimalMember['id'], + toCopy: AppSettingRaw[], + ) { for (const appSetting of toCopy) { if (!appSetting.data) { throw new Error('App setting file is not correctly defined'); } // create file data object - const itemId = appSetting.item.id; + const itemId = appSetting.itemId; const newFilePath = this.buildFilePath(itemId, appSetting.id); const originalFileExtra = appSetting.data['file'] as AppSettingFileProperties; const newFileData = { @@ -134,7 +138,7 @@ class AppSettingFileService { }; // run copy task - await this.fileService.copy(actor, { + await this.fileService.copy(actorId, { newId: appSetting.id, newFilePath, originalPath: originalFileExtra.path, @@ -148,7 +152,7 @@ class AppSettingFileService { } } - async deleteOne(dbConnection: DBConnection, actor: AuthenticatedUser, appSetting: AppSettingRaw) { + async deleteOne(dbConnection: DBConnection, actor: MaybeUser, appSetting: AppSettingRaw) { // TODO: check rights? but only use in posthook try { // delete file only if type is the current file type diff --git a/src/services/item/plugins/app/chatBot/utils.ts b/src/services/item/plugins/app/chatBot/utils.ts index ad05229cdf..4557644a07 100644 --- a/src/services/item/plugins/app/chatBot/utils.ts +++ b/src/services/item/plugins/app/chatBot/utils.ts @@ -21,17 +21,20 @@ export const fetchOpenAI = async ( const choice = completion.choices[0]; return computeResult(choice, gptVersion); - } catch (e) { + } catch (e: unknown) { if (e instanceof OpenAIBaseError) { throw e; - } else if (e.status === 429) { - // if the catched error is OpenAI insufficient quota - // throw a new OpenAIQuota error - throw new OpenAIQuotaError(); + } else if (e !== null && typeof e === 'object') { + if ('status' in e && e.status === 429) { + // if the catched error is OpenAI insufficient quota + // throw a new OpenAIQuota error + throw new OpenAIQuotaError(); + } else if ('message' in e && typeof e.message === 'string') { + // handle unexpected errors (like billing expired) + throw new OpenAIBaseError({ message: e.message }); + } } - - // handle unexpected errors (like billing expired) - throw new OpenAIBaseError({ message: e.message }); + throw new OpenAIBaseError({ message: String(e) }); } }; diff --git a/src/services/item/plugins/embeddedLink/link.service.ts b/src/services/item/plugins/embeddedLink/link.service.ts index 6c9bc16bbc..ee5c7822bf 100644 --- a/src/services/item/plugins/embeddedLink/link.service.ts +++ b/src/services/item/plugins/embeddedLink/link.service.ts @@ -204,10 +204,10 @@ export class EmbeddedLinkItemService extends ItemService { } return true; - } catch (error) { + } catch (error: unknown) { const msgError = 'Error checking embedding permission:'; if (logger) { - logger?.error(msgError, error); + logger?.error(`${msgError}: ${String(error)}`); } else { console.error(msgError, error); } diff --git a/src/services/item/plugins/file/itemFile.controller.ts b/src/services/item/plugins/file/itemFile.controller.ts index c155f0d9a6..880e031c15 100644 --- a/src/services/item/plugins/file/itemFile.controller.ts +++ b/src/services/item/plugins/file/itemFile.controller.ts @@ -150,10 +150,11 @@ const basePlugin: FastifyPluginAsyncTypebox = async (fastify) => { } items.push(item); - } catch (e) { + } catch (err: unknown) { // ignore errors - log.error(e); - errors.push(e); + log.error(err); + const error = err instanceof Error ? err : new Error(String(err)); + errors.push(error); } finally { // force close to avoid hanging // necessary for errors that don't read the stream diff --git a/src/services/item/plugins/file/itemFile.service.ts b/src/services/item/plugins/file/itemFile.service.ts index 4ace0ce1bc..81aefaf7c0 100644 --- a/src/services/item/plugins/file/itemFile.service.ts +++ b/src/services/item/plugins/file/itemFile.service.ts @@ -246,7 +246,7 @@ class FileItemService extends ItemService { // check member storage limit in pre copy because all items are pretested - const filepath = await this.fileService.copy(member, data); + const filepath = await this.fileService.copy(member.id, data); // update item copy's 'extra' await this.itemRepository.updateOne(dbConnection, copy.id, { diff --git a/src/services/item/plugins/html/h5p/h5p.controller.ts b/src/services/item/plugins/html/h5p/h5p.controller.ts index dc7c348b23..bbd11835c0 100644 --- a/src/services/item/plugins/html/h5p/h5p.controller.ts +++ b/src/services/item/plugins/html/h5p/h5p.controller.ts @@ -7,6 +7,7 @@ import type { FastifyPluginAsyncTypebox } from '@fastify/type-provider-typebox'; import { resolveDependency } from '../../../../../di/utils'; import { type DBConnection, db } from '../../../../../drizzle/db'; +import { MinimalItemForInsert } from '../../../../../drizzle/types'; import type { MaybeUser } from '../../../../../types'; import { asDefined } from '../../../../../utils/assertions'; import { H5P_FILE_STORAGE_TYPE } from '../../../../../utils/config'; @@ -155,10 +156,10 @@ const plugin: FastifyPluginAsyncTypebox = async (fastify) => { async function copyH5PAssets( actor: MaybeUser, dbConnection: DBConnection, - { original: item, copy }: { original: ItemRaw; copy: ItemRaw }, + { original: item, copy }: { original: ItemRaw; copy: MinimalItemForInsert }, ) { // only execute this handler for H5P item types - if (!isH5PItem(item) || !isH5PItem(copy)) { + if (!isH5PItem(item) || copy.type !== 'h5p') { return; } if (!actor || !isMember(actor)) { diff --git a/src/services/item/plugins/html/h5p/h5p.service.ts b/src/services/item/plugins/html/h5p/h5p.service.ts index 1910878cee..5ff925ded4 100644 --- a/src/services/item/plugins/html/h5p/h5p.service.ts +++ b/src/services/item/plugins/html/h5p/h5p.service.ts @@ -8,6 +8,7 @@ import type { FastifyBaseLogger } from 'fastify'; import { type H5PItemExtra } from '@graasp/sdk'; import type { DBConnection } from '../../../../../drizzle/db'; +import { MinimalItemForInsert } from '../../../../../drizzle/types'; import { BaseLogger } from '../../../../../logger'; import type { MinimalMember } from '../../../../../types'; import { @@ -82,7 +83,7 @@ export class H5PService extends HtmlService { copy, }: { original: H5PItem; - copy: H5PItem; + copy: MinimalItemForInsert; }, ): Promise { const { extra } = item; @@ -95,7 +96,7 @@ export class H5PService extends HtmlService { const remoteRootPath = this.buildRootPath(this.pathPrefix, newContentId); // copy .h5p file - await this.fileService.copy(member, { + await this.fileService.copy(member.id, { originalPath: path.join(this.pathPrefix, extra.h5p.h5pFilePath), newFilePath: path.join(this.pathPrefix, this.buildPackagePath(newContentId, newName)), }); diff --git a/src/services/item/plugins/publication/validation/moderators/itemValidationModerator.ts b/src/services/item/plugins/publication/validation/moderators/itemValidationModerator.ts index 937584d746..66f8dd7304 100644 --- a/src/services/item/plugins/publication/validation/moderators/itemValidationModerator.ts +++ b/src/services/item/plugins/publication/validation/moderators/itemValidationModerator.ts @@ -72,11 +72,16 @@ export class ItemValidationModerator { try { ({ status, result } = await validate()); - } catch (error) { + } catch (error: unknown) { // if some error happend during the execution of a process, it is counted as failure status = ItemValidationStatus.Failure; // in the case of a missing s3 file, we count it as OK - if (error.message == 'S3_FILE_NOT_FOUND') { + if ( + error !== null && + typeof error === 'object' && + 'message' in error && + error.message === 'S3_FILE_NOT_FOUND' + ) { status = ItemValidationStatus.Success; } if (error instanceof Error) { diff --git a/src/services/item/plugins/shortLink/shortlink.repository.ts b/src/services/item/plugins/shortLink/shortlink.repository.ts index 17c1ef205c..da95eb3b88 100644 --- a/src/services/item/plugins/shortLink/shortlink.repository.ts +++ b/src/services/item/plugins/shortLink/shortlink.repository.ts @@ -44,13 +44,18 @@ export class ShortLinkRepository { .returning(); return res[0]; - } catch (e) { + } catch (err: unknown) { // can throw on alias conflict - if (e.code === DUPLICATE_ERROR_CODE) { + if ( + err !== null && + typeof err === 'object' && + 'code' in err && + err.code === DUPLICATE_ERROR_CODE + ) { throw new ShortLinkDuplication(alias); } - assertIsError(e); - throw e; + assertIsError(err); + throw err; } } @@ -114,13 +119,18 @@ export class ShortLinkRepository { } return updatedEntity; - } catch (e) { - if (e.code === DUPLICATE_ERROR_CODE) { + } catch (error: unknown) { + if ( + error !== null && + typeof error === 'object' && + 'code' in error && + error.code === DUPLICATE_ERROR_CODE + ) { throw new ShortLinkDuplication(entity.alias); } - assertIsError(e); + assertIsError(error); - throw new UpdateException(e.message); + throw new UpdateException(error.message); } } diff --git a/src/services/member/plugins/action/memberAction.controller.ts b/src/services/member/plugins/action/memberAction.controller.ts index 4b00a43b7e..1a0c1a26f0 100644 --- a/src/services/member/plugins/action/memberAction.controller.ts +++ b/src/services/member/plugins/action/memberAction.controller.ts @@ -1,28 +1,13 @@ import type { FastifyPluginAsyncTypebox } from '@fastify/type-provider-typebox'; -import type { FileItemType } from '@graasp/sdk'; - import { resolveDependency } from '../../../../di/utils'; import { db } from '../../../../drizzle/db'; import { asDefined } from '../../../../utils/assertions'; import { isAuthenticated } from '../../../auth/plugins/passport'; -import type { - LocalFileConfiguration, - S3FileConfiguration, -} from '../../../file/interfaces/configuration'; import { deleteAllById, getMemberFilteredActions } from './memberAction.schemas'; import { ActionMemberService } from './memberAction.service'; -export interface GraaspActionsOptions { - shouldSave?: boolean; - fileItemType: FileItemType; - fileConfigurations: { - s3: S3FileConfiguration; - local: LocalFileConfiguration; - }; -} - -const plugin: FastifyPluginAsyncTypebox = async (fastify) => { +const plugin: FastifyPluginAsyncTypebox = async (fastify) => { const actionMemberService = resolveDependency(ActionMemberService); fastify.get( diff --git a/src/services/thumbnail/thumbnail.service.spec.ts b/src/services/thumbnail/thumbnail.service.spec.ts index a4d024c737..b09bff77de 100644 --- a/src/services/thumbnail/thumbnail.service.spec.ts +++ b/src/services/thumbnail/thumbnail.service.spec.ts @@ -7,6 +7,8 @@ import { THUMBNAIL_MIMETYPE, ThumbnailSizeFormat } from './constants'; import { ThumbnailService } from './thumbnail.service'; const MockedFileService = vi.fn(function () { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error this.uploadMany = vi.fn(); }); diff --git a/src/services/websockets/test/ws-app.ts b/src/services/websockets/test/ws-app.ts index 7f62e103b4..66ce624ab0 100644 --- a/src/services/websockets/test/ws-app.ts +++ b/src/services/websockets/test/ws-app.ts @@ -12,8 +12,13 @@ async function listenOnRandomPort(app: FastifyInstance): Promise { port: Math.floor(Math.random() * (MAX_PORT - MIN_PORT)) + MIN_PORT, host: HOST_LISTEN_ADDRESS, }); - } catch (error) { - if (error.code === 'EADDRINUSE') { + } catch (error: unknown) { + if ( + error !== null && + typeof error === 'object' && + 'code' in error && + error.code === 'EADDRINUSE' + ) { return listenOnRandomPort(app); } throw error; diff --git a/src/services/websockets/ws-service.ts b/src/services/websockets/ws-service.ts index ecd7083ee2..7d4e15790b 100644 --- a/src/services/websockets/ws-service.ts +++ b/src/services/websockets/ws-service.ts @@ -10,6 +10,7 @@ import { createServerSuccessResponse, createServerUpdate, } from './message'; +import { AjvMessageSerializer } from './message-serializer'; import { MultiInstanceChannelsBroker } from './multi-instance'; import { WebSocketChannels } from './ws-channels'; @@ -39,14 +40,14 @@ export class WebsocketService { // multi-instance channels broker reference (to send across servers cluster) private wsMultiBroker: MultiInstanceChannelsBroker; // parser function that converts raw client websocket data into JS - private parse: (data: Data) => GraaspWS.ClientMessage | undefined; + private parse: AjvMessageSerializer['parse']; // logger private logger: FastifyBaseLogger; constructor( wsChannels: WebSocketChannels, wsMultiBroker: MultiInstanceChannelsBroker, - parse: (data: Data) => GraaspWS.ClientMessage | undefined, + parse: AjvMessageSerializer['parse'], log: FastifyBaseLogger, ) { this.wsChannels = wsChannels; diff --git a/tsconfig.json b/tsconfig.json index 17b3549e16..fa993f70c8 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,10 +1,6 @@ { "compilerOptions": { - "lib": [ - "es2022", - "es6", - "esnext.asynciterable" - ], + "lib": ["es2023", "es6", "esnext.asynciterable"], "target": "es6", "module": "node16", // TODO: use "preserve" when moving to ESM // "strict": true, // TODO: enable @@ -23,14 +19,9 @@ // for running typescript with tsx without the need to build and watch // "verbatimModuleSyntax": true, // TODO: enable when we want to move to ESM "strictPropertyInitialization": false, // disable because of entities, - "typeRoots": [ - "./node_modules/@types", - "./src/@types/*" - ], + "strict": true, + "typeRoots": ["./node_modules/@types", "./src/@types/*"], "sourceMap": true }, - "include": [ - "src/**/*", - "test" - ] + "include": ["src/**/*", "test"] } From 3df9724048a26a3d68d851bfed6050812cc173fa Mon Sep 17 00:00:00 2001 From: kim Date: Thu, 26 Feb 2026 17:08:54 +0100 Subject: [PATCH 2/3] refactor: fix tests --- .../passport/strategies/emailChange.ts | 10 +++-- .../plugins/passport/strategies/jwtApps.ts | 10 +++-- .../strategies/jwtChallengeVerifier.ts | 12 ++++-- src/services/file/utils/errors.ts | 37 ++++++------------- .../plugins/file/itemFile.controller.test.ts | 12 ++++-- .../item/plugins/file/itemFile.controller.ts | 8 ++-- .../item/plugins/file/utils/errors.ts | 19 ++++------ src/utils/errors.ts | 7 ++++ 8 files changed, 59 insertions(+), 56 deletions(-) diff --git a/src/services/auth/plugins/passport/strategies/emailChange.ts b/src/services/auth/plugins/passport/strategies/emailChange.ts index ec04783c94..b7935efbe8 100644 --- a/src/services/auth/plugins/passport/strategies/emailChange.ts +++ b/src/services/auth/plugins/passport/strategies/emailChange.ts @@ -4,7 +4,7 @@ import { Authenticator } from '@fastify/passport'; import { EMAIL_CHANGE_JWT_SECRET } from '../../../../../config/secrets'; import { db } from '../../../../../drizzle/db'; -import { MemberNotFound, UnauthorizedMember } from '../../../../../utils/errors'; +import { MemberNotFound, UnauthorizedMember, buildError } from '../../../../../utils/errors'; import { MemberRepository } from '../../../../member/member.repository'; import { PassportStrategy } from '../strategies'; import type { CustomStrategyOptions, StrictVerifiedCallback } from '../types'; @@ -47,10 +47,12 @@ export default ( false, ); } - } catch (err: unknown) { + } catch (error: unknown) { // Exception occurred while fetching member - const error = err instanceof Error ? err : new Error(String(err)); - return done(options?.propagateError ? error : new UnauthorizedMember(), false); + return done( + options?.propagateError ? buildError(error) : new UnauthorizedMember(), + false, + ); } }, ), diff --git a/src/services/auth/plugins/passport/strategies/jwtApps.ts b/src/services/auth/plugins/passport/strategies/jwtApps.ts index 229d1433a2..a056f645c2 100644 --- a/src/services/auth/plugins/passport/strategies/jwtApps.ts +++ b/src/services/auth/plugins/passport/strategies/jwtApps.ts @@ -4,7 +4,7 @@ import { Authenticator } from '@fastify/passport'; import { APPS_JWT_SECRET } from '../../../../../config/secrets'; import { db } from '../../../../../drizzle/db'; -import { UnauthorizedMember } from '../../../../../utils/errors'; +import { UnauthorizedMember, buildError } from '../../../../../utils/errors'; import { AccountRepository } from '../../../../account/account.repository'; import { ItemRepository } from '../../../../item/item.repository'; import { PassportStrategy } from '../strategies'; @@ -53,12 +53,14 @@ export default ( key, }, }); - } catch (err) { + } catch (error: unknown) { // Exception occurred while fetching item // itemRepository.getOneOrThrow() can fail for many reasons like the item was not found, database error, etc. // To avoid leaking information, we prefer to return UnauthorizedMember error. - const error = err instanceof Error ? err : new Error(String(err)); - return done(options?.propagateError ? error : new UnauthorizedMember(), false); + return done( + options?.propagateError ? buildError(error) : new UnauthorizedMember(), + false, + ); } }, ), diff --git a/src/services/auth/plugins/passport/strategies/jwtChallengeVerifier.ts b/src/services/auth/plugins/passport/strategies/jwtChallengeVerifier.ts index 4c4b1471e0..bf8829ffb4 100644 --- a/src/services/auth/plugins/passport/strategies/jwtChallengeVerifier.ts +++ b/src/services/auth/plugins/passport/strategies/jwtChallengeVerifier.ts @@ -5,7 +5,12 @@ import { Authenticator } from '@fastify/passport'; import { JWT_SECRET } from '../../../../../config/secrets'; import { db } from '../../../../../drizzle/db'; -import { ChallengeFailed, MemberNotFound, UnauthorizedMember } from '../../../../../utils/errors'; +import { + ChallengeFailed, + MemberNotFound, + UnauthorizedMember, + buildError, +} from '../../../../../utils/errors'; import { AccountRepository } from '../../../../account/account.repository'; import { SHORT_TOKEN_PARAM } from '../constants'; import { PassportStrategy } from '../strategies'; @@ -57,10 +62,9 @@ export default ( false, ); } - } catch (err: unknown) { + } catch (error: unknown) { // Exception occurred while fetching member - const error = err instanceof Error ? err : new Error(String(err)); - return done(spreadException ? error : new UnauthorizedMember(), false); + return done(spreadException ? buildError(error) : new UnauthorizedMember(), false); } }, ), diff --git a/src/services/file/utils/errors.ts b/src/services/file/utils/errors.ts index fe20713518..5a53925534 100644 --- a/src/services/file/utils/errors.ts +++ b/src/services/file/utils/errors.ts @@ -1,5 +1,7 @@ import { StatusCodes } from 'http-status-codes'; +import { createError } from '@fastify/error'; + import { ErrorFactory, FAILURE_MESSAGES } from '@graasp/sdk'; import { PLUGIN_NAME } from './constants'; @@ -108,32 +110,17 @@ export class S3FileNotFound extends GraaspFileError { } } -export class UploadEmptyFileError extends GraaspFileError { - constructor(data?: unknown) { - super( - { - code: 'GPFERR008', - statusCode: StatusCodes.BAD_REQUEST, - message: FAILURE_MESSAGES.UPLOAD_EMPTY_FILE, - }, - data, - ); - } -} +export const UploadEmptyFileError = createError( + 'GPFERR008', + FAILURE_MESSAGES.UPLOAD_EMPTY_FILE, + StatusCodes.BAD_REQUEST, +); -export class UploadFileUnexpectedError extends GraaspFileError { - constructor(data?: unknown) { - super( - { - code: 'GPFERR010', - statusCode: StatusCodes.INTERNAL_SERVER_ERROR, - // TODO: change message - message: FAILURE_MESSAGES.UPLOAD_FILE_UNEXPECTED_ERROR, - }, - data, - ); - } -} +export const UploadFileUnexpectedError = createError( + 'GPFERR010', + FAILURE_MESSAGES.UPLOAD_FILE_UNEXPECTED_ERROR, + StatusCodes.INTERNAL_SERVER_ERROR, +); export class DownloadFileUnexpectedError extends GraaspFileError { constructor(data?: unknown) { diff --git a/src/services/item/plugins/file/itemFile.controller.test.ts b/src/services/item/plugins/file/itemFile.controller.test.ts index c6c124b48c..aefacff8e2 100644 --- a/src/services/item/plugins/file/itemFile.controller.test.ts +++ b/src/services/item/plugins/file/itemFile.controller.test.ts @@ -345,7 +345,9 @@ describe('File Item routes tests', () => { headers: form.getHeaders(), }); - expect(response.json()).toMatchObject(new StorageExceeded(expect.anything())); + const error = new StorageExceeded(); + expect(response.statusCode).toEqual(error.statusCode); + expect(response.json().message).toMatch(error.message); }); it('Cannot upload empty file', async () => { @@ -367,7 +369,9 @@ describe('File Item routes tests', () => { headers: form.getHeaders(), }); - expect(response.json().message).toEqual(new UploadEmptyFileError().message); + const error = new UploadEmptyFileError(); + expect(response.statusCode).toEqual(error.statusCode); + expect(response.json().message).toEqual(error.message); expect(deleteObjectsMock).toHaveBeenCalled(); }); }); @@ -431,7 +435,9 @@ describe('File Item routes tests', () => { headers: form.getHeaders(), }); - expect(response.json()).toMatchObject(new UploadFileUnexpectedError(expect.anything())); + const error = new UploadFileUnexpectedError(); + expect(response.statusCode).toEqual(error.statusCode); + expect(response.json().message).toMatch(error.message); }); }); }); diff --git a/src/services/item/plugins/file/itemFile.controller.ts b/src/services/item/plugins/file/itemFile.controller.ts index 880e031c15..c0dbd043cc 100644 --- a/src/services/item/plugins/file/itemFile.controller.ts +++ b/src/services/item/plugins/file/itemFile.controller.ts @@ -8,6 +8,7 @@ import { type FileItemProperties, getFileExtension } from '@graasp/sdk'; import { resolveDependency } from '../../../../di/utils'; import { db } from '../../../../drizzle/db'; import { asDefined, assertIsDefined } from '../../../../utils/assertions'; +import { buildError } from '../../../../utils/errors'; import { isAuthenticated, matchOne, optionalIsAuthenticated } from '../../../auth/plugins/passport'; import { assertIsMember, isMember } from '../../../authentication'; import { AuthorizedItemService } from '../../../authorizedItem.service'; @@ -150,11 +151,10 @@ const basePlugin: FastifyPluginAsyncTypebox = async (fastify) => { } items.push(item); - } catch (err: unknown) { + } catch (error: unknown) { // ignore errors - log.error(err); - const error = err instanceof Error ? err : new Error(String(err)); - errors.push(error); + log.error(error); + errors.push(buildError(error)); } finally { // force close to avoid hanging // necessary for errors that don't read the stream diff --git a/src/services/item/plugins/file/utils/errors.ts b/src/services/item/plugins/file/utils/errors.ts index 517fc7bbdc..b345474bff 100644 --- a/src/services/item/plugins/file/utils/errors.ts +++ b/src/services/item/plugins/file/utils/errors.ts @@ -1,20 +1,15 @@ import { StatusCodes } from 'http-status-codes'; +import { createError } from '@fastify/error'; + import { ErrorFactory, FAILURE_MESSAGES } from '@graasp/sdk'; import { PLUGIN_NAME } from './constants'; export const GraaspFileItemError = ErrorFactory(PLUGIN_NAME); -export class StorageExceeded extends GraaspFileItemError { - constructor(data?: unknown) { - super( - { - code: 'GPFERR009', - statusCode: StatusCodes.INSUFFICIENT_STORAGE, - message: FAILURE_MESSAGES.STORAGE_EXCEEDED, - }, - data, - ); - } -} +export const StorageExceeded = createError( + 'GPFERR009', + FAILURE_MESSAGES.STORAGE_EXCEEDED, + StatusCodes.INSUFFICIENT_STORAGE, +); diff --git a/src/utils/errors.ts b/src/utils/errors.ts index d5eaf0c4e5..14b0a8857d 100644 --- a/src/utils/errors.ts +++ b/src/utils/errors.ts @@ -4,6 +4,13 @@ import { createError } from '@fastify/error'; import { ErrorFactory, FAILURE_MESSAGES } from '@graasp/sdk'; +export function buildError(err: unknown) { + if (err instanceof Error) { + return err; + } + return new Error(String(err)); +} + export const ConfigurationError = ErrorFactory('config'); export const CoreError = ErrorFactory('core'); From 4088ac40a79c1b9d7b38d6cde6bb7b257ba2f6d3 Mon Sep 17 00:00:00 2001 From: kim Date: Thu, 26 Feb 2026 16:26:52 +0000 Subject: [PATCH 3/3] feat: use fastify errors --- src/services/account/errors.ts | 38 ++-- src/services/action/utils/errors.ts | 22 +-- src/services/chat/errors.ts | 74 +++----- src/services/item/errors.ts | 23 +-- .../item/plugins/embeddedLink/errors.ts | 23 +-- .../item/plugins/geolocation/errors.ts | 86 +++------ src/services/item/plugins/html/errors.ts | 35 ++-- src/services/item/plugins/html/h5p/errors.ts | 36 ++-- .../item/plugins/html/h5p/test/errors.test.ts | 6 - .../item/plugins/html/html.service.ts | 8 +- .../item/plugins/importExport/errors.ts | 103 ++++------- .../item/plugins/invitation/utils/errors.ts | 167 ++++++------------ .../item/plugins/itemVisibility/errors.ts | 105 ++++------- 13 files changed, 215 insertions(+), 511 deletions(-) diff --git a/src/services/account/errors.ts b/src/services/account/errors.ts index 9d1000a062..12f514ab46 100644 --- a/src/services/account/errors.ts +++ b/src/services/account/errors.ts @@ -1,31 +1,17 @@ import { StatusCodes } from 'http-status-codes'; -import { ErrorFactory, FAILURE_MESSAGES } from '@graasp/sdk'; +import { createError } from '@fastify/error'; -export const GraaspAccountError = ErrorFactory('graasp-plugin-account'); +import { FAILURE_MESSAGES } from '@graasp/sdk'; -export class AccountNotFound extends GraaspAccountError { - constructor(data?: unknown) { - super( - { - code: 'GPAECCRR001', - statusCode: StatusCodes.NOT_FOUND, - message: FAILURE_MESSAGES.ACCOUNT_NOT_FOUND, - }, - data, - ); - } -} +export const AccountNotFound = createError( + 'GPAECCRR001', + FAILURE_MESSAGES.ACCOUNT_NOT_FOUND, + StatusCodes.NOT_FOUND, +); -export class NotMemberOrGuest extends GraaspAccountError { - constructor(data?: unknown) { - super( - { - code: 'GPAECCRR002', - statusCode: StatusCodes.FORBIDDEN, - message: FAILURE_MESSAGES.NOT_MEMBER_OR_GUEST, - }, - data, - ); - } -} +export const NotMemberOrGuest = createError( + 'GPAECCRR002', + FAILURE_MESSAGES.NOT_MEMBER_OR_GUEST, + StatusCodes.FORBIDDEN, +); diff --git a/src/services/action/utils/errors.ts b/src/services/action/utils/errors.ts index 481a3ee17f..39a7c43441 100644 --- a/src/services/action/utils/errors.ts +++ b/src/services/action/utils/errors.ts @@ -1,19 +1,9 @@ import { StatusCodes } from 'http-status-codes'; -import { ErrorFactory } from '@graasp/sdk'; +import { createError } from '@fastify/error'; -import { PLUGIN_NAME } from '../constants'; - -export const GraaspActionError = ErrorFactory(PLUGIN_NAME); -export class CannotWriteFileError extends GraaspActionError { - constructor(data?: unknown) { - super( - { - code: 'GPAERR001', - statusCode: StatusCodes.NOT_FOUND, - message: 'A file was not created properly for the requested archive', - }, - data, - ); - } -} +export const CannotWriteFileError = createError( + 'GPAERR001', + 'A file was not created properly for the requested archive', + StatusCodes.NOT_FOUND, +); diff --git a/src/services/chat/errors.ts b/src/services/chat/errors.ts index 366f3e2cd5..c0a9970ddd 100644 --- a/src/services/chat/errors.ts +++ b/src/services/chat/errors.ts @@ -2,67 +2,33 @@ import { StatusCodes } from 'http-status-codes'; import { createError } from '@fastify/error'; -import { ErrorFactory } from '@graasp/sdk'; - -const PLUGIN_NAME = 'graasp-plugin-chatbox'; - /** * Errors thrown by the chat tasks */ -export const GraaspChatboxError = ErrorFactory(PLUGIN_NAME); - -export class ChatMessageNotFound extends GraaspChatboxError { - constructor(data?: unknown) { - super( - { - code: 'GICERR003', - statusCode: StatusCodes.NOT_FOUND, - message: 'Chat Message not found', - }, - data, - ); - } -} +export const ChatMessageNotFound = createError( + 'GICERR003', + 'Chat Message not found', + StatusCodes.NOT_FOUND, +); -export class MemberCannotEditMessage extends GraaspChatboxError { - constructor(data?: unknown) { - super( - { - code: 'GICERR002', - statusCode: StatusCodes.UNAUTHORIZED, - message: 'Member can only edit own messages', - }, - data, - ); - } -} +export const MemberCannotEditMessage = createError( + 'GICERR002', + 'Member can only edit own messages', + StatusCodes.UNAUTHORIZED, +); -export class MemberCannotDeleteMessage extends GraaspChatboxError { - constructor(data: { id: string }) { - super( - { - code: 'GICERR005', - statusCode: StatusCodes.UNAUTHORIZED, - message: 'Member can only delete own messages', - }, - data.id, - ); - } -} +export const MemberCannotDeleteMessage = createError( + 'GICERR005', + 'Member can only delete own messages', + StatusCodes.UNAUTHORIZED, +); -export class MemberCannotAccessMention extends GraaspChatboxError { - constructor(data: { id: string }) { - super( - { - code: 'GICERR004', - statusCode: StatusCodes.UNAUTHORIZED, - message: 'Member can only view own mentions', - }, - data.id, - ); - } -} +export const MemberCannotAccessMention = createError( + 'GICERR004', + 'Member can only view own mentions', + StatusCodes.UNAUTHORIZED, +); export const ChatMentionNotFound = createError( 'GICERR006', diff --git a/src/services/item/errors.ts b/src/services/item/errors.ts index 9cc96bcef2..24c3d98b3b 100644 --- a/src/services/item/errors.ts +++ b/src/services/item/errors.ts @@ -2,31 +2,18 @@ import { StatusCodes } from 'http-status-codes'; import { createError } from '@fastify/error'; -import { ErrorFactory } from '@graasp/sdk'; - -const PLUGIN_NAME = 'graasp-plugin-item'; - /** * Errors thrown by the item services */ -export const GraaspItemError = ErrorFactory(PLUGIN_NAME); - export const WrongItemTypeError = createError( 'GIERR001', 'Item does not have the correct type', StatusCodes.BAD_REQUEST, ); -export class ItemOrderingError extends GraaspItemError { - constructor(reason?: string) { - super( - { - code: 'GIERR002', - statusCode: StatusCodes.INTERNAL_SERVER_ERROR, - message: 'Error while rescaling', - }, - reason, - ); - } -} +export const ItemOrderingError = createError( + 'GIERR002', + 'Error while rescaling', + StatusCodes.INTERNAL_SERVER_ERROR, +); diff --git a/src/services/item/plugins/embeddedLink/errors.ts b/src/services/item/plugins/embeddedLink/errors.ts index c33b3176ca..fc382dab33 100644 --- a/src/services/item/plugins/embeddedLink/errors.ts +++ b/src/services/item/plugins/embeddedLink/errors.ts @@ -1,20 +1,9 @@ import { StatusCodes } from 'http-status-codes'; -import { ErrorFactory } from '@graasp/sdk'; +import { createError } from '@fastify/error'; -const PLUGIN_NAME = 'graasp-plugin-item-embedded-link'; - -export const GraaspError = ErrorFactory(PLUGIN_NAME); - -export class InvalidUrl extends GraaspError { - constructor(data?: unknown) { - super( - { - code: 'GPIELERR002', - statusCode: StatusCodes.BAD_REQUEST, - message: `The URL is not valid.`, - }, - data, - ); - } -} +export const InvalidUrl = createError( + 'GPIELERR002', + `The URL is not valid.`, + StatusCodes.BAD_REQUEST, +); diff --git a/src/services/item/plugins/geolocation/errors.ts b/src/services/item/plugins/geolocation/errors.ts index 267f3f204d..f386c4477f 100644 --- a/src/services/item/plugins/geolocation/errors.ts +++ b/src/services/item/plugins/geolocation/errors.ts @@ -1,63 +1,27 @@ import { StatusCodes } from 'http-status-codes'; -import { ErrorFactory } from '@graasp/sdk'; - -const PLUGIN_NAME = 'graasp-plugin-item-geolocation'; - -/** - * Errors thrown by item geolocation - */ - -export const GraaspItemGeolocationError = ErrorFactory(PLUGIN_NAME); - -export class ItemGeolocationNotFound extends GraaspItemGeolocationError { - constructor(data?: unknown) { - super( - { - code: 'GIGEOERR001', - statusCode: StatusCodes.NOT_FOUND, - message: 'Geolocation not found', - }, - data, - ); - } -} - -export class PartialItemGeolocation extends GraaspItemGeolocationError { - constructor(data?: { lat?: unknown; lng?: unknown }) { - super( - { - code: 'GIGEOERR002', - statusCode: StatusCodes.BAD_REQUEST, - message: 'Geolocation should have both lat and lng', - }, - data, - ); - } -} - -export class MissingGeolocationSearchParams extends GraaspItemGeolocationError { - constructor(data?: unknown) { - super( - { - code: 'GIGEOERR003', - statusCode: StatusCodes.BAD_REQUEST, - message: 'Geolocation Search should include parent item, or all lat1, lat2, lng1 and lng2', - }, - data, - ); - } -} - -export class MissingGeolocationApiKey extends GraaspItemGeolocationError { - constructor(data?: unknown) { - super( - { - code: 'GIGEOERR004', - statusCode: StatusCodes.SERVICE_UNAVAILABLE, - message: 'Geolocation API key is not defined', - }, - data, - ); - } -} +import { createError } from '@fastify/error'; + +export const ItemGeolocationNotFound = createError( + 'GIGEOERR001', + 'Geolocation not found', + StatusCodes.NOT_FOUND, +); + +export const PartialItemGeolocation = createError( + 'GIGEOERR002', + 'Geolocation should have both lat and lng', + StatusCodes.BAD_REQUEST, +); + +export const MissingGeolocationSearchParams = createError( + 'GIGEOERR003', + 'Geolocation Search should include parent item, or all lat1, lat2, lng1 and lng2', + StatusCodes.BAD_REQUEST, +); + +export const MissingGeolocationApiKey = createError( + 'GIGEOERR004', + 'Geolocation API key is not defined', + StatusCodes.SERVICE_UNAVAILABLE, +); diff --git a/src/services/item/plugins/html/errors.ts b/src/services/item/plugins/html/errors.ts index 4de1ed34bc..f05cd2c7e3 100644 --- a/src/services/item/plugins/html/errors.ts +++ b/src/services/item/plugins/html/errors.ts @@ -1,31 +1,18 @@ import { StatusCodes } from 'http-status-codes'; -import { ErrorFactory } from '@graasp/sdk'; +import { createError } from '@fastify/error'; -export const GraaspHtmlError = ErrorFactory('graasp-plugin-html'); - -export class HtmlItemNotFoundError extends GraaspHtmlError { - constructor(data?: unknown) { - super( - { - code: 'GPHTMLERR002', - statusCode: StatusCodes.NOT_FOUND, - message: 'Html item not found', - }, - data, - ); - } -} +export const HtmlItemNotFoundError = createError( + 'GPHTMLERR002', + 'Html item not found', + StatusCodes.NOT_FOUND, +); /** * Fallback error on unexpected internal error, opaque to avoid leaking information */ -export class HtmlImportError extends GraaspHtmlError { - constructor() { - super({ - code: 'GPHTMLERR003', - statusCode: StatusCodes.INTERNAL_SERVER_ERROR, - message: 'Unexpected server error while importing Html', - }); - } -} +export const HtmlImportError = createError( + 'GPHTMLERR003', + 'Unexpected server error while importing Html', + StatusCodes.INTERNAL_SERVER_ERROR, +); diff --git a/src/services/item/plugins/html/h5p/errors.ts b/src/services/item/plugins/html/h5p/errors.ts index 273b15b77c..69ada29aad 100644 --- a/src/services/item/plugins/html/h5p/errors.ts +++ b/src/services/item/plugins/html/h5p/errors.ts @@ -1,29 +1,15 @@ import { StatusCodes } from 'http-status-codes'; -import { GraaspHtmlError } from '../errors'; +import { createError } from '@fastify/error'; -export class H5PInvalidFileError extends GraaspHtmlError { - constructor(data?: unknown) { - super( - { - code: 'GPH5PERR001', - statusCode: StatusCodes.BAD_REQUEST, - message: 'File is not a valid H5P package', - }, - data, - ); - } -} +export const H5PInvalidFileError = createError( + 'GPH5PERR001', + 'File is not a valid H5P package', + StatusCodes.BAD_REQUEST, +); -export class H5PInvalidManifestError extends GraaspHtmlError { - constructor(data?: unknown) { - super( - { - code: 'GPH5PERR002', - statusCode: StatusCodes.BAD_REQUEST, - message: 'Invalid h5p.json manifest file', - }, - data, - ); - } -} +export const H5PInvalidManifestError = createError( + 'GPH5PERR002', + 'Invalid h5p.json manifest file', + StatusCodes.BAD_REQUEST, +); diff --git a/src/services/item/plugins/html/h5p/test/errors.test.ts b/src/services/item/plugins/html/h5p/test/errors.test.ts index d0547bd792..904ec1f3ba 100644 --- a/src/services/item/plugins/html/h5p/test/errors.test.ts +++ b/src/services/item/plugins/html/h5p/test/errors.test.ts @@ -11,9 +11,7 @@ describe('Custom errors', () => { const error = new H5PInvalidFileError(MOCK_ITEM); expect(error.code).toEqual('GPH5PERR001'); - expect(error.data).toEqual(MOCK_ITEM); expect(error.message).toEqual('File is not a valid H5P package'); - expect(error.origin).toEqual('graasp-plugin-html'); expect(error.statusCode).toEqual(StatusCodes.BAD_REQUEST); }); @@ -21,9 +19,7 @@ describe('Custom errors', () => { const error = new HtmlItemNotFoundError(MOCK_ITEM); expect(error.code).toEqual('GPHTMLERR002'); - expect(error.data).toEqual(MOCK_ITEM); expect(error.message).toEqual('Html item not found'); - expect(error.origin).toEqual('graasp-plugin-html'); expect(error.statusCode).toEqual(StatusCodes.NOT_FOUND); }); @@ -31,9 +27,7 @@ describe('Custom errors', () => { const error = new HtmlImportError(); expect(error.code).toEqual('GPHTMLERR003'); - expect(error.data).toBeUndefined(); expect(error.message).toEqual('Unexpected server error while importing Html'); - expect(error.origin).toEqual('graasp-plugin-html'); expect(error.statusCode).toEqual(StatusCodes.INTERNAL_SERVER_ERROR); }); }); diff --git a/src/services/item/plugins/html/html.service.ts b/src/services/item/plugins/html/html.service.ts index e4d0914afd..01e66e7913 100644 --- a/src/services/item/plugins/html/html.service.ts +++ b/src/services/item/plugins/html/html.service.ts @@ -19,7 +19,7 @@ import type { FileStorageType } from '../../../file/types'; import { fileRepositoryFactory } from '../../../file/utils/factory'; import { StorageService } from '../../../member/plugins/storage/memberStorage.service'; import { type ItemRaw } from '../../item'; -import { GraaspHtmlError, HtmlImportError } from './errors'; +import { HtmlImportError } from './errors'; import { DEFAULT_MIME_TYPE } from './h5p/constants'; import type { HtmlValidator } from './validator'; @@ -214,10 +214,8 @@ export abstract class HtmlService { log?.error('graasp-plugin-html: unexpected error occured while importing Html:'); log?.error(error); // wrap into plugin error type if not ours - if (!(error instanceof GraaspHtmlError)) { - error = new HtmlImportError(); - } - throw error; + // if the caught error is not one of our plugin errors (codes start with GPHTMLERR) + throw new HtmlImportError(error); } finally { // in all cases, remove local temp folder await tmpDir.cleanup(); diff --git a/src/services/item/plugins/importExport/errors.ts b/src/services/item/plugins/importExport/errors.ts index df46be1803..510feb2f83 100644 --- a/src/services/item/plugins/importExport/errors.ts +++ b/src/services/item/plugins/importExport/errors.ts @@ -1,72 +1,35 @@ import { StatusCodes } from 'http-status-codes'; -import { ErrorFactory, FAILURE_MESSAGES } from '@graasp/sdk'; - -import { PLUGIN_NAME } from './constants'; - -export const GraaspItemZipError = ErrorFactory(PLUGIN_NAME); - -export class FileIsInvalidArchiveError extends GraaspItemZipError { - constructor(data?: unknown) { - super( - { - code: 'GPIZERR001', - statusCode: StatusCodes.BAD_REQUEST, - message: FAILURE_MESSAGES.INVALID_ARCHIVE_FILE, - }, - data, - ); - } -} - -export class InvalidFileItemError extends GraaspItemZipError { - constructor(data?: unknown) { - super( - { - code: 'GPIZERR002', - statusCode: StatusCodes.BAD_REQUEST, - message: FAILURE_MESSAGES.INVALID_FILE_ITEM, - }, - data, - ); - } -} - -export class UnexpectedExportError extends GraaspItemZipError { - constructor(data?: unknown) { - super( - { - code: 'GPIZERR003', - statusCode: StatusCodes.INTERNAL_SERVER_ERROR, - message: FAILURE_MESSAGES.UNEXPECTED_EXPORT_ERROR, - }, - data, - ); - } -} - -export class InvalidItemTypeForDownloadError extends GraaspItemZipError { - constructor(data?: unknown) { - super( - { - code: 'GPIZERR004', - statusCode: StatusCodes.BAD_REQUEST, - message: FAILURE_MESSAGES.INVALID_ITEM_TYPE_FOR_DOWNLOAD, - }, - data, - ); - } -} - -export class GraaspExportInvalidFileError extends GraaspItemZipError { - constructor(data?: unknown) { - super( - { - code: 'GPIZERR005', - statusCode: StatusCodes.BAD_REQUEST, - message: FAILURE_MESSAGES.GRAASP_EXPORT_FILE_ERROR, - }, - data, - ); - } -} +import { createError } from '@fastify/error'; + +import { FAILURE_MESSAGES } from '@graasp/sdk'; + +export const FileIsInvalidArchiveError = createError( + 'GPIZERR001', + FAILURE_MESSAGES.INVALID_ARCHIVE_FILE, + StatusCodes.BAD_REQUEST, +); + +export const InvalidFileItemError = createError( + 'GPIZERR002', + FAILURE_MESSAGES.INVALID_FILE_ITEM, + StatusCodes.BAD_REQUEST, +); + +export const UnexpectedExportError = createError( + 'GPIZERR003', + FAILURE_MESSAGES.UNEXPECTED_EXPORT_ERROR, + StatusCodes.INTERNAL_SERVER_ERROR, +); + +export const InvalidItemTypeForDownloadError = createError( + 'GPIZERR004', + FAILURE_MESSAGES.INVALID_ITEM_TYPE_FOR_DOWNLOAD, + StatusCodes.BAD_REQUEST, +); + +export const GraaspExportInvalidFileError = createError( + 'GPIZERR005', + FAILURE_MESSAGES.GRAASP_EXPORT_FILE_ERROR, + StatusCodes.BAD_REQUEST, +); diff --git a/src/services/item/plugins/invitation/utils/errors.ts b/src/services/item/plugins/invitation/utils/errors.ts index bdc8737ef4..5e6956d702 100644 --- a/src/services/item/plugins/invitation/utils/errors.ts +++ b/src/services/item/plugins/invitation/utils/errors.ts @@ -1,128 +1,65 @@ import { StatusCodes } from 'http-status-codes'; -import { ErrorFactory, FAILURE_MESSAGES } from '@graasp/sdk'; +import { createError } from '@fastify/error'; -import { PLUGIN_NAME } from './constants'; +import { FAILURE_MESSAGES } from '@graasp/sdk'; -export const GraaspInvitationsError = ErrorFactory(PLUGIN_NAME); +export const InvitationNotFound = createError( + 'GPINVERR003', + FAILURE_MESSAGES.INVITATION_NOT_FOUND, + StatusCodes.NOT_FOUND, +); -export class InvitationNotFound extends GraaspInvitationsError { - constructor(data?: unknown) { - super( - { - code: 'GPINVERR003', - statusCode: StatusCodes.NOT_FOUND, - message: FAILURE_MESSAGES.INVITATION_NOT_FOUND, - }, - data, - ); - } -} +export const MissingEmailColumnInCSVError = createError( + 'GPINVERR005', + FAILURE_MESSAGES.INVITATION_CSV_MISSING_EMAIL_COLUMN, + StatusCodes.BAD_REQUEST, +); -export class MissingEmailColumnInCSVError extends GraaspInvitationsError { - constructor() { - super({ - code: 'GPINVERR005', - statusCode: StatusCodes.BAD_REQUEST, - message: FAILURE_MESSAGES.INVITATION_CSV_MISSING_EMAIL_COLUMN, - }); - } -} +export const MissingGroupColumnInCSVError = createError( + 'GPINVERR006', + FAILURE_MESSAGES.INVITATION_CSV_MISSING_GROUP_COLUMN, + StatusCodes.BAD_REQUEST, +); -export class MissingGroupColumnInCSVError extends GraaspInvitationsError { - constructor() { - super({ - code: 'GPINVERR006', - statusCode: StatusCodes.BAD_REQUEST, - message: FAILURE_MESSAGES.INVITATION_CSV_MISSING_GROUP_COLUMN, - }); - } -} +export const MissingEmailInRowError = createError( + 'GPINVERR007', + FAILURE_MESSAGES.INVITATION_CSV_MISSING_EMAIL_IN_ROW, + StatusCodes.BAD_REQUEST, +); -export class MissingEmailInRowError extends GraaspInvitationsError { - constructor(data?: unknown) { - super( - { - code: 'GPINVERR007', - statusCode: StatusCodes.BAD_REQUEST, - message: FAILURE_MESSAGES.INVITATION_CSV_MISSING_EMAIL_IN_ROW, - }, - data, - ); - } -} +export const MissingGroupInRowError = createError( + 'GPINVERR008', + FAILURE_MESSAGES.INVITATION_CSV_MISSING_GROUP_IN_ROW, + StatusCodes.BAD_REQUEST, +); -export class MissingGroupInRowError extends GraaspInvitationsError { - constructor(data?: unknown) { - super( - { - code: 'GPINVERR008', - statusCode: StatusCodes.BAD_REQUEST, - message: FAILURE_MESSAGES.INVITATION_CSV_MISSING_GROUP_IN_ROW, - }, - data, - ); - } -} +export const NoFileProvidedForInvitations = createError( + 'GPINVERR011', + FAILURE_MESSAGES.INVITATION_CSV_NO_FILE_PROVIDED, + StatusCodes.BAD_REQUEST, +); -export class NoFileProvidedForInvitations extends GraaspInvitationsError { - constructor(data?: unknown) { - super( - { - code: 'GPINVERR011', - statusCode: StatusCodes.BAD_REQUEST, - message: FAILURE_MESSAGES.INVITATION_CSV_NO_FILE_PROVIDED, - }, - data, - ); - } -} +export const NoDataInFile = createError( + 'GPINVERR012', + FAILURE_MESSAGES.INVITATION_CSV_NO_DATA_IN_FILE, + StatusCodes.BAD_REQUEST, +); -export class NoDataInFile extends GraaspInvitationsError { - constructor(data?: unknown) { - super( - { - code: 'GPINVERR012', - statusCode: StatusCodes.BAD_REQUEST, - message: FAILURE_MESSAGES.INVITATION_CSV_NO_DATA_IN_FILE, - }, - data, - ); - } -} +export const CantCreateStructureInNoFolderItem = createError( + 'GPINVERR013', + FAILURE_MESSAGES.INVITATION_CANNOT_CREATE_STRUCTURE_IN_NON_FOLDER_ITEM, + StatusCodes.BAD_REQUEST, +); -export class CantCreateStructureInNoFolderItem extends GraaspInvitationsError { - constructor(data?: unknown) { - super( - { - code: 'GPINVERR013', - statusCode: StatusCodes.BAD_REQUEST, - message: FAILURE_MESSAGES.INVITATION_CANNOT_CREATE_STRUCTURE_IN_NON_FOLDER_ITEM, - }, - data, - ); - } -} +export const TemplateItemDoesNotExist = createError( + 'GPINVERR014', + FAILURE_MESSAGES.INVITATION_CSV_TEMPLATE_ITEM_DOES_NOT_EXIST, + StatusCodes.BAD_REQUEST, +); -export class TemplateItemDoesNotExist extends GraaspInvitationsError { - constructor(data?: unknown) { - super( - { - code: 'GPINVERR014', - statusCode: StatusCodes.BAD_REQUEST, - message: FAILURE_MESSAGES.INVITATION_CSV_TEMPLATE_ITEM_DOES_NOT_EXIST, - }, - data, - ); - } -} - -export class NoInvitationReceivedFound extends GraaspInvitationsError { - constructor() { - super({ - code: 'GPINVERR015', - statusCode: StatusCodes.BAD_REQUEST, - message: FAILURE_MESSAGES.NO_INVITATION_RECEIVED, - }); - } -} +export const NoInvitationReceivedFound = createError( + 'GPINVERR015', + FAILURE_MESSAGES.NO_INVITATION_RECEIVED, + StatusCodes.BAD_REQUEST, +); diff --git a/src/services/item/plugins/itemVisibility/errors.ts b/src/services/item/plugins/itemVisibility/errors.ts index 9c8c7a43a7..59062e1508 100644 --- a/src/services/item/plugins/itemVisibility/errors.ts +++ b/src/services/item/plugins/itemVisibility/errors.ts @@ -1,80 +1,37 @@ import { StatusCodes } from 'http-status-codes'; -import { ErrorFactory } from '@graasp/sdk'; +import { createError } from '@fastify/error'; -export const PLUGIN_NAME = 'graasp-plugin-item-visibilities'; +export const ItemHasVisibility = createError( + 'GITERR004', + 'Item already has visibility', + StatusCodes.BAD_REQUEST, +); +export const ItemVisibilityNotFound = createError( + 'GITERR005', + 'Item visibility not found', + StatusCodes.NOT_FOUND, +); +export const VisibilityNotFound = createError( + 'GITERR006', + 'Visibility not found', + StatusCodes.NOT_FOUND, +); -export const GraaspItemVisibilityError = ErrorFactory(PLUGIN_NAME); +export const CannotModifyParentVisibility = createError( + 'GITERR008', + 'Cannot modify inherited Visibility', + StatusCodes.FORBIDDEN, +); -export class ItemHasVisibility extends GraaspItemVisibilityError { - constructor(data?: unknown) { - super( - { - code: 'GITERR004', - statusCode: StatusCodes.BAD_REQUEST, - message: 'Item already has visibility', - }, - data, - ); - } -} -export class ItemVisibilityNotFound extends GraaspItemVisibilityError { - constructor(data?: unknown) { - super( - { - code: 'GITERR005', - statusCode: StatusCodes.NOT_FOUND, - message: 'Item visibility not found', - }, - data, - ); - } -} -export class VisibilityNotFound extends GraaspItemVisibilityError { - constructor(data?: unknown) { - super( - { code: 'GITERR006', statusCode: StatusCodes.NOT_FOUND, message: 'Visibility not found' }, - data, - ); - } -} +export const ConflictingVisibilitiesInTheHierarchy = createError( + 'GITERR007', + 'Visibility already present in the hierarchy - itself or ancestors', + StatusCodes.FORBIDDEN, +); -export class CannotModifyParentVisibility extends GraaspItemVisibilityError { - constructor(data?: unknown) { - super( - { - code: 'GITERR008', - statusCode: StatusCodes.FORBIDDEN, - message: 'Cannot modify inherited Visibility', - }, - data, - ); - } -} - -export class ConflictingVisibilitiesInTheHierarchy extends GraaspItemVisibilityError { - constructor(data?: unknown) { - super( - { - code: 'GITERR007', - statusCode: StatusCodes.FORBIDDEN, - message: 'Visibility already present in the hierarchy - itself or ancestors', - }, - data, - ); - } -} - -export class InvalidUseOfItemVisibilityRepository extends GraaspItemVisibilityError { - constructor(data?: unknown) { - super( - { - code: 'GITERR008', - statusCode: StatusCodes.BAD_REQUEST, - message: - 'ItemVisibilityRepository was not used correctly, this should not happen. Consider this an internal error. Contact your local developer team.', - }, - data, - ); - } -} +export const InvalidUseOfItemVisibilityRepository = createError( + 'GITERR008', + 'ItemVisibilityRepository was not used correctly, this should not happen. Consider this an internal error. Contact your local developer team.', + StatusCodes.BAD_REQUEST, +);