From f69ddcd79646389f2fdbc764b96a7e42e4aa263b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=A4=95=E0=A4=BE=E0=A4=B0=E0=A4=A4=E0=A5=8B=E0=A4=AB?= =?UTF-8?q?=E0=A5=8D=E0=A4=AB=E0=A5=87=E0=A4=B2=E0=A4=B8=E0=A5=8D=E0=A4=95?= =?UTF-8?q?=E0=A5=8D=E0=A4=B0=E0=A4=BF=E0=A4=AA=E0=A5=8D=E0=A4=9F=E2=84=A2?= Date: Wed, 27 Dec 2023 11:50:43 +0100 Subject: [PATCH] refactor(core): Use Dependency Injection for all Controller classes (no-changelog) (#8146) ## Review / Merge checklist - [x] PR title and summary are descriptive --- packages/cli/src/AbstractServer.ts | 3 +- packages/cli/src/ExternalHooks.ts | 8 +- .../ExternalSecrets.controller.ee.ts | 2 - packages/cli/src/Interfaces.ts | 136 +------------ packages/cli/src/InternalHooks.ts | 3 +- packages/cli/src/LoadNodesAndCredentials.ts | 4 +- packages/cli/src/Server.ts | 178 ++++++------------ packages/cli/src/commands/BaseCommand.ts | 4 +- packages/cli/src/config/index.ts | 1 - .../controllers/activeWorkflows.controller.ts | 2 - .../cli/src/controllers/auth.controller.ts | 2 - .../src/controllers/binaryData.controller.ts | 2 - .../communityPackages.controller.ts | 8 +- .../cli/src/controllers/debug.controller.ts | 2 - .../dynamicNodeParameters.controller.ts | 2 - .../cli/src/controllers/e2e.controller.ts | 25 +-- .../src/controllers/invitation.controller.ts | 21 +-- .../cli/src/controllers/ldap.controller.ts | 19 +- packages/cli/src/controllers/me.controller.ts | 2 - .../cli/src/controllers/mfa.controller.ts | 2 - .../src/controllers/nodeTypes.controller.ts | 9 +- .../oauth/oAuth1Credential.controller.ts | 2 - .../oauth/oAuth2Credential.controller.ts | 2 - .../controllers/orchestration.controller.ts | 4 +- .../cli/src/controllers/owner.controller.ts | 16 +- .../controllers/passwordReset.controller.ts | 2 - .../cli/src/controllers/role.controller.ts | 2 - .../cli/src/controllers/tags.controller.ts | 4 +- .../src/controllers/translation.controller.ts | 11 +- .../cli/src/controllers/users.controller.ts | 7 +- .../workflowStatistics.controller.ts | 6 +- .../src/databases/entities/AbstractEntity.ts | 7 +- packages/cli/src/decorators/OnShutdown.ts | 1 + packages/cli/src/decorators/RestController.ts | 3 + .../cli/src/decorators/registerController.ts | 12 +- .../environments/sourceControl/constants.ts | 1 - .../sourceControl.controller.ee.ts | 17 +- .../variables/variables.controller.ee.ts | 5 +- .../cli/src/license/license.controller.ts | 2 - packages/cli/src/services/frontend.service.ts | 6 +- packages/cli/src/shutdown/Shutdown.service.ts | 6 +- .../src/sso/saml/routes/saml.controller.ee.ts | 9 +- .../workflowHistory.controller.ee.ts | 2 - .../environments/SourceControl.test.ts | 23 +-- .../cli/test/integration/shared/testDb.ts | 3 +- .../integration/shared/utils/testServer.ts | 105 +++-------- packages/cli/test/shared/mocking.ts | 5 +- .../unit/controllers/owner.controller.test.ts | 20 +- .../translation.controller.test.ts | 8 +- packages/core/src/Interfaces.ts | 2 + packages/core/test/utils.ts | 3 +- 51 files changed, 209 insertions(+), 522 deletions(-) diff --git a/packages/cli/src/AbstractServer.ts b/packages/cli/src/AbstractServer.ts index 679e22142a..d0d7f78816 100644 --- a/packages/cli/src/AbstractServer.ts +++ b/packages/cli/src/AbstractServer.ts @@ -10,7 +10,6 @@ import { N8N_VERSION, inDevelopment, inTest } from '@/constants'; import { ActiveWorkflowRunner } from '@/ActiveWorkflowRunner'; import * as Db from '@/Db'; import { N8nInstanceType } from '@/Interfaces'; -import type { IExternalHooksClass } from '@/Interfaces'; import { ExternalHooks } from '@/ExternalHooks'; import { send, sendErrorResponse } from '@/ResponseHelper'; import { rawBodyReader, bodyParser, corsMiddleware } from '@/middlewares'; @@ -31,7 +30,7 @@ export abstract class AbstractServer { readonly app: express.Application; - protected externalHooks: IExternalHooksClass; + protected externalHooks: ExternalHooks; protected activeWorkflowRunner: ActiveWorkflowRunner; diff --git a/packages/cli/src/ExternalHooks.ts b/packages/cli/src/ExternalHooks.ts index 5a17f3c2f8..eea5f1f4cf 100644 --- a/packages/cli/src/ExternalHooks.ts +++ b/packages/cli/src/ExternalHooks.ts @@ -1,10 +1,6 @@ /* eslint-disable @typescript-eslint/no-var-requires */ import { Service } from 'typedi'; -import type { - IExternalHooksClass, - IExternalHooksFileData, - IExternalHooksFunctions, -} from '@/Interfaces'; +import type { IExternalHooksFileData, IExternalHooksFunctions } from '@/Interfaces'; import config from '@/config'; import { UserRepository } from '@db/repositories/user.repository'; import { CredentialsRepository } from '@db/repositories/credentials.repository'; @@ -13,7 +9,7 @@ import { WorkflowRepository } from '@db/repositories/workflow.repository'; import { ApplicationError } from 'n8n-workflow'; @Service() -export class ExternalHooks implements IExternalHooksClass { +export class ExternalHooks { externalHooks: { [key: string]: Array<() => {}>; } = {}; diff --git a/packages/cli/src/ExternalSecrets/ExternalSecrets.controller.ee.ts b/packages/cli/src/ExternalSecrets/ExternalSecrets.controller.ee.ts index d6487f9964..0cc8a6c086 100644 --- a/packages/cli/src/ExternalSecrets/ExternalSecrets.controller.ee.ts +++ b/packages/cli/src/ExternalSecrets/ExternalSecrets.controller.ee.ts @@ -1,12 +1,10 @@ import { Authorized, Get, Post, RestController, RequireGlobalScope } from '@/decorators'; import { ExternalSecretsRequest } from '@/requests'; import { Response } from 'express'; -import { Service } from 'typedi'; import { ExternalSecretsService } from './ExternalSecrets.service.ee'; import { ExternalSecretsProviderNotFoundError } from '@/errors/external-secrets-provider-not-found.error'; import { NotFoundError } from '@/errors/response-errors/not-found.error'; -@Service() @Authorized() @RestController('/external-secrets') export class ExternalSecretsController { diff --git a/packages/cli/src/Interfaces.ts b/packages/cli/src/Interfaces.ts index 05231828e8..b4930e54d7 100644 --- a/packages/cli/src/Interfaces.ts +++ b/packages/cli/src/Interfaces.ts @@ -44,6 +44,7 @@ import type { CredentialsRepository } from '@db/repositories/credentials.reposit import type { SettingsRepository } from '@db/repositories/settings.repository'; import type { UserRepository } from '@db/repositories/user.repository'; import type { WorkflowRepository } from '@db/repositories/workflow.repository'; +import type { ExternalHooks } from './ExternalHooks'; import type { LICENSE_FEATURES, LICENSE_QUOTAS } from './constants'; import type { WorkflowWithSharingsAndCredentials } from './workflows/workflows.types'; import type { WorkerJobStatusSummary } from './services/orchestration/worker/types'; @@ -254,11 +255,6 @@ export interface IExternalHooksFunctions { }; } -export interface IExternalHooksClass { - init(): Promise; - run(hookName: string, hookParameters?: any[]): Promise; -} - export type WebhookCORSRequest = Request & { method: 'OPTIONS' }; export type WebhookRequest = Request<{ path: string }> & { @@ -326,134 +322,6 @@ export interface ITelemetryUserDeletionData { migration_user_id?: string; } -export interface IInternalHooksClass { - onN8nStop(): Promise; - onServerStarted( - diagnosticInfo: IDiagnosticInfo, - firstWorkflowCreatedAt?: Date, - ): Promise; - onPersonalizationSurveySubmitted(userId: string, answers: Record): Promise; - onWorkflowCreated(user: User, workflow: IWorkflowBase, publicApi: boolean): Promise; - onWorkflowDeleted(user: User, workflowId: string, publicApi: boolean): Promise; - onWorkflowSaved(user: User, workflow: IWorkflowBase, publicApi: boolean): Promise; - onWorkflowBeforeExecute(executionId: string, data: IWorkflowExecutionDataProcess): Promise; - onWorkflowPostExecute( - executionId: string, - workflow: IWorkflowBase, - runData?: IRun, - userId?: string, - ): Promise; - onNodeBeforeExecute( - executionId: string, - workflow: IWorkflowBase, - nodeName: string, - ): Promise; - onNodePostExecute(executionId: string, workflow: IWorkflowBase, nodeName: string): Promise; - onUserDeletion(userDeletionData: { - user: User; - telemetryData: ITelemetryUserDeletionData; - publicApi: boolean; - }): Promise; - onUserInvite(userInviteData: { - user: User; - target_user_id: string[]; - public_api: boolean; - email_sent: boolean; - invitee_role: string; - }): Promise; - onUserRoleChange(userInviteData: { - user: User; - target_user_id: string; - public_api: boolean; - target_user_new_role: string; - }): Promise; - onUserReinvite(userReinviteData: { - user: User; - target_user_id: string; - public_api: boolean; - }): Promise; - onUserUpdate(userUpdateData: { user: User; fields_changed: string[] }): Promise; - onUserInviteEmailClick(userInviteClickData: { inviter: User; invitee: User }): Promise; - onUserPasswordResetEmailClick(userPasswordResetData: { user: User }): Promise; - onUserTransactionalEmail( - userTransactionalEmailData: { - user_id: string; - message_type: 'Reset password' | 'New user invite' | 'Resend invite'; - public_api: boolean; - }, - user?: User, - ): Promise; - onEmailFailed(failedEmailData: { - user: User; - message_type: 'Reset password' | 'New user invite' | 'Resend invite'; - public_api: boolean; - }): Promise; - onUserCreatedCredentials(userCreatedCredentialsData: { - user: User; - credential_name: string; - credential_type: string; - credential_id: string; - public_api: boolean; - }): Promise; - - onUserSharedCredentials(userSharedCredentialsData: { - user: User; - credential_name: string; - credential_type: string; - credential_id: string; - user_id_sharer: string; - user_ids_sharees_added: string[]; - sharees_removed: number | null; - }): Promise; - onUserPasswordResetRequestClick(userPasswordResetData: { user: User }): Promise; - onInstanceOwnerSetup(instanceOwnerSetupData: { user_id: string }, user?: User): Promise; - onUserSignup( - user: User, - userSignupData: { - user_type: AuthProviderType; - was_disabled_ldap_user: boolean; - }, - ): Promise; - onCommunityPackageInstallFinished(installationData: { - user: User; - input_string: string; - package_name: string; - success: boolean; - package_version?: string; - package_node_names?: string[]; - package_author?: string; - package_author_email?: string; - failure_reason?: string; - }): Promise; - onCommunityPackageUpdateFinished(updateData: { - user: User; - package_name: string; - package_version_current: string; - package_version_new: string; - package_node_names: string[]; - package_author?: string; - package_author_email?: string; - }): Promise; - onCommunityPackageDeleteFinished(deleteData: { - user: User; - package_name: string; - package_version?: string; - package_node_names?: string[]; - package_author?: string; - package_author_email?: string; - }): Promise; - onApiKeyCreated(apiKeyDeletedData: { user: User; public_api: boolean }): Promise; - onApiKeyDeleted(apiKeyDeletedData: { user: User; public_api: boolean }): Promise; - onVariableCreated(createData: { variable_type: string }): Promise; - onExternalSecretsProviderSettingsSaved(saveData: { - user_id?: string; - vault_type: string; - is_valid: boolean; - is_new: boolean; - error_message?: string; - }): Promise; -} - export interface IVersionNotificationSettings { enabled: boolean; endpoint: string; @@ -839,7 +707,7 @@ export interface PublicUser { export interface N8nApp { app: Application; restEndpoint: string; - externalHooks: IExternalHooksClass; + externalHooks: ExternalHooks; activeWorkflowRunner: ActiveWorkflowRunner; } diff --git a/packages/cli/src/InternalHooks.ts b/packages/cli/src/InternalHooks.ts index 45b5c8ed1c..c2829fb061 100644 --- a/packages/cli/src/InternalHooks.ts +++ b/packages/cli/src/InternalHooks.ts @@ -13,7 +13,6 @@ import { TelemetryHelpers } from 'n8n-workflow'; import { get as pslGet } from 'psl'; import type { IDiagnosticInfo, - IInternalHooksClass, ITelemetryUserDeletionData, IWorkflowDb, IExecutionTrackProperties, @@ -49,7 +48,7 @@ function userToPayload(user: User): { } @Service() -export class InternalHooks implements IInternalHooksClass { +export class InternalHooks { constructor( private telemetry: Telemetry, private nodeTypes: NodeTypes, diff --git a/packages/cli/src/LoadNodesAndCredentials.ts b/packages/cli/src/LoadNodesAndCredentials.ts index 042a15121a..7912679e1c 100644 --- a/packages/cli/src/LoadNodesAndCredentials.ts +++ b/packages/cli/src/LoadNodesAndCredentials.ts @@ -3,7 +3,7 @@ import { Container, Service } from 'typedi'; import path from 'path'; import fsPromises from 'fs/promises'; -import type { DirectoryLoader, Types } from 'n8n-core'; +import type { Class, DirectoryLoader, Types } from 'n8n-core'; import { CUSTOM_EXTENSION_ENV, InstanceSettings, @@ -250,7 +250,7 @@ export class LoadNodesAndCredentials { * Run a loader of source files of nodes and credentials in a directory. */ private async runDirectoryLoader( - constructor: new (...args: ConstructorParameters) => T, + constructor: Class>, dir: string, ) { const loader = new constructor(dir, this.excludeNodes, this.includeNodes); diff --git a/packages/cli/src/Server.ts b/packages/cli/src/Server.ts index 8d1dc08149..50333f2078 100644 --- a/packages/cli/src/Server.ts +++ b/packages/cli/src/Server.ts @@ -1,9 +1,6 @@ /* eslint-disable @typescript-eslint/no-unsafe-argument */ -/* eslint-disable @typescript-eslint/no-unnecessary-type-assertion */ -/* eslint-disable prefer-const */ /* eslint-disable @typescript-eslint/no-shadow */ /* eslint-disable @typescript-eslint/no-unsafe-return */ -/* eslint-disable @typescript-eslint/no-unused-vars */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { Container, Service } from 'typedi'; @@ -20,14 +17,9 @@ import type { ServeStaticOptions } from 'serve-static'; import type { FindManyOptions, FindOptionsWhere } from 'typeorm'; import { Not, In } from 'typeorm'; -import { InstanceSettings } from 'n8n-core'; +import { type Class, InstanceSettings } from 'n8n-core'; -import type { - ICredentialTypes, - ExecutionStatus, - IExecutionsSummary, - IN8nUISettings, -} from 'n8n-workflow'; +import type { ExecutionStatus, IExecutionsSummary, IN8nUISettings } from 'n8n-workflow'; import { jsonParse } from 'n8n-workflow'; // @ts-ignore @@ -52,7 +44,6 @@ import { registerController } from '@/decorators'; import { AuthController } from '@/controllers/auth.controller'; import { BinaryDataController } from '@/controllers/binaryData.controller'; import { DynamicNodeParametersController } from '@/controllers/dynamicNodeParameters.controller'; -import { LdapController } from '@/controllers/ldap.controller'; import { MeController } from '@/controllers/me.controller'; import { MFAController } from '@/controllers/mfa.controller'; import { NodeTypesController } from '@/controllers/nodeTypes.controller'; @@ -70,9 +61,7 @@ import { isApiEnabled, loadPublicApiVersions } from '@/PublicApi'; import type { ICredentialsOverwrite, IDiagnosticInfo, IExecutionsStopData } from '@/Interfaces'; import { ActiveExecutions } from '@/ActiveExecutions'; import { CredentialsOverwrites } from '@/CredentialsOverwrites'; -import { CredentialTypes } from '@/CredentialTypes'; import { LoadNodesAndCredentials } from '@/LoadNodesAndCredentials'; -import { NodeTypes } from '@/NodeTypes'; import * as ResponseHelper from '@/ResponseHelper'; import { WaitTracker } from '@/WaitTracker'; import { toHttpNodeParameters } from '@/CurlConverterHelper'; @@ -91,7 +80,6 @@ import { getStatusUsingPreviousExecutionStatusMethod } from './executions/execut import { SamlController } from './sso/saml/routes/saml.controller.ee'; import { SamlService } from './sso/saml/saml.service.ee'; import { VariablesController } from './environments/variables/variables.controller.ee'; -import { LdapManager } from './Ldap/LdapManager.ee'; import { isLdapCurrentAuthenticationMethod, isSamlCurrentAuthenticationMethod, @@ -101,16 +89,10 @@ import { SourceControlController } from '@/environments/sourceControl/sourceCont import type { ExecutionEntity } from '@db/entities/ExecutionEntity'; import { ExecutionRepository } from '@db/repositories/execution.repository'; -import { SettingsRepository } from '@db/repositories/settings.repository'; -import { SharedCredentialsRepository } from '@db/repositories/sharedCredentials.repository'; -import { SharedWorkflowRepository } from '@db/repositories/sharedWorkflow.repository'; import { WorkflowRepository } from '@db/repositories/workflow.repository'; -import { MfaService } from './Mfa/mfa.service'; import { handleMfaDisable, isMfaFeatureEnabled } from './Mfa/helpers'; import type { FrontendService } from './services/frontend.service'; -import { RoleService } from './services/role.service'; -import { UserService } from './services/user.service'; import { ActiveWorkflowsController } from './controllers/activeWorkflows.controller'; import { OrchestrationController } from './controllers/orchestration.controller'; import { WorkflowHistoryController } from './workflows/workflowHistory/workflowHistory.controller.ee'; @@ -120,7 +102,6 @@ import { RoleController } from './controllers/role.controller'; import { BadRequestError } from './errors/response-errors/bad-request.error'; import { NotFoundError } from './errors/response-errors/not-found.error'; import { MultiMainSetup } from './services/orchestration/main/MultiMainSetup.ee'; -import { PasswordUtility } from './services/password.utility'; const exec = promisify(callbackExec); @@ -136,16 +117,8 @@ export class Server extends AbstractServer { private loadNodesAndCredentials: LoadNodesAndCredentials; - private nodeTypes: NodeTypes; - - private credentialTypes: ICredentialTypes; - private frontendService?: FrontendService; - private postHog: PostHogClient; - - private collaborationService: CollaborationService; - constructor() { super('main'); @@ -159,8 +132,6 @@ export class Server extends AbstractServer { async start() { this.loadNodesAndCredentials = Container.get(LoadNodesAndCredentials); - this.credentialTypes = Container.get(CredentialTypes); - this.nodeTypes = Container.get(NodeTypes); if (!config.getEnv('endpoints.disableUi')) { // eslint-disable-next-line @typescript-eslint/no-var-requires @@ -169,7 +140,6 @@ export class Server extends AbstractServer { this.activeExecutionsInstance = Container.get(ActiveExecutions); this.waitTracker = Container.get(WaitTracker); - this.postHog = Container.get(PostHogClient); this.presetCredentialsLoaded = false; this.endpointPresetCredentials = config.getEnv('credentials.overwrite.endpoint'); @@ -241,101 +211,70 @@ export class Server extends AbstractServer { .then(async (workflow) => Container.get(InternalHooks).onServerStarted(diagnosticInfo, workflow?.createdAt), ); - this.collaborationService = Container.get(CollaborationService); + + Container.get(CollaborationService); } private async registerControllers(ignoredEndpoints: Readonly) { - const { app, externalHooks, activeWorkflowRunner, nodeTypes, logger } = this; + const { app } = this; setupAuthMiddlewares(app, ignoredEndpoints, this.restEndpoint); - const internalHooks = Container.get(InternalHooks); - const userService = Container.get(UserService); - const postHog = this.postHog; - const mfaService = Container.get(MfaService); - - const controllers: object[] = [ - new EventBusController(), - new EventBusControllerEE(), - Container.get(AuthController), - Container.get(LicenseController), - Container.get(OAuth1CredentialController), - Container.get(OAuth2CredentialController), - new OwnerController( - config, - logger, - internalHooks, - Container.get(SettingsRepository), - userService, - Container.get(PasswordUtility), - postHog, - ), - Container.get(MeController), - Container.get(DynamicNodeParametersController), - new NodeTypesController(config, nodeTypes), - Container.get(PasswordResetController), - Container.get(TagsController), - new TranslationController(config, this.credentialTypes), - new UsersController( - logger, - externalHooks, - internalHooks, - Container.get(SharedCredentialsRepository), - Container.get(SharedWorkflowRepository), - activeWorkflowRunner, - Container.get(RoleService), - userService, - Container.get(License), - ), - Container.get(SamlController), - Container.get(SourceControlController), - Container.get(WorkflowStatisticsController), - Container.get(ExternalSecretsController), - Container.get(OrchestrationController), - Container.get(WorkflowHistoryController), - Container.get(BinaryDataController), - Container.get(VariablesController), - new InvitationController( - config, - logger, - internalHooks, - externalHooks, - Container.get(UserService), - Container.get(License), - Container.get(PasswordUtility), - postHog, - ), - Container.get(VariablesController), - Container.get(RoleController), - Container.get(ActiveWorkflowsController), + const controllers: Array> = [ + EventBusController, + EventBusControllerEE, + AuthController, + LicenseController, + OAuth1CredentialController, + OAuth2CredentialController, + OwnerController, + MeController, + DynamicNodeParametersController, + NodeTypesController, + PasswordResetController, + TagsController, + TranslationController, + UsersController, + SamlController, + SourceControlController, + WorkflowStatisticsController, + ExternalSecretsController, + OrchestrationController, + WorkflowHistoryController, + BinaryDataController, + VariablesController, + InvitationController, + VariablesController, + RoleController, + ActiveWorkflowsController, ]; if (Container.get(MultiMainSetup).isEnabled) { const { DebugController } = await import('./controllers/debug.controller'); - controllers.push(Container.get(DebugController)); + controllers.push(DebugController); } if (isLdapEnabled()) { - const { service, sync } = LdapManager.getInstance(); - controllers.push(new LdapController(service, sync, internalHooks)); + const { LdapController } = await require('@/controllers/ldap.controller'); + controllers.push(LdapController); } if (config.getEnv('nodes.communityPackages.enabled')) { const { CommunityPackagesController } = await import( '@/controllers/communityPackages.controller' ); - controllers.push(Container.get(CommunityPackagesController)); + controllers.push(CommunityPackagesController); } if (inE2ETests) { const { E2EController } = await import('./controllers/e2e.controller'); - controllers.push(Container.get(E2EController)); + controllers.push(E2EController); } if (isMfaFeatureEnabled()) { - controllers.push(new MFAController(mfaService)); + controllers.push(MFAController); } - controllers.forEach((controller) => registerController(app, config, controller)); + controllers.forEach((controller) => registerController(app, controller)); } async configure(): Promise { @@ -356,7 +295,7 @@ export class Server extends AbstractServer { await this.externalHooks.run('frontend.settings', [frontendService.getSettings()]); } - await this.postHog.init(); + await Container.get(PostHogClient).init(); const publicApiEndpoint = config.getEnv('publicApi.path'); const excludeEndpoints = config.getEnv('security.excludeEndpoints'); @@ -450,21 +389,16 @@ export class Server extends AbstractServer { // ---------------------------------------- this.app.post( `/${this.restEndpoint}/curl-to-json`, - ResponseHelper.send( - async ( - req: CurlHelper.ToJson, - res: express.Response, - ): Promise<{ [key: string]: string }> => { - const curlCommand = req.body.curlCommand ?? ''; + ResponseHelper.send(async (req: CurlHelper.ToJson) => { + const curlCommand = req.body.curlCommand ?? ''; - try { - const parameters = toHttpNodeParameters(curlCommand); - return ResponseHelper.flattenObject(parameters, 'parameters'); - } catch (e) { - throw new BadRequestError('Invalid cURL command'); - } - }, - ), + try { + const parameters = toHttpNodeParameters(curlCommand); + return ResponseHelper.flattenObject(parameters, 'parameters'); + } catch (e) { + throw new BadRequestError('Invalid cURL command'); + } + }), ); // ---------------------------------------- @@ -687,9 +621,7 @@ export class Server extends AbstractServer { // Returns all the available timezones this.app.get( `/${this.restEndpoint}/options/timezones`, - ResponseHelper.send(async (req: express.Request, res: express.Response): Promise => { - return timezones; - }), + ResponseHelper.send(async () => timezones), ); // ---------------------------------------- @@ -701,13 +633,8 @@ export class Server extends AbstractServer { this.app.get( `/${this.restEndpoint}/settings`, ResponseHelper.send( - async (req: express.Request, res: express.Response): Promise => { - void Container.get(InternalHooks).onFrontendSettingsAPI( - req.headers.sessionid as string, - ); - - return frontendService.getSettings(); - }, + async (req: express.Request): Promise => + frontendService.getSettings(req.headers.sessionid as string), ), ); } @@ -766,6 +693,7 @@ export class Server extends AbstractServer { }; const serveIcons: express.RequestHandler = async (req, res) => { + // eslint-disable-next-line prefer-const let { scope, packageName } = req.params; if (scope) packageName = `@${scope}/${packageName}`; const filePath = this.loadNodesAndCredentials.resolveIcon(packageName, req.originalUrl); diff --git a/packages/cli/src/commands/BaseCommand.ts b/packages/cli/src/commands/BaseCommand.ts index 91a356b4e1..f6091eb011 100644 --- a/packages/cli/src/commands/BaseCommand.ts +++ b/packages/cli/src/commands/BaseCommand.ts @@ -14,7 +14,7 @@ import { initErrorHandling } from '@/ErrorReporting'; import { ExternalHooks } from '@/ExternalHooks'; import { NodeTypes } from '@/NodeTypes'; import { LoadNodesAndCredentials } from '@/LoadNodesAndCredentials'; -import type { IExternalHooksClass, N8nInstanceType } from '@/Interfaces'; +import type { N8nInstanceType } from '@/Interfaces'; import { InternalHooks } from '@/InternalHooks'; import { PostHogClient } from '@/posthog'; import { License } from '@/License'; @@ -27,7 +27,7 @@ import { ShutdownService } from '@/shutdown/Shutdown.service'; export abstract class BaseCommand extends Command { protected logger = Container.get(Logger); - protected externalHooks?: IExternalHooksClass; + protected externalHooks?: ExternalHooks; protected nodeTypes: NodeTypes; diff --git a/packages/cli/src/config/index.ts b/packages/cli/src/config/index.ts index 8e3fe112f6..44d341afcc 100644 --- a/packages/cli/src/config/index.ts +++ b/packages/cli/src/config/index.ts @@ -73,4 +73,3 @@ setGlobalState({ // eslint-disable-next-line import/no-default-export export default config; -export type Config = typeof config; diff --git a/packages/cli/src/controllers/activeWorkflows.controller.ts b/packages/cli/src/controllers/activeWorkflows.controller.ts index e1a427b3ec..323ec018a4 100644 --- a/packages/cli/src/controllers/activeWorkflows.controller.ts +++ b/packages/cli/src/controllers/activeWorkflows.controller.ts @@ -1,9 +1,7 @@ -import { Service } from 'typedi'; import { Authorized, Get, RestController } from '@/decorators'; import { WorkflowRequest } from '@/requests'; import { ActiveWorkflowsService } from '@/services/activeWorkflows.service'; -@Service() @Authorized() @RestController('/active-workflows') export class ActiveWorkflowsController { diff --git a/packages/cli/src/controllers/auth.controller.ts b/packages/cli/src/controllers/auth.controller.ts index 7aaa3718ea..b139013d38 100644 --- a/packages/cli/src/controllers/auth.controller.ts +++ b/packages/cli/src/controllers/auth.controller.ts @@ -1,6 +1,5 @@ import validator from 'validator'; import { In } from 'typeorm'; -import { Service } from 'typedi'; import { Authorized, Get, Post, RestController } from '@/decorators'; import { issueCookie, resolveJwt } from '@/auth/jwt'; import { AUTH_COOKIE_NAME, RESPONSE_ERROR_MESSAGES } from '@/constants'; @@ -27,7 +26,6 @@ import { BadRequestError } from '@/errors/response-errors/bad-request.error'; import { UnauthorizedError } from '@/errors/response-errors/unauthorized.error'; import { ApplicationError } from 'n8n-workflow'; -@Service() @RestController() export class AuthController { constructor( diff --git a/packages/cli/src/controllers/binaryData.controller.ts b/packages/cli/src/controllers/binaryData.controller.ts index 505815bc14..63390f46ab 100644 --- a/packages/cli/src/controllers/binaryData.controller.ts +++ b/packages/cli/src/controllers/binaryData.controller.ts @@ -1,11 +1,9 @@ -import { Service } from 'typedi'; import express from 'express'; import { BinaryDataService, FileNotFoundError, isValidNonDefaultMode } from 'n8n-core'; import { Get, RestController } from '@/decorators'; import { BinaryDataRequest } from '@/requests'; @RestController('/binary-data') -@Service() export class BinaryDataController { constructor(private readonly binaryDataService: BinaryDataService) {} diff --git a/packages/cli/src/controllers/communityPackages.controller.ts b/packages/cli/src/controllers/communityPackages.controller.ts index 1e7d8310cb..0e260c863b 100644 --- a/packages/cli/src/controllers/communityPackages.controller.ts +++ b/packages/cli/src/controllers/communityPackages.controller.ts @@ -1,4 +1,3 @@ -import { Service } from 'typedi'; import { Request, Response, NextFunction } from 'express'; import config from '@/config'; import { @@ -42,14 +41,13 @@ export function isNpmError(error: unknown): error is { code: number; stdout: str return typeof error === 'object' && error !== null && 'code' in error && 'stdout' in error; } -@Service() @Authorized() @RestController('/community-packages') export class CommunityPackagesController { constructor( - private push: Push, - private internalHooks: InternalHooks, - private communityPackagesService: CommunityPackagesService, + private readonly push: Push, + private readonly internalHooks: InternalHooks, + private readonly communityPackagesService: CommunityPackagesService, ) {} // TODO: move this into a new decorator `@IfConfig('executions.mode', 'queue')` diff --git a/packages/cli/src/controllers/debug.controller.ts b/packages/cli/src/controllers/debug.controller.ts index 59829ee282..5580eff476 100644 --- a/packages/cli/src/controllers/debug.controller.ts +++ b/packages/cli/src/controllers/debug.controller.ts @@ -1,4 +1,3 @@ -import { Service } from 'typedi'; import { Get, RestController } from '@/decorators'; import { ActiveWorkflowRunner } from '@/ActiveWorkflowRunner'; import { MultiMainSetup } from '@/services/orchestration/main/MultiMainSetup.ee'; @@ -6,7 +5,6 @@ import { WorkflowRepository } from '@/databases/repositories/workflow.repository import { In } from 'typeorm'; @RestController('/debug') -@Service() export class DebugController { constructor( private readonly multiMainSetup: MultiMainSetup, diff --git a/packages/cli/src/controllers/dynamicNodeParameters.controller.ts b/packages/cli/src/controllers/dynamicNodeParameters.controller.ts index 599b71e65c..d18799341b 100644 --- a/packages/cli/src/controllers/dynamicNodeParameters.controller.ts +++ b/packages/cli/src/controllers/dynamicNodeParameters.controller.ts @@ -1,4 +1,3 @@ -import { Service } from 'typedi'; import type { RequestHandler } from 'express'; import { NextFunction, Response } from 'express'; import type { @@ -22,7 +21,6 @@ const assertMethodName: RequestHandler = (req, res, next) => { next(); }; -@Service() @Authorized() @RestController('/dynamic-node-parameters') export class DynamicNodeParametersController { diff --git a/packages/cli/src/controllers/e2e.controller.ts b/packages/cli/src/controllers/e2e.controller.ts index ddc6e7f2e6..bad2ce536a 100644 --- a/packages/cli/src/controllers/e2e.controller.ts +++ b/packages/cli/src/controllers/e2e.controller.ts @@ -1,5 +1,4 @@ import { Request } from 'express'; -import { Container, Service } from 'typedi'; import { v4 as uuid } from 'uuid'; import config from '@/config'; import type { Role } from '@db/entities/Role'; @@ -64,7 +63,6 @@ type PushRequest = Request< } >; -@Service() @NoAuthRequired() @RestController('/e2e') export class E2EController { @@ -89,12 +87,13 @@ export class E2EController { constructor( license: License, - private roleRepo: RoleRepository, - private settingsRepo: SettingsRepository, - private userRepo: UserRepository, - private workflowRunner: ActiveWorkflowRunner, - private mfaService: MfaService, - private cacheService: CacheService, + private readonly roleRepo: RoleRepository, + private readonly settingsRepo: SettingsRepository, + private readonly userRepo: UserRepository, + private readonly workflowRunner: ActiveWorkflowRunner, + private readonly mfaService: MfaService, + private readonly cacheService: CacheService, + private readonly push: Push, private readonly passwordUtility: PasswordUtility, ) { license.isFeatureEnabled = (feature: BooleanLicenseFeature) => @@ -112,14 +111,8 @@ export class E2EController { } @Post('/push') - async push(req: PushRequest) { - const pushInstance = Container.get(Push); - - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - const sessionId = Object.keys(pushInstance.getBackend().connections as object)[0]; - - pushInstance.send(req.body.type, req.body.data, sessionId); + async pushSend(req: PushRequest) { + this.push.broadcast(req.body.type, req.body.data); } @Patch('/feature') diff --git a/packages/cli/src/controllers/invitation.controller.ts b/packages/cli/src/controllers/invitation.controller.ts index 1bd2fb104a..4760a9f26e 100644 --- a/packages/cli/src/controllers/invitation.controller.ts +++ b/packages/cli/src/controllers/invitation.controller.ts @@ -1,12 +1,11 @@ import { In } from 'typeorm'; -import Container, { Service } from 'typedi'; +import { Response } from 'express'; + +import config from '@/config'; import { Authorized, NoAuthRequired, Post, RequireGlobalScope, RestController } from '@/decorators'; import { issueCookie } from '@/auth/jwt'; import { RESPONSE_ERROR_MESSAGES } from '@/constants'; -import { Response } from 'express'; import { UserRequest } from '@/requests'; -import { Config } from '@/config'; -import { IExternalHooksClass, IInternalHooksClass } from '@/Interfaces'; import { License } from '@/License'; import { UserService } from '@/services/user.service'; import { Logger } from '@/Logger'; @@ -17,20 +16,20 @@ import type { User } from '@/databases/entities/User'; import validator from 'validator'; import { BadRequestError } from '@/errors/response-errors/bad-request.error'; import { UnauthorizedError } from '@/errors/response-errors/unauthorized.error'; +import { InternalHooks } from '@/InternalHooks'; +import { ExternalHooks } from '@/ExternalHooks'; -@Service() @Authorized() @RestController('/invitations') export class InvitationController { constructor( - private readonly config: Config, private readonly logger: Logger, - private readonly internalHooks: IInternalHooksClass, - private readonly externalHooks: IExternalHooksClass, + private readonly internalHooks: InternalHooks, + private readonly externalHooks: ExternalHooks, private readonly userService: UserService, private readonly license: License, private readonly passwordUtility: PasswordUtility, - private readonly postHog?: PostHogClient, + private readonly postHog: PostHogClient, ) {} /** @@ -40,7 +39,7 @@ export class InvitationController { @Post('/') @RequireGlobalScope('user:create') async inviteUser(req: UserRequest.Invite) { - const isWithinUsersLimit = Container.get(License).isWithinUsersLimit(); + const isWithinUsersLimit = this.license.isWithinUsersLimit(); if (isSamlLicensedAndEnabled()) { this.logger.debug( @@ -58,7 +57,7 @@ export class InvitationController { throw new UnauthorizedError(RESPONSE_ERROR_MESSAGES.USERS_QUOTA_REACHED); } - if (!this.config.getEnv('userManagement.isInstanceOwnerSetUp')) { + if (!config.getEnv('userManagement.isInstanceOwnerSetUp')) { this.logger.debug( 'Request to send email invite(s) to user(s) failed because the owner account is not set up', ); diff --git a/packages/cli/src/controllers/ldap.controller.ts b/packages/cli/src/controllers/ldap.controller.ts index 6de886e1e9..c0cb9eae1c 100644 --- a/packages/cli/src/controllers/ldap.controller.ts +++ b/packages/cli/src/controllers/ldap.controller.ts @@ -1,8 +1,9 @@ import pick from 'lodash/pick'; import { Authorized, Get, Post, Put, RestController, RequireGlobalScope } from '@/decorators'; import { getLdapConfig, getLdapSynchronizations, updateLdapConfig } from '@/Ldap/helpers'; -import { LdapService } from '@/Ldap/LdapService.ee'; -import { LdapSync } from '@/Ldap/LdapSync.ee'; +import { LdapManager } from '@/Ldap/LdapManager.ee'; +import type { LdapService } from '@/Ldap/LdapService.ee'; +import type { LdapSync } from '@/Ldap/LdapSync.ee'; import { LdapConfiguration } from '@/Ldap/types'; import { NON_SENSIBLE_LDAP_CONFIG_PROPERTIES } from '@/Ldap/constants'; import { InternalHooks } from '@/InternalHooks'; @@ -11,11 +12,15 @@ import { BadRequestError } from '@/errors/response-errors/bad-request.error'; @Authorized() @RestController('/ldap') export class LdapController { - constructor( - private ldapService: LdapService, - private ldapSync: LdapSync, - private internalHooks: InternalHooks, - ) {} + private ldapService: LdapService; + + private ldapSync: LdapSync; + + constructor(private readonly internalHooks: InternalHooks) { + const { service, sync } = LdapManager.getInstance(); + this.ldapService = service; + this.ldapSync = sync; + } @Get('/config') @RequireGlobalScope('ldap:manage') diff --git a/packages/cli/src/controllers/me.controller.ts b/packages/cli/src/controllers/me.controller.ts index 66a2da0d8a..19060e43fe 100644 --- a/packages/cli/src/controllers/me.controller.ts +++ b/packages/cli/src/controllers/me.controller.ts @@ -1,7 +1,6 @@ import validator from 'validator'; import { plainToInstance } from 'class-transformer'; import { Response } from 'express'; -import { Service } from 'typedi'; import { randomBytes } from 'crypto'; import { Authorized, Delete, Get, Patch, Post, RestController } from '@/decorators'; import { PasswordUtility } from '@/services/password.utility'; @@ -22,7 +21,6 @@ import { ExternalHooks } from '@/ExternalHooks'; import { InternalHooks } from '@/InternalHooks'; import { BadRequestError } from '@/errors/response-errors/bad-request.error'; -@Service() @Authorized() @RestController('/me') export class MeController { diff --git a/packages/cli/src/controllers/mfa.controller.ts b/packages/cli/src/controllers/mfa.controller.ts index 5bdc140728..9d4f370cd0 100644 --- a/packages/cli/src/controllers/mfa.controller.ts +++ b/packages/cli/src/controllers/mfa.controller.ts @@ -1,10 +1,8 @@ -import { Service } from 'typedi'; import { Authorized, Delete, Get, Post, RestController } from '@/decorators'; import { AuthenticatedRequest, MFA } from '@/requests'; import { MfaService } from '@/Mfa/mfa.service'; import { BadRequestError } from '@/errors/response-errors/bad-request.error'; -@Service() @Authorized() @RestController('/mfa') export class MFAController { diff --git a/packages/cli/src/controllers/nodeTypes.controller.ts b/packages/cli/src/controllers/nodeTypes.controller.ts index 029f4d8790..507c7a3c20 100644 --- a/packages/cli/src/controllers/nodeTypes.controller.ts +++ b/packages/cli/src/controllers/nodeTypes.controller.ts @@ -3,22 +3,19 @@ import get from 'lodash/get'; import { Request } from 'express'; import type { INodeTypeDescription, INodeTypeNameVersion } from 'n8n-workflow'; import { Authorized, Post, RestController } from '@/decorators'; -import { Config } from '@/config'; +import config from '@/config'; import { NodeTypes } from '@/NodeTypes'; @Authorized() @RestController('/node-types') export class NodeTypesController { - constructor( - private readonly config: Config, - private readonly nodeTypes: NodeTypes, - ) {} + constructor(private readonly nodeTypes: NodeTypes) {} @Post('/') async getNodeInfo(req: Request) { const nodeInfos = get(req, 'body.nodeInfos', []) as INodeTypeNameVersion[]; - const defaultLocale = this.config.getEnv('defaultLocale'); + const defaultLocale = config.getEnv('defaultLocale'); if (defaultLocale === 'en') { return nodeInfos.reduce((acc, { name, version }) => { diff --git a/packages/cli/src/controllers/oauth/oAuth1Credential.controller.ts b/packages/cli/src/controllers/oauth/oAuth1Credential.controller.ts index e0d72ac05f..761e6b15b3 100644 --- a/packages/cli/src/controllers/oauth/oAuth1Credential.controller.ts +++ b/packages/cli/src/controllers/oauth/oAuth1Credential.controller.ts @@ -1,4 +1,3 @@ -import { Service } from 'typedi'; import { Response } from 'express'; import type { AxiosRequestConfig } from 'axios'; import axios from 'axios'; @@ -30,7 +29,6 @@ const algorithmMap = { /* eslint-enable */ } as const; -@Service() @Authorized() @RestController('/oauth1-credential') export class OAuth1CredentialController extends AbstractOAuthController { diff --git a/packages/cli/src/controllers/oauth/oAuth2Credential.controller.ts b/packages/cli/src/controllers/oauth/oAuth2Credential.controller.ts index 169affb29c..69719fb09b 100644 --- a/packages/cli/src/controllers/oauth/oAuth2Credential.controller.ts +++ b/packages/cli/src/controllers/oauth/oAuth2Credential.controller.ts @@ -1,4 +1,3 @@ -import { Service } from 'typedi'; import type { ClientOAuth2Options } from '@n8n/client-oauth2'; import { ClientOAuth2 } from '@n8n/client-oauth2'; import Csrf from 'csrf'; @@ -31,7 +30,6 @@ interface CsrfStateParam { token: string; } -@Service() @Authorized() @RestController('/oauth2-credential') export class OAuth2CredentialController extends AbstractOAuthController { diff --git a/packages/cli/src/controllers/orchestration.controller.ts b/packages/cli/src/controllers/orchestration.controller.ts index fc73db8ddc..c1c03696fc 100644 --- a/packages/cli/src/controllers/orchestration.controller.ts +++ b/packages/cli/src/controllers/orchestration.controller.ts @@ -1,12 +1,10 @@ import { Authorized, Post, RestController, RequireGlobalScope } from '@/decorators'; import { OrchestrationRequest } from '@/requests'; -import { Service } from 'typedi'; import { SingleMainSetup } from '@/services/orchestration/main/SingleMainSetup'; -import { License } from '../License'; +import { License } from '@/License'; @Authorized() @RestController('/orchestration') -@Service() export class OrchestrationController { constructor( private readonly singleMainSetup: SingleMainSetup, diff --git a/packages/cli/src/controllers/owner.controller.ts b/packages/cli/src/controllers/owner.controller.ts index ef3e9ecbed..2b496c26af 100644 --- a/packages/cli/src/controllers/owner.controller.ts +++ b/packages/cli/src/controllers/owner.controller.ts @@ -1,29 +1,29 @@ import validator from 'validator'; +import { Response } from 'express'; + +import config from '@/config'; import { validateEntity } from '@/GenericHelpers'; import { Authorized, Post, RestController } from '@/decorators'; import { PasswordUtility } from '@/services/password.utility'; import { issueCookie } from '@/auth/jwt'; -import { Response } from 'express'; -import { Config } from '@/config'; import { OwnerRequest } from '@/requests'; -import { IInternalHooksClass } from '@/Interfaces'; import { SettingsRepository } from '@db/repositories/settings.repository'; import { PostHogClient } from '@/posthog'; import { UserService } from '@/services/user.service'; import { Logger } from '@/Logger'; import { BadRequestError } from '@/errors/response-errors/bad-request.error'; +import { InternalHooks } from '@/InternalHooks'; @Authorized(['global', 'owner']) @RestController('/owner') export class OwnerController { constructor( - private readonly config: Config, private readonly logger: Logger, - private readonly internalHooks: IInternalHooksClass, + private readonly internalHooks: InternalHooks, private readonly settingsRepository: SettingsRepository, private readonly userService: UserService, private readonly passwordUtility: PasswordUtility, - private readonly postHog?: PostHogClient, + private readonly postHog: PostHogClient, ) {} /** @@ -35,7 +35,7 @@ export class OwnerController { const { email, firstName, lastName, password } = req.body; const { id: userId, globalRole } = req.user; - if (this.config.getEnv('userManagement.isInstanceOwnerSetUp')) { + if (config.getEnv('userManagement.isInstanceOwnerSetUp')) { this.logger.debug( 'Request to claim instance ownership failed because instance owner already exists', { @@ -94,7 +94,7 @@ export class OwnerController { { value: JSON.stringify(true) }, ); - this.config.set('userManagement.isInstanceOwnerSetUp', true); + config.set('userManagement.isInstanceOwnerSetUp', true); this.logger.debug('Setting isInstanceOwnerSetUp updated successfully', { userId }); diff --git a/packages/cli/src/controllers/passwordReset.controller.ts b/packages/cli/src/controllers/passwordReset.controller.ts index dce0338e2e..b8e26b8c75 100644 --- a/packages/cli/src/controllers/passwordReset.controller.ts +++ b/packages/cli/src/controllers/passwordReset.controller.ts @@ -1,6 +1,5 @@ import { Response } from 'express'; import { rateLimit } from 'express-rate-limit'; -import { Service } from 'typedi'; import { IsNull, Not } from 'typeorm'; import validator from 'validator'; @@ -31,7 +30,6 @@ const throttle = rateLimit({ message: { message: 'Too many requests' }, }); -@Service() @RestController() export class PasswordResetController { constructor( diff --git a/packages/cli/src/controllers/role.controller.ts b/packages/cli/src/controllers/role.controller.ts index f918e17717..4b16ceb771 100644 --- a/packages/cli/src/controllers/role.controller.ts +++ b/packages/cli/src/controllers/role.controller.ts @@ -1,9 +1,7 @@ import { License } from '@/License'; import { Get, RestController } from '@/decorators'; import { RoleService } from '@/services/role.service'; -import { Service } from 'typedi'; -@Service() @RestController('/roles') export class RoleController { constructor( diff --git a/packages/cli/src/controllers/tags.controller.ts b/packages/cli/src/controllers/tags.controller.ts index 2e8abc0dfb..43c06c83e2 100644 --- a/packages/cli/src/controllers/tags.controller.ts +++ b/packages/cli/src/controllers/tags.controller.ts @@ -12,16 +12,14 @@ import { } from '@/decorators'; import { TagService } from '@/services/tag.service'; import { TagsRequest } from '@/requests'; -import { Service } from 'typedi'; import { BadRequestError } from '@/errors/response-errors/bad-request.error'; @Authorized() @RestController('/tags') -@Service() export class TagsController { private config = config; - constructor(private tagService: TagService) {} + constructor(private readonly tagService: TagService) {} // TODO: move this into a new decorator `@IfEnabled('workflowTagsDisabled')` @Middleware() diff --git a/packages/cli/src/controllers/translation.controller.ts b/packages/cli/src/controllers/translation.controller.ts index e7ef6bc3cd..7b5f6bef57 100644 --- a/packages/cli/src/controllers/translation.controller.ts +++ b/packages/cli/src/controllers/translation.controller.ts @@ -1,12 +1,12 @@ import type { Request } from 'express'; -import { ICredentialTypes } from 'n8n-workflow'; import { join } from 'path'; import { access } from 'fs/promises'; import { Authorized, Get, RestController } from '@/decorators'; -import { Config } from '@/config'; +import config from '@/config'; import { NODES_BASE_DIR } from '@/constants'; import { BadRequestError } from '@/errors/response-errors/bad-request.error'; import { InternalServerError } from '@/errors/response-errors/internal-server.error'; +import { CredentialTypes } from '@/CredentialTypes'; export const CREDENTIAL_TRANSLATIONS_DIR = 'n8n-nodes-base/dist/credentials/translations'; export const NODE_HEADERS_PATH = join(NODES_BASE_DIR, 'dist/nodes/headers'); @@ -18,10 +18,7 @@ export declare namespace TranslationRequest { @Authorized() @RestController('/') export class TranslationController { - constructor( - private config: Config, - private credentialTypes: ICredentialTypes, - ) {} + constructor(private readonly credentialTypes: CredentialTypes) {} @Get('/credential-translation') async getCredentialTranslation(req: TranslationRequest.Credential) { @@ -30,7 +27,7 @@ export class TranslationController { if (!this.credentialTypes.recognizes(credentialType)) throw new BadRequestError(`Invalid Credential type: "${credentialType}"`); - const defaultLocale = this.config.getEnv('defaultLocale'); + const defaultLocale = config.getEnv('defaultLocale'); const translationPath = join( CREDENTIAL_TRANSLATIONS_DIR, defaultLocale, diff --git a/packages/cli/src/controllers/users.controller.ts b/packages/cli/src/controllers/users.controller.ts index 6e0ae6438e..376f047d15 100644 --- a/packages/cli/src/controllers/users.controller.ts +++ b/packages/cli/src/controllers/users.controller.ts @@ -6,7 +6,6 @@ import { SharedWorkflow } from '@db/entities/SharedWorkflow'; import { RequireGlobalScope, Authorized, Delete, Get, RestController, Patch } from '@/decorators'; import { ListQuery, UserRequest, UserSettingsUpdatePayload } from '@/requests'; import { ActiveWorkflowRunner } from '@/ActiveWorkflowRunner'; -import { IExternalHooksClass, IInternalHooksClass } from '@/Interfaces'; import type { PublicUser, ITelemetryUserDeletionData } from '@/Interfaces'; import { AuthIdentity } from '@db/entities/AuthIdentity'; import { SharedCredentialsRepository } from '@db/repositories/sharedCredentials.repository'; @@ -20,14 +19,16 @@ import { UnauthorizedError } from '@/errors/response-errors/unauthorized.error'; import { NotFoundError } from '@/errors/response-errors/not-found.error'; import { BadRequestError } from '@/errors/response-errors/bad-request.error'; import { License } from '@/License'; +import { ExternalHooks } from '@/ExternalHooks'; +import { InternalHooks } from '@/InternalHooks'; @Authorized() @RestController('/users') export class UsersController { constructor( private readonly logger: Logger, - private readonly externalHooks: IExternalHooksClass, - private readonly internalHooks: IInternalHooksClass, + private readonly externalHooks: ExternalHooks, + private readonly internalHooks: InternalHooks, private readonly sharedCredentialsRepository: SharedCredentialsRepository, private readonly sharedWorkflowRepository: SharedWorkflowRepository, private readonly activeWorkflowRunner: ActiveWorkflowRunner, diff --git a/packages/cli/src/controllers/workflowStatistics.controller.ts b/packages/cli/src/controllers/workflowStatistics.controller.ts index 2eb9e0dcac..b1f5d0e68e 100644 --- a/packages/cli/src/controllers/workflowStatistics.controller.ts +++ b/packages/cli/src/controllers/workflowStatistics.controller.ts @@ -1,4 +1,3 @@ -import { Service } from 'typedi'; import { Response, NextFunction } from 'express'; import { Get, Middleware, RestController } from '@/decorators'; import type { WorkflowStatistics } from '@db/entities/WorkflowStatistics'; @@ -18,12 +17,11 @@ interface WorkflowStatisticsData { manualError: T; } -@Service() @RestController('/workflow-stats') export class WorkflowStatisticsController { constructor( - private sharedWorkflowRepository: SharedWorkflowRepository, - private workflowStatisticsRepository: WorkflowStatisticsRepository, + private readonly sharedWorkflowRepository: SharedWorkflowRepository, + private readonly workflowStatisticsRepository: WorkflowStatisticsRepository, private readonly logger: Logger, ) {} diff --git a/packages/cli/src/databases/entities/AbstractEntity.ts b/packages/cli/src/databases/entities/AbstractEntity.ts index 8200ae33a2..3c64a43aef 100644 --- a/packages/cli/src/databases/entities/AbstractEntity.ts +++ b/packages/cli/src/databases/entities/AbstractEntity.ts @@ -7,6 +7,7 @@ import { UpdateDateColumn, } from 'typeorm'; import config from '@/config'; +import type { Class } from 'n8n-core'; import { generateNanoId } from '../utils/generators'; const dbType = config.getEnv('database.type'); @@ -27,9 +28,7 @@ const tsColumnOptions: ColumnOptions = { type: datetimeColumnType, }; -type Constructor = new (...args: any[]) => T; - -function mixinStringId>(base: T) { +function mixinStringId>(base: T) { class Derived extends base { @PrimaryColumn('varchar') id: string; @@ -44,7 +43,7 @@ function mixinStringId>(base: T) { return Derived; } -function mixinTimestamps>(base: T) { +function mixinTimestamps>(base: T) { class Derived extends base { @CreateDateColumn(tsColumnOptions) createdAt: Date; diff --git a/packages/cli/src/decorators/OnShutdown.ts b/packages/cli/src/decorators/OnShutdown.ts index 87e8a6a457..88eaf8fb11 100644 --- a/packages/cli/src/decorators/OnShutdown.ts +++ b/packages/cli/src/decorators/OnShutdown.ts @@ -24,6 +24,7 @@ import { type ServiceClass, ShutdownService } from '@/shutdown/Shutdown.service' export const OnShutdown = (priority = 100): MethodDecorator => (prototype, propertyKey, descriptor) => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const serviceClass = prototype.constructor as ServiceClass; const methodName = String(propertyKey); // TODO: assert that serviceClass is decorated with @Service diff --git a/packages/cli/src/decorators/RestController.ts b/packages/cli/src/decorators/RestController.ts index 7113e7774d..ca235cba59 100644 --- a/packages/cli/src/decorators/RestController.ts +++ b/packages/cli/src/decorators/RestController.ts @@ -1,7 +1,10 @@ +import { Service } from 'typedi'; import { CONTROLLER_BASE_PATH } from './constants'; export const RestController = (basePath: `/${string}` = '/'): ClassDecorator => (target: object) => { Reflect.defineMetadata(CONTROLLER_BASE_PATH, basePath, target); + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return Service()(target); }; diff --git a/packages/cli/src/decorators/registerController.ts b/packages/cli/src/decorators/registerController.ts index ccc097dde9..c3b38c8183 100644 --- a/packages/cli/src/decorators/registerController.ts +++ b/packages/cli/src/decorators/registerController.ts @@ -1,6 +1,9 @@ +import { Container } from 'typedi'; import { Router } from 'express'; import type { Application, Request, Response, RequestHandler } from 'express'; -import type { Config } from '@/config'; +import type { Class } from 'n8n-core'; + +import config from '@/config'; import type { AuthenticatedRequest } from '@/requests'; import { send } from '@/ResponseHelper'; // TODO: move `ResponseHelper.send` to this file import { @@ -21,7 +24,7 @@ import type { ScopeMetadata, } from './types'; import type { BooleanLicenseFeature } from '@/Interfaces'; -import Container from 'typedi'; + import { License } from '@/License'; import type { Scope } from '@n8n/permissions'; import { ApplicationError } from 'n8n-workflow'; @@ -81,9 +84,8 @@ const authFreeRoutes: string[] = []; export const canSkipAuth = (method: string, path: string): boolean => authFreeRoutes.includes(`${method.toLowerCase()} ${path}`); -export const registerController = (app: Application, config: Config, cObj: object) => { - const controller = cObj as Controller; - const controllerClass = controller.constructor; +export const registerController = (app: Application, controllerClass: Class) => { + const controller = Container.get(controllerClass as Class); const controllerBasePath = Reflect.getMetadata(CONTROLLER_BASE_PATH, controllerClass) as | string | undefined; diff --git a/packages/cli/src/environments/sourceControl/constants.ts b/packages/cli/src/environments/sourceControl/constants.ts index 5b88d858b7..6580ce9979 100644 --- a/packages/cli/src/environments/sourceControl/constants.ts +++ b/packages/cli/src/environments/sourceControl/constants.ts @@ -10,7 +10,6 @@ export const SOURCE_CONTROL_SSH_FOLDER = 'ssh'; export const SOURCE_CONTROL_SSH_KEY_NAME = 'key'; export const SOURCE_CONTROL_DEFAULT_BRANCH = 'main'; export const SOURCE_CONTROL_ORIGIN = 'origin'; -export const SOURCE_CONTROL_API_ROOT = 'source-control'; export const SOURCE_CONTROL_README = ` # n8n Source Control `; diff --git a/packages/cli/src/environments/sourceControl/sourceControl.controller.ee.ts b/packages/cli/src/environments/sourceControl/sourceControl.controller.ee.ts index e5291c9059..d589af30d8 100644 --- a/packages/cli/src/environments/sourceControl/sourceControl.controller.ee.ts +++ b/packages/cli/src/environments/sourceControl/sourceControl.controller.ee.ts @@ -1,4 +1,3 @@ -import { Container, Service } from 'typedi'; import type { PullResult } from 'simple-git'; import express from 'express'; import { Authorized, Get, Post, Patch, RestController, RequireGlobalScope } from '@/decorators'; @@ -11,20 +10,20 @@ import { SourceControlRequest } from './types/requests'; import { SourceControlPreferencesService } from './sourceControlPreferences.service.ee'; import type { SourceControlPreferences } from './types/sourceControlPreferences'; import type { SourceControlledFile } from './types/sourceControlledFile'; -import { SOURCE_CONTROL_API_ROOT, SOURCE_CONTROL_DEFAULT_BRANCH } from './constants'; +import { SOURCE_CONTROL_DEFAULT_BRANCH } from './constants'; import type { ImportResult } from './types/importResult'; -import { InternalHooks } from '../../InternalHooks'; +import { InternalHooks } from '@/InternalHooks'; import { getRepoType } from './sourceControlHelper.ee'; import { SourceControlGetStatus } from './types/sourceControlGetStatus'; import { BadRequestError } from '@/errors/response-errors/bad-request.error'; -@Service() @Authorized() -@RestController(`/${SOURCE_CONTROL_API_ROOT}`) +@RestController('/source-control') export class SourceControlController { constructor( - private sourceControlService: SourceControlService, - private sourceControlPreferencesService: SourceControlPreferencesService, + private readonly sourceControlService: SourceControlService, + private readonly sourceControlPreferencesService: SourceControlPreferencesService, + private readonly internalHooks: InternalHooks, ) {} @Authorized('none') @@ -85,7 +84,7 @@ export class SourceControlController { const resultingPreferences = this.sourceControlPreferencesService.getPreferences(); // #region Tracking Information // located in controller so as to not call this multiple times when updating preferences - void Container.get(InternalHooks).onSourceControlSettingsUpdated({ + void this.internalHooks.onSourceControlSettingsUpdated({ branch_name: resultingPreferences.branchName, connected: resultingPreferences.connected, read_only_instance: resultingPreferences.branchReadOnly, @@ -130,7 +129,7 @@ export class SourceControlController { } await this.sourceControlService.init(); const resultingPreferences = this.sourceControlPreferencesService.getPreferences(); - void Container.get(InternalHooks).onSourceControlSettingsUpdated({ + void this.internalHooks.onSourceControlSettingsUpdated({ branch_name: resultingPreferences.branchName, connected: resultingPreferences.connected, read_only_instance: resultingPreferences.branchReadOnly, diff --git a/packages/cli/src/environments/variables/variables.controller.ee.ts b/packages/cli/src/environments/variables/variables.controller.ee.ts index 125c56bf89..69a0b03d2d 100644 --- a/packages/cli/src/environments/variables/variables.controller.ee.ts +++ b/packages/cli/src/environments/variables/variables.controller.ee.ts @@ -1,5 +1,3 @@ -import { Service } from 'typedi'; - import { VariablesRequest } from '@/requests'; import { Authorized, @@ -17,11 +15,10 @@ import { NotFoundError } from '@/errors/response-errors/not-found.error'; import { VariableValidationError } from '@/errors/variable-validation.error'; import { VariableCountLimitReachedError } from '@/errors/variable-count-limit-reached.error'; -@Service() @Authorized() @RestController('/variables') export class VariablesController { - constructor(private variablesService: VariablesService) {} + constructor(private readonly variablesService: VariablesService) {} @Get('/') @RequireGlobalScope('variable:list') diff --git a/packages/cli/src/license/license.controller.ts b/packages/cli/src/license/license.controller.ts index 0351361648..ea0df696c9 100644 --- a/packages/cli/src/license/license.controller.ts +++ b/packages/cli/src/license/license.controller.ts @@ -1,9 +1,7 @@ -import { Service } from 'typedi'; import { Authorized, Get, Post, RequireGlobalScope, RestController } from '@/decorators'; import { LicenseRequest } from '@/requests'; import { LicenseService } from './license.service'; -@Service() @Authorized() @RestController('/license') export class LicenseController { diff --git a/packages/cli/src/services/frontend.service.ts b/packages/cli/src/services/frontend.service.ts index cac41c7fd7..9c0828c912 100644 --- a/packages/cli/src/services/frontend.service.ts +++ b/packages/cli/src/services/frontend.service.ts @@ -30,6 +30,7 @@ import { UserManagementMailer } from '@/UserManagement/email'; import type { CommunityPackagesService } from '@/services/communityPackages.service'; import { Logger } from '@/Logger'; import { UrlService } from './url.service'; +import { InternalHooks } from '@/InternalHooks'; @Service() export class FrontendService { @@ -46,6 +47,7 @@ export class FrontendService { private readonly mailer: UserManagementMailer, private readonly instanceSettings: InstanceSettings, private readonly urlService: UrlService, + private readonly internalHooks: InternalHooks, ) { loadNodesAndCredentials.addPostProcessor(async () => this.generateTypes()); void this.generateTypes(); @@ -218,7 +220,9 @@ export class FrontendService { this.writeStaticJSON('credentials', credentials); } - getSettings(): IN8nUISettings { + getSettings(sessionId?: string): IN8nUISettings { + void this.internalHooks.onFrontendSettingsAPI(sessionId); + const restEndpoint = config.getEnv('endpoints.rest'); // Update all urls, in case `WEBHOOK_URL` was updated by `--tunnel` diff --git a/packages/cli/src/shutdown/Shutdown.service.ts b/packages/cli/src/shutdown/Shutdown.service.ts index b52d8ab11a..3c6608ecc7 100644 --- a/packages/cli/src/shutdown/Shutdown.service.ts +++ b/packages/cli/src/shutdown/Shutdown.service.ts @@ -1,10 +1,10 @@ import { Container, Service } from 'typedi'; import { ApplicationError, ErrorReporterProxy, assert } from 'n8n-workflow'; +import type { Class } from 'n8n-core'; import { Logger } from '@/Logger'; -export interface ServiceClass { - new (): Record Promise | void>; -} +type HandlerFn = () => Promise | void; +export type ServiceClass = Class>; export interface ShutdownHandler { serviceClass: ServiceClass; diff --git a/packages/cli/src/sso/saml/routes/saml.controller.ee.ts b/packages/cli/src/sso/saml/routes/saml.controller.ee.ts index eeaf74bf14..0dd3bb8ba2 100644 --- a/packages/cli/src/sso/saml/routes/saml.controller.ee.ts +++ b/packages/cli/src/sso/saml/routes/saml.controller.ee.ts @@ -1,5 +1,4 @@ import express from 'express'; -import { Container, Service } from 'typedi'; import { Authorized, Get, @@ -36,13 +35,13 @@ import { BadRequestError } from '@/errors/response-errors/bad-request.error'; import { AuthError } from '@/errors/response-errors/auth.error'; import { UrlService } from '@/services/url.service'; -@Service() @Authorized() @RestController('/sso/saml') export class SamlController { constructor( private readonly samlService: SamlService, private readonly urlService: UrlService, + private readonly internalHooks: InternalHooks, ) {} @NoAuthRequired() @@ -142,7 +141,7 @@ export class SamlController { } } if (loginResult.authenticatedUser) { - void Container.get(InternalHooks).onUserLoginSuccess({ + void this.internalHooks.onUserLoginSuccess({ user: loginResult.authenticatedUser, authenticationMethod: 'saml', }); @@ -159,7 +158,7 @@ export class SamlController { return res.status(202).send(loginResult.attributes); } } - void Container.get(InternalHooks).onUserLoginFailed({ + void this.internalHooks.onUserLoginFailed({ user: loginResult.attributes.email ?? 'unknown', authenticationMethod: 'saml', }); @@ -168,7 +167,7 @@ export class SamlController { if (isConnectionTestRequest(req)) { return res.send(getSamlConnectionTestFailedView((error as Error).message)); } - void Container.get(InternalHooks).onUserLoginFailed({ + void this.internalHooks.onUserLoginFailed({ user: 'unknown', authenticationMethod: 'saml', }); diff --git a/packages/cli/src/workflows/workflowHistory/workflowHistory.controller.ee.ts b/packages/cli/src/workflows/workflowHistory/workflowHistory.controller.ee.ts index c5f7159a14..2ac2cf4233 100644 --- a/packages/cli/src/workflows/workflowHistory/workflowHistory.controller.ee.ts +++ b/packages/cli/src/workflows/workflowHistory/workflowHistory.controller.ee.ts @@ -1,6 +1,5 @@ import { Authorized, RestController, Get, Middleware } from '@/decorators'; import { WorkflowHistoryRequest } from '@/requests'; -import { Service } from 'typedi'; import { WorkflowHistoryService } from './workflowHistory.service.ee'; import { Request, Response, NextFunction } from 'express'; import { isWorkflowHistoryEnabled, isWorkflowHistoryLicensed } from './workflowHistoryHelper.ee'; @@ -12,7 +11,6 @@ import { WorkflowHistoryVersionNotFoundError } from '@/errors/workflow-history-v const DEFAULT_TAKE = 20; -@Service() @Authorized() @RestController('/workflow-history') export class WorkflowHistoryController { diff --git a/packages/cli/test/integration/environments/SourceControl.test.ts b/packages/cli/test/integration/environments/SourceControl.test.ts index d0a69c1d13..384c02d446 100644 --- a/packages/cli/test/integration/environments/SourceControl.test.ts +++ b/packages/cli/test/integration/environments/SourceControl.test.ts @@ -1,22 +1,18 @@ +import { Container } from 'typedi'; import type { SuperAgentTest } from 'supertest'; -import { SOURCE_CONTROL_API_ROOT } from '@/environments/sourceControl/constants'; -import * as utils from '../shared/utils/'; + import type { User } from '@db/entities/User'; -import * as UserManagementHelpers from '@/UserManagement/UserManagementHelper'; -import Container from 'typedi'; import config from '@/config'; import { SourceControlPreferencesService } from '@/environments/sourceControl/sourceControlPreferences.service.ee'; import { SourceControlService } from '@/environments/sourceControl/sourceControl.service.ee'; import type { SourceControlledFile } from '@/environments/sourceControl/types/sourceControlledFile'; -import { getGlobalMemberRole, getGlobalOwnerRole } from '../shared/db/roles'; + +import * as utils from '../shared/utils/'; +import { getGlobalOwnerRole } from '../shared/db/roles'; import { createUser } from '../shared/db/users'; let authOwnerAgent: SuperAgentTest; -let authMemberAgent: SuperAgentTest; let owner: User; -let member: User; - -const sharingSpy = jest.spyOn(UserManagementHelpers, 'isSharingEnabled').mockReturnValue(true); const testServer = utils.setupTestServer({ endpointGroups: ['sourceControl', 'license', 'auth'], @@ -25,11 +21,8 @@ const testServer = utils.setupTestServer({ beforeAll(async () => { const globalOwnerRole = await getGlobalOwnerRole(); - const globalMemberRole = await getGlobalMemberRole(); owner = await createUser({ globalRole: globalOwnerRole }); - member = await createUser({ globalRole: globalMemberRole }); authOwnerAgent = testServer.authAgentFor(owner); - authMemberAgent = testServer.authAgentFor(member); Container.get(SourceControlPreferencesService).isSourceControlConnected = () => true; }); @@ -37,7 +30,7 @@ beforeAll(async () => { describe('GET /sourceControl/preferences', () => { test('should return Source Control preferences', async () => { await authOwnerAgent - .get(`/${SOURCE_CONTROL_API_ROOT}/preferences`) + .get('/source-control/preferences') .expect(200) .expect((res) => { return 'repositoryUrl' in res.body && 'branchName' in res.body; @@ -60,7 +53,7 @@ describe('GET /sourceControl/preferences', () => { ] as SourceControlledFile[]; }; await authOwnerAgent - .get(`/${SOURCE_CONTROL_API_ROOT}/get-status`) + .get('/source-control/get-status') .query({ direction: 'push', preferLocalVersion: 'true', verbose: 'false' }) .expect(200) .expect((res) => { @@ -73,7 +66,7 @@ describe('GET /sourceControl/preferences', () => { test('refreshing key pairsshould return new rsa key', async () => { config.set('sourceControl.defaultKeyPairType', 'rsa'); await authOwnerAgent - .post(`/${SOURCE_CONTROL_API_ROOT}/generate-key-pair`) + .post('/source-control/generate-key-pair') .send() .expect(200) .expect((res) => { diff --git a/packages/cli/test/integration/shared/testDb.ts b/packages/cli/test/integration/shared/testDb.ts index 065652cc4d..1ed5938895 100644 --- a/packages/cli/test/integration/shared/testDb.ts +++ b/packages/cli/test/integration/shared/testDb.ts @@ -1,6 +1,7 @@ import type { DataSourceOptions as ConnectionOptions, Repository } from 'typeorm'; import { DataSource as Connection } from 'typeorm'; import { Container } from 'typedi'; +import type { Class } from 'n8n-core'; import config from '@/config'; import * as Db from '@/Db'; @@ -116,7 +117,7 @@ const repositories = [ */ export async function truncate(names: Array<(typeof repositories)[number]>) { for (const name of names) { - const RepositoryClass: { new (): Repository } = ( + const RepositoryClass: Class> = ( await import(`@db/repositories/${name.charAt(0).toLowerCase() + name.slice(1)}.repository`) )[`${name}Repository`]; await Container.get(RepositoryClass).delete({}); diff --git a/packages/cli/test/integration/shared/utils/testServer.ts b/packages/cli/test/integration/shared/utils/testServer.ts index f765f83dac..9f85e4c487 100644 --- a/packages/cli/test/integration/shared/utils/testServer.ts +++ b/packages/cli/test/integration/shared/utils/testServer.ts @@ -14,14 +14,13 @@ import { rawBodyReader, bodyParser, setupAuthMiddlewares } from '@/middlewares'; import { PostHogClient } from '@/posthog'; import { License } from '@/License'; import { Logger } from '@/Logger'; +import { InternalHooks } from '@/InternalHooks'; import { mockInstance } from '../../../shared/mocking'; import * as testDb from '../../shared/testDb'; import { AUTHLESS_ENDPOINTS, PUBLIC_API_REST_PATH_SEGMENT, REST_PATH_SEGMENT } from '../constants'; import type { SetupProps, TestServer } from '../types'; -import { InternalHooks } from '@/InternalHooks'; import { LicenseMocker } from '../license'; -import { PasswordUtility } from '@/services/password.utility'; /** * Plugin to prefix a path segment into a request URL pathname. @@ -76,7 +75,7 @@ export const setupTestServer = ({ app.use(cookieParser()); // Mock all telemetry and logging - const logger = mockInstance(Logger); + mockInstance(Logger); mockInstance(InternalHooks); mockInstance(PostHogClient); @@ -140,12 +139,12 @@ export const setupTestServer = ({ const { VariablesController } = await import( '@/environments/variables/variables.controller.ee' ); - registerController(app, config, Container.get(VariablesController)); + registerController(app, VariablesController); break; case 'license': const { LicenseController } = await import('@/license/license.controller'); - registerController(app, config, Container.get(LicenseController)); + registerController(app, LicenseController); break; case 'metrics': @@ -156,166 +155,108 @@ export const setupTestServer = ({ case 'eventBus': const { EventBusController } = await import('@/eventbus/eventBus.controller'); const { EventBusControllerEE } = await import('@/eventbus/eventBus.controller.ee'); - registerController(app, config, new EventBusController()); - registerController(app, config, new EventBusControllerEE()); + registerController(app, EventBusController); + registerController(app, EventBusControllerEE); break; case 'auth': const { AuthController } = await import('@/controllers/auth.controller'); - registerController(app, config, Container.get(AuthController)); + registerController(app, AuthController); break; case 'mfa': const { MFAController } = await import('@/controllers/mfa.controller'); - registerController(app, config, Container.get(MFAController)); + registerController(app, MFAController); break; case 'ldap': - const { LdapManager } = await import('@/Ldap/LdapManager.ee'); const { handleLdapInit } = await import('@/Ldap/helpers'); const { LdapController } = await import('@/controllers/ldap.controller'); testServer.license.enable('feat:ldap'); await handleLdapInit(); - const { service, sync } = LdapManager.getInstance(); - registerController( - app, - config, - new LdapController(service, sync, Container.get(InternalHooks)), - ); + registerController(app, LdapController); break; case 'saml': const { setSamlLoginEnabled } = await import('@/sso/saml/samlHelpers'); const { SamlController } = await import('@/sso/saml/routes/saml.controller.ee'); await setSamlLoginEnabled(true); - registerController(app, config, Container.get(SamlController)); + registerController(app, SamlController); break; case 'sourceControl': const { SourceControlController } = await import( '@/environments/sourceControl/sourceControl.controller.ee' ); - registerController(app, config, Container.get(SourceControlController)); + registerController(app, SourceControlController); break; case 'community-packages': const { CommunityPackagesController } = await import( '@/controllers/communityPackages.controller' ); - registerController(app, config, Container.get(CommunityPackagesController)); + registerController(app, CommunityPackagesController); break; case 'me': const { MeController } = await import('@/controllers/me.controller'); - registerController(app, config, Container.get(MeController)); + registerController(app, MeController); break; case 'passwordReset': const { PasswordResetController } = await import( '@/controllers/passwordReset.controller' ); - registerController(app, config, Container.get(PasswordResetController)); + registerController(app, PasswordResetController); break; case 'owner': - const { UserService } = await import('@/services/user.service'); - const { SettingsRepository } = await import('@db/repositories/settings.repository'); const { OwnerController } = await import('@/controllers/owner.controller'); - registerController( - app, - config, - new OwnerController( - config, - logger, - Container.get(InternalHooks), - Container.get(SettingsRepository), - Container.get(UserService), - Container.get(PasswordUtility), - ), - ); + registerController(app, OwnerController); break; case 'users': - const { SharedCredentialsRepository } = await import( - '@db/repositories/sharedCredentials.repository' - ); - const { SharedWorkflowRepository } = await import( - '@db/repositories/sharedWorkflow.repository' - ); - const { ActiveWorkflowRunner } = await import('@/ActiveWorkflowRunner'); - const { UserService: US } = await import('@/services/user.service'); - const { ExternalHooks: EH } = await import('@/ExternalHooks'); - const { RoleService: RS } = await import('@/services/role.service'); const { UsersController } = await import('@/controllers/users.controller'); - registerController( - app, - config, - new UsersController( - logger, - Container.get(EH), - Container.get(InternalHooks), - Container.get(SharedCredentialsRepository), - Container.get(SharedWorkflowRepository), - Container.get(ActiveWorkflowRunner), - Container.get(RS), - Container.get(US), - Container.get(License), - ), - ); + registerController(app, UsersController); break; case 'invitations': const { InvitationController } = await import('@/controllers/invitation.controller'); - const { ExternalHooks: EHS } = await import('@/ExternalHooks'); - const { UserService: USE } = await import('@/services/user.service'); - - registerController( - app, - config, - new InvitationController( - config, - logger, - Container.get(InternalHooks), - Container.get(EHS), - Container.get(USE), - Container.get(License), - Container.get(PasswordUtility), - ), - ); + registerController(app, InvitationController); break; case 'tags': const { TagsController } = await import('@/controllers/tags.controller'); - registerController(app, config, Container.get(TagsController)); + registerController(app, TagsController); break; case 'externalSecrets': const { ExternalSecretsController } = await import( '@/ExternalSecrets/ExternalSecrets.controller.ee' ); - registerController(app, config, Container.get(ExternalSecretsController)); + registerController(app, ExternalSecretsController); break; case 'workflowHistory': const { WorkflowHistoryController } = await import( '@/workflows/workflowHistory/workflowHistory.controller.ee' ); - registerController(app, config, Container.get(WorkflowHistoryController)); + registerController(app, WorkflowHistoryController); break; case 'binaryData': const { BinaryDataController } = await import('@/controllers/binaryData.controller'); - registerController(app, config, Container.get(BinaryDataController)); + registerController(app, BinaryDataController); break; case 'role': const { RoleController } = await import('@/controllers/role.controller'); - registerController(app, config, Container.get(RoleController)); + registerController(app, RoleController); break; case 'debug': const { DebugController } = await import('@/controllers/debug.controller'); - registerController(app, config, Container.get(DebugController)); + registerController(app, DebugController); break; } } diff --git a/packages/cli/test/shared/mocking.ts b/packages/cli/test/shared/mocking.ts index a7fc5d183e..7f941378b1 100644 --- a/packages/cli/test/shared/mocking.ts +++ b/packages/cli/test/shared/mocking.ts @@ -1,12 +1,13 @@ import { Container } from 'typedi'; import { mock } from 'jest-mock-extended'; import type { DeepPartial } from 'ts-essentials'; +import type { Class } from 'n8n-core'; export const mockInstance = ( - ctor: new (...args: unknown[]) => T, + serviceClass: Class, data: DeepPartial | undefined = undefined, ) => { const instance = mock(data); - Container.set(ctor, instance); + Container.set(serviceClass, instance); return instance; }; diff --git a/packages/cli/test/unit/controllers/owner.controller.test.ts b/packages/cli/test/unit/controllers/owner.controller.test.ts index 6f81f53a5d..3ba61147dc 100644 --- a/packages/cli/test/unit/controllers/owner.controller.test.ts +++ b/packages/cli/test/unit/controllers/owner.controller.test.ts @@ -1,10 +1,9 @@ import type { CookieOptions, Response } from 'express'; import { anyObject, captor, mock } from 'jest-mock-extended'; import jwt from 'jsonwebtoken'; -import type { IInternalHooksClass } from '@/Interfaces'; import type { User } from '@db/entities/User'; import type { SettingsRepository } from '@db/repositories/settings.repository'; -import type { Config } from '@/config'; +import config from '@/config'; import type { OwnerRequest } from '@/requests'; import { OwnerController } from '@/controllers/owner.controller'; import { AUTH_COOKIE_NAME } from '@/constants'; @@ -16,32 +15,33 @@ import { badPasswords } from '../shared/testData'; import { BadRequestError } from '@/errors/response-errors/bad-request.error'; import { PasswordUtility } from '@/services/password.utility'; import Container from 'typedi'; +import type { InternalHooks } from '@/InternalHooks'; describe('OwnerController', () => { - const config = mock(); - const internalHooks = mock(); + const configGetSpy = jest.spyOn(config, 'getEnv'); + const internalHooks = mock(); const userService = mockInstance(UserService); const settingsRepository = mock(); mockInstance(License).isWithinUsersLimit.mockReturnValue(true); const controller = new OwnerController( - config, mock(), internalHooks, settingsRepository, userService, Container.get(PasswordUtility), + mock(), ); describe('setupOwner', () => { it('should throw a BadRequestError if the instance owner is already setup', async () => { - config.getEnv.calledWith('userManagement.isInstanceOwnerSetUp').mockReturnValue(true); + configGetSpy.mockReturnValue(true); await expect(controller.setupOwner(mock(), mock())).rejects.toThrowError( new BadRequestError('Instance owner already setup'), ); }); it('should throw a BadRequestError if the email is invalid', async () => { - config.getEnv.calledWith('userManagement.isInstanceOwnerSetUp').mockReturnValue(false); + configGetSpy.mockReturnValue(false); const req = mock({ body: { email: 'invalid email' } }); await expect(controller.setupOwner(req, mock())).rejects.toThrowError( new BadRequestError('Invalid email address'), @@ -51,7 +51,7 @@ describe('OwnerController', () => { describe('should throw if the password is invalid', () => { Object.entries(badPasswords).forEach(([password, errorMessage]) => { it(password, async () => { - config.getEnv.calledWith('userManagement.isInstanceOwnerSetUp').mockReturnValue(false); + configGetSpy.mockReturnValue(false); const req = mock({ body: { email: 'valid@email.com', password } }); await expect(controller.setupOwner(req, mock())).rejects.toThrowError( new BadRequestError(errorMessage), @@ -61,7 +61,7 @@ describe('OwnerController', () => { }); it('should throw a BadRequestError if firstName & lastName are missing ', async () => { - config.getEnv.calledWith('userManagement.isInstanceOwnerSetUp').mockReturnValue(false); + configGetSpy.mockReturnValue(false); const req = mock({ body: { email: 'valid@email.com', password: 'NewPassword123', firstName: '', lastName: '' }, }); @@ -86,7 +86,7 @@ describe('OwnerController', () => { user, }); const res = mock(); - config.getEnv.calledWith('userManagement.isInstanceOwnerSetUp').mockReturnValue(false); + configGetSpy.mockReturnValue(false); userService.save.calledWith(anyObject()).mockResolvedValue(user); jest.spyOn(jwt, 'sign').mockImplementation(() => 'signed-token'); diff --git a/packages/cli/test/unit/controllers/translation.controller.test.ts b/packages/cli/test/unit/controllers/translation.controller.test.ts index 3abaf7f933..b56fdf2a58 100644 --- a/packages/cli/test/unit/controllers/translation.controller.test.ts +++ b/packages/cli/test/unit/controllers/translation.controller.test.ts @@ -1,6 +1,6 @@ import { mock } from 'jest-mock-extended'; import type { ICredentialTypes } from 'n8n-workflow'; -import type { Config } from '@/config'; +import config from '@/config'; import type { TranslationRequest } from '@/controllers/translation.controller'; import { TranslationController, @@ -9,9 +9,9 @@ import { import { BadRequestError } from '@/errors/response-errors/bad-request.error'; describe('TranslationController', () => { - const config = mock(); + const configGetSpy = jest.spyOn(config, 'getEnv'); const credentialTypes = mock(); - const controller = new TranslationController(config, credentialTypes); + const controller = new TranslationController(credentialTypes); describe('getCredentialTranslation', () => { it('should throw 400 on invalid credential types', async () => { @@ -27,7 +27,7 @@ describe('TranslationController', () => { it('should return translation json on valid credential types', async () => { const credentialType = 'credential-type'; const req = mock({ query: { credentialType } }); - config.getEnv.calledWith('defaultLocale').mockReturnValue('de'); + configGetSpy.mockReturnValue('de'); credentialTypes.recognizes.calledWith(credentialType).mockReturnValue(true); const response = { translation: 'string' }; jest.mock(`${CREDENTIAL_TRANSLATIONS_DIR}/de/credential-type.json`, () => response, { diff --git a/packages/core/src/Interfaces.ts b/packages/core/src/Interfaces.ts index 777f9055a7..40e5d88eee 100644 --- a/packages/core/src/Interfaces.ts +++ b/packages/core/src/Interfaces.ts @@ -5,6 +5,8 @@ import type { ValidationResult, } from 'n8n-workflow'; +export type Class = new (...args: A) => T; + export interface IProcessMessage { // eslint-disable-next-line @typescript-eslint/no-explicit-any data?: any; diff --git a/packages/core/test/utils.ts b/packages/core/test/utils.ts index 9dd142ebd5..8895875240 100644 --- a/packages/core/test/utils.ts +++ b/packages/core/test/utils.ts @@ -3,9 +3,10 @@ import { mock } from 'jest-mock-extended'; import { Duplex } from 'stream'; import type { DeepPartial } from 'ts-essentials'; +import type { Class } from '@/Interfaces'; export const mockInstance = ( - constructor: new (...args: unknown[]) => T, + constructor: Class, data: DeepPartial | undefined = undefined, ) => { const instance = mock(data);