From d8d6f664005d03707b7f2b84b016fe544e97d65f 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, 12 Apr 2023 10:59:14 +0200 Subject: [PATCH] refactor(core): Use injectable classes for db repositories (part-1) (no-changelog) (#5953) Co-authored-by: ricardo --- packages/cli/src/Db.ts | 77 +++++++++++-------- packages/cli/src/Interfaces.ts | 71 +++++++++-------- packages/cli/src/InternalHooks.ts | 11 ++- packages/cli/src/Ldap/helpers.ts | 3 +- .../credentials/credentials.service.ts | 6 +- .../v1/handlers/users/users.service.ts | 8 +- .../UserManagement/UserManagementHelper.ts | 16 ++-- packages/cli/src/WaitTracker.ts | 2 +- .../cli/src/WorkflowExecuteAdditionalData.ts | 2 +- packages/cli/src/WorkflowHelpers.ts | 11 +-- packages/cli/src/WorkflowRunnerProcess.ts | 6 +- packages/cli/src/api/e2e.api.ts | 15 ++-- packages/cli/src/api/workflowStats.api.ts | 2 +- packages/cli/src/commands/BaseCommand.ts | 10 +-- .../cli/src/commands/export/credentials.ts | 2 +- .../cli/src/commands/import/credentials.ts | 10 +-- packages/cli/src/commands/import/workflow.ts | 12 ++- .../cli/src/commands/user-management/reset.ts | 22 ++---- .../cli/src/controllers/auth.controller.ts | 4 +- packages/cli/src/controllers/me.controller.ts | 6 +- .../cli/src/controllers/owner.controller.ts | 20 ++--- .../controllers/passwordReset.controller.ts | 5 +- .../cli/src/controllers/tags.controller.ts | 4 +- .../cli/src/controllers/users.controller.ts | 22 +++--- .../src/credentials/credentials.service.ts | 19 ++--- ...tinationEntity.ts => EventDestinations.ts} | 0 packages/cli/src/databases/entities/index.ts | 2 +- .../repositories/authIdentity.repository.ts | 10 +++ .../authProviderSyncHistory.repository.ts | 10 +++ .../repositories/credentials.repository.ts | 10 +++ .../eventDestinations.repository.ts | 10 +++ .../repositories/execution.repository.ts | 10 +++ .../executionMetadata.repository.ts | 10 +++ .../cli/src/databases/repositories/index.ts | 18 +++++ .../repositories/installedNodes.repository.ts | 10 +++ .../installedPackages.repository.ts | 10 +++ .../databases/repositories/role.repository.ts | 59 ++++++++++++++ .../repositories/settings.repository.ts | 10 +++ .../sharedCredentials.repository.ts | 10 +++ .../repositories/sharedWorkflow.repository.ts | 10 +++ .../databases/repositories/tag.repository.ts | 10 +++ .../databases/repositories/user.repository.ts | 10 +++ .../repositories/webhook.repository.ts | 10 +++ .../repositories/workflow.repository.ts | 10 +++ .../workflowStatistics.repository.ts | 10 +++ .../workflowTagMapping.repository.ts | 10 +++ .../MessageEventBusDestination/Helpers.ee.ts | 2 +- .../cli/src/executions/executions.service.ts | 2 +- packages/cli/src/middlewares/auth.ts | 5 +- packages/cli/src/role/role.service.ts | 12 +-- packages/cli/src/sso/saml/saml.service.ee.ts | 4 +- packages/cli/src/sso/saml/samlHelpers.ts | 5 +- packages/cli/src/sso/ssoHelpers.ts | 2 +- .../src/workflows/workflows.controller.ee.ts | 11 +-- .../cli/src/workflows/workflows.controller.ts | 6 +- .../cli/test/integration/eventbus.test.ts | 3 +- .../test/integration/ldap/ldap.api.test.ts | 3 +- .../test/integration/saml/saml.api.test.ts | 3 +- .../cli/test/integration/shared/random.ts | 2 + .../cli/test/integration/shared/testDb.ts | 40 +++------- .../workflows.controller.ee.test.ts | 3 +- .../test/unit/ActiveWorkflowRunner.test.ts | 8 +- packages/cli/test/unit/Events.test.ts | 4 +- .../unit/controllers/me.controller.test.ts | 3 +- .../unit/controllers/owner.controller.test.ts | 19 +++-- .../unit/repositories/role.repository.test.ts | 47 +++++++++++ .../test/unit/services/role.service.test.ts | 28 +++++++ 67 files changed, 557 insertions(+), 270 deletions(-) rename packages/cli/src/databases/entities/{MessageEventBusDestinationEntity.ts => EventDestinations.ts} (100%) create mode 100644 packages/cli/src/databases/repositories/authIdentity.repository.ts create mode 100644 packages/cli/src/databases/repositories/authProviderSyncHistory.repository.ts create mode 100644 packages/cli/src/databases/repositories/credentials.repository.ts create mode 100644 packages/cli/src/databases/repositories/eventDestinations.repository.ts create mode 100644 packages/cli/src/databases/repositories/execution.repository.ts create mode 100644 packages/cli/src/databases/repositories/executionMetadata.repository.ts create mode 100644 packages/cli/src/databases/repositories/index.ts create mode 100644 packages/cli/src/databases/repositories/installedNodes.repository.ts create mode 100644 packages/cli/src/databases/repositories/installedPackages.repository.ts create mode 100644 packages/cli/src/databases/repositories/role.repository.ts create mode 100644 packages/cli/src/databases/repositories/settings.repository.ts create mode 100644 packages/cli/src/databases/repositories/sharedCredentials.repository.ts create mode 100644 packages/cli/src/databases/repositories/sharedWorkflow.repository.ts create mode 100644 packages/cli/src/databases/repositories/tag.repository.ts create mode 100644 packages/cli/src/databases/repositories/user.repository.ts create mode 100644 packages/cli/src/databases/repositories/webhook.repository.ts create mode 100644 packages/cli/src/databases/repositories/workflow.repository.ts create mode 100644 packages/cli/src/databases/repositories/workflowStatistics.repository.ts create mode 100644 packages/cli/src/databases/repositories/workflowTagMapping.repository.ts create mode 100644 packages/cli/test/unit/repositories/role.repository.test.ts create mode 100644 packages/cli/test/unit/services/role.service.test.ts diff --git a/packages/cli/src/Db.ts b/packages/cli/src/Db.ts index 2a6a4f38436ca..c97a95cd9e224 100644 --- a/packages/cli/src/Db.ts +++ b/packages/cli/src/Db.ts @@ -3,14 +3,8 @@ /* eslint-disable @typescript-eslint/restrict-template-expressions */ /* eslint-disable no-case-declarations */ /* eslint-disable @typescript-eslint/naming-convention */ -import type { - DataSourceOptions as ConnectionOptions, - EntityManager, - EntityTarget, - LoggerOptions, - ObjectLiteral, - Repository, -} from 'typeorm'; +import { Container } from 'typedi'; +import type { DataSourceOptions as ConnectionOptions, EntityManager, LoggerOptions } from 'typeorm'; import { DataSource as Connection } from 'typeorm'; import type { TlsOptions } from 'tls'; import type { DatabaseType, IDatabaseCollections } from '@/Interfaces'; @@ -25,11 +19,31 @@ import { getPostgresConnectionOptions, getSqliteConnectionOptions, } from '@db/config'; +import { + AuthIdentityRepository, + AuthProviderSyncHistoryRepository, + CredentialsRepository, + EventDestinationsRepository, + ExecutionMetadataRepository, + ExecutionRepository, + InstalledNodesRepository, + InstalledPackagesRepository, + RoleRepository, + SettingsRepository, + SharedCredentialsRepository, + SharedWorkflowRepository, + TagRepository, + UserRepository, + WebhookRepository, + WorkflowRepository, + WorkflowStatisticsRepository, + WorkflowTagMappingRepository, +} from '@db/repositories'; export let isInitialized = false; export const collections = {} as IDatabaseCollections; -export let connection: Connection; +let connection: Connection; export const getConnection = () => connection!; @@ -37,12 +51,6 @@ export async function transaction(fn: (entityManager: EntityManager) => Promi return connection.transaction(fn); } -export function linkRepository( - entityClass: EntityTarget, -): Repository { - return connection.getRepository(entityClass); -} - export function getConnectionOptions(dbType: DatabaseType): ConnectionOptions { switch (dbType) { case 'postgresdb': @@ -114,6 +122,7 @@ export async function init( }); connection = new Connection(connectionOptions); + Container.set(Connection, connection); await connection.initialize(); if (dbType === 'postgresdb') { @@ -148,31 +157,31 @@ export async function init( if (migrations.length === 0) { await connection.destroy(); connection = new Connection(connectionOptions); + Container.set(Connection, connection); await connection.initialize(); } } else { await connection.runMigrations({ transaction: 'each' }); } - collections.Credentials = linkRepository(entities.CredentialsEntity); - collections.Execution = linkRepository(entities.ExecutionEntity); - collections.Workflow = linkRepository(entities.WorkflowEntity); - collections.Webhook = linkRepository(entities.WebhookEntity); - collections.Tag = linkRepository(entities.TagEntity); - collections.WorkflowTagMapping = linkRepository(entities.WorkflowTagMapping); - collections.Role = linkRepository(entities.Role); - collections.User = linkRepository(entities.User); - collections.AuthIdentity = linkRepository(entities.AuthIdentity); - collections.AuthProviderSyncHistory = linkRepository(entities.AuthProviderSyncHistory); - collections.SharedCredentials = linkRepository(entities.SharedCredentials); - collections.SharedWorkflow = linkRepository(entities.SharedWorkflow); - collections.Settings = linkRepository(entities.Settings); - collections.InstalledPackages = linkRepository(entities.InstalledPackages); - collections.InstalledNodes = linkRepository(entities.InstalledNodes); - collections.WorkflowStatistics = linkRepository(entities.WorkflowStatistics); - collections.ExecutionMetadata = linkRepository(entities.ExecutionMetadata); - - collections.EventDestinations = linkRepository(entities.EventDestinations); + collections.AuthIdentity = Container.get(AuthIdentityRepository); + collections.AuthProviderSyncHistory = Container.get(AuthProviderSyncHistoryRepository); + collections.Credentials = Container.get(CredentialsRepository); + collections.EventDestinations = Container.get(EventDestinationsRepository); + collections.Execution = Container.get(ExecutionRepository); + collections.ExecutionMetadata = Container.get(ExecutionMetadataRepository); + collections.InstalledNodes = Container.get(InstalledNodesRepository); + collections.InstalledPackages = Container.get(InstalledPackagesRepository); + collections.Role = Container.get(RoleRepository); + collections.Settings = Container.get(SettingsRepository); + collections.SharedCredentials = Container.get(SharedCredentialsRepository); + collections.SharedWorkflow = Container.get(SharedWorkflowRepository); + collections.Tag = Container.get(TagRepository); + collections.User = Container.get(UserRepository); + collections.Webhook = Container.get(WebhookRepository); + collections.Workflow = Container.get(WorkflowRepository); + collections.WorkflowStatistics = Container.get(WorkflowStatisticsRepository); + collections.WorkflowTagMapping = Container.get(WorkflowTagMappingRepository); isInitialized = true; diff --git a/packages/cli/src/Interfaces.ts b/packages/cli/src/Interfaces.ts index 62e83bb5d6bf4..cb4ad810f7f87 100644 --- a/packages/cli/src/Interfaces.ts +++ b/packages/cli/src/Interfaces.ts @@ -32,26 +32,35 @@ import type { ActiveWorkflowRunner } from '@/ActiveWorkflowRunner'; import type { WorkflowExecute } from 'n8n-core'; import type PCancelable from 'p-cancelable'; -import type { FindOperator, Repository } from 'typeorm'; +import type { FindOperator } from 'typeorm'; import type { ChildProcess } from 'child_process'; -import type { AuthIdentity, AuthProviderType } from '@db/entities/AuthIdentity'; -import type { AuthProviderSyncHistory } from '@db/entities/AuthProviderSyncHistory'; -import type { InstalledNodes } from '@db/entities/InstalledNodes'; -import type { InstalledPackages } from '@db/entities/InstalledPackages'; +import type { AuthProviderType } from '@db/entities/AuthIdentity'; import type { Role } from '@db/entities/Role'; -import type { Settings } from '@db/entities/Settings'; import type { SharedCredentials } from '@db/entities/SharedCredentials'; -import type { SharedWorkflow } from '@db/entities/SharedWorkflow'; import type { TagEntity } from '@db/entities/TagEntity'; import type { User } from '@db/entities/User'; -import type { WebhookEntity } from '@db/entities/WebhookEntity'; -import type { WorkflowEntity } from '@db/entities/WorkflowEntity'; -import type { WorkflowStatistics } from '@db/entities/WorkflowStatistics'; -import type { WorkflowTagMapping } from '@db/entities/WorkflowTagMapping'; -import type { EventDestinations } from '@db/entities/MessageEventBusDestinationEntity'; -import type { ExecutionMetadata } from '@db/entities/ExecutionMetadata'; +import type { + AuthIdentityRepository, + AuthProviderSyncHistoryRepository, + CredentialsRepository, + EventDestinationsRepository, + ExecutionMetadataRepository, + ExecutionRepository, + InstalledNodesRepository, + InstalledPackagesRepository, + RoleRepository, + SettingsRepository, + SharedCredentialsRepository, + SharedWorkflowRepository, + TagRepository, + UserRepository, + WebhookRepository, + WorkflowRepository, + WorkflowStatisticsRepository, + WorkflowTagMappingRepository, +} from '@db/repositories'; export interface IActivationError { time: number; @@ -76,24 +85,24 @@ export interface ICredentialsOverwrite { } export interface IDatabaseCollections { - AuthIdentity: Repository; - AuthProviderSyncHistory: Repository; - Credentials: Repository; - Execution: Repository; - Workflow: Repository; - Webhook: Repository; - Tag: Repository; - WorkflowTagMapping: Repository; - Role: Repository; - User: Repository; - SharedCredentials: Repository; - SharedWorkflow: Repository; - Settings: Repository; - InstalledPackages: Repository; - InstalledNodes: Repository; - WorkflowStatistics: Repository; - EventDestinations: Repository; - ExecutionMetadata: Repository; + AuthIdentity: AuthIdentityRepository; + AuthProviderSyncHistory: AuthProviderSyncHistoryRepository; + Credentials: CredentialsRepository; + EventDestinations: EventDestinationsRepository; + Execution: ExecutionRepository; + ExecutionMetadata: ExecutionMetadataRepository; + InstalledNodes: InstalledNodesRepository; + InstalledPackages: InstalledPackagesRepository; + Role: RoleRepository; + Settings: SettingsRepository; + SharedCredentials: SharedCredentialsRepository; + SharedWorkflow: SharedWorkflowRepository; + Tag: TagRepository; + User: UserRepository; + Webhook: WebhookRepository; + Workflow: WorkflowRepository; + WorkflowStatistics: WorkflowStatisticsRepository; + WorkflowTagMapping: WorkflowTagMappingRepository; } // ---------------------------------- diff --git a/packages/cli/src/InternalHooks.ts b/packages/cli/src/InternalHooks.ts index ea36dc04b8627..0092a3d990d90 100644 --- a/packages/cli/src/InternalHooks.ts +++ b/packages/cli/src/InternalHooks.ts @@ -51,7 +51,11 @@ function userToPayload(user: User): { export class InternalHooks implements IInternalHooksClass { private instanceId: string; - constructor(private telemetry: Telemetry, private nodeTypes: NodeTypes) {} + constructor( + private telemetry: Telemetry, + private nodeTypes: NodeTypes, + private roleService: RoleService, + ) {} async init(instanceId: string) { this.instanceId = instanceId; @@ -155,7 +159,7 @@ export class InternalHooks implements IInternalHooksClass { let userRole: 'owner' | 'sharee' | undefined = undefined; if (user.id && workflow.id) { - const role = await RoleService.getUserRoleForWorkflow(user.id, workflow.id); + const role = await this.roleService.getUserRoleForWorkflow(user.id, workflow.id); if (role) { userRole = role.name === 'owner' ? 'owner' : 'sharee'; } @@ -342,8 +346,7 @@ export class InternalHooks implements IInternalHooksClass { let userRole: 'owner' | 'sharee' | undefined = undefined; if (userId) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument - const role = await RoleService.getUserRoleForWorkflow(userId, workflow.id); + const role = await this.roleService.getUserRoleForWorkflow(userId, workflow.id); if (role) { userRole = role.name === 'owner' ? 'owner' : 'sharee'; } diff --git a/packages/cli/src/Ldap/helpers.ts b/packages/cli/src/Ldap/helpers.ts index 21779d21a03ac..8c51add60be37 100644 --- a/packages/cli/src/Ldap/helpers.ts +++ b/packages/cli/src/Ldap/helpers.ts @@ -10,6 +10,7 @@ import config from '@/config'; import type { Role } from '@db/entities/Role'; import { User } from '@db/entities/User'; import { AuthIdentity } from '@db/entities/AuthIdentity'; +import { RoleRepository } from '@db/repositories'; import type { AuthProviderSyncHistory } from '@db/entities/AuthProviderSyncHistory'; import { isUserManagementEnabled } from '@/UserManagement/UserManagementHelper'; import { LdapManager } from './LdapManager.ee'; @@ -93,7 +94,7 @@ export const randomPassword = (): string => { * Return the user role to be assigned to LDAP users */ export const getLdapUserRole = async (): Promise => { - return Db.collections.Role.findOneByOrFail({ scope: 'global', name: 'member' }); + return Container.get(RoleRepository).findGlobalMemberRoleOrFail(); }; /** diff --git a/packages/cli/src/PublicApi/v1/handlers/credentials/credentials.service.ts b/packages/cli/src/PublicApi/v1/handlers/credentials/credentials.service.ts index d1a44c8c08a82..e207ac55bc7bf 100644 --- a/packages/cli/src/PublicApi/v1/handlers/credentials/credentials.service.ts +++ b/packages/cli/src/PublicApi/v1/handlers/credentials/credentials.service.ts @@ -5,6 +5,7 @@ import type { ICredentialsDb } from '@/Interfaces'; import { CredentialsEntity } from '@db/entities/CredentialsEntity'; import { SharedCredentials } from '@db/entities/SharedCredentials'; import type { User } from '@db/entities/User'; +import { RoleRepository } from '@db/repositories'; import { ExternalHooks } from '@/ExternalHooks'; import type { IDependency, IJsonSchema } from '../../../types'; import type { CredentialRequest } from '@/requests'; @@ -58,10 +59,7 @@ export async function saveCredential( user: User, encryptedData: ICredentialsDb, ): Promise { - const role = await Db.collections.Role.findOneByOrFail({ - name: 'owner', - scope: 'credential', - }); + const role = await Container.get(RoleRepository).findCredentialOwnerRoleOrFail(); await Container.get(ExternalHooks).run('credentials.create', [encryptedData]); diff --git a/packages/cli/src/PublicApi/v1/handlers/users/users.service.ts b/packages/cli/src/PublicApi/v1/handlers/users/users.service.ts index 57ddb4b84a389..064d0be471416 100644 --- a/packages/cli/src/PublicApi/v1/handlers/users/users.service.ts +++ b/packages/cli/src/PublicApi/v1/handlers/users/users.service.ts @@ -1,4 +1,5 @@ -import * as Db from '@/Db'; +import { Container } from 'typedi'; +import { RoleRepository } from '@db/repositories'; import type { Role } from '@db/entities/Role'; import type { User } from '@db/entities/User'; @@ -7,8 +8,5 @@ export function isInstanceOwner(user: User): boolean { } export async function getWorkflowOwnerRole(): Promise { - return Db.collections.Role.findOneByOrFail({ - name: 'owner', - scope: 'workflow', - }); + return Container.get(RoleRepository).findWorkflowOwnerRoleOrFail(); } diff --git a/packages/cli/src/UserManagement/UserManagementHelper.ts b/packages/cli/src/UserManagement/UserManagementHelper.ts index 60278be38f799..3e705efbd648c 100644 --- a/packages/cli/src/UserManagement/UserManagementHelper.ts +++ b/packages/cli/src/UserManagement/UserManagementHelper.ts @@ -3,7 +3,7 @@ import { In } from 'typeorm'; import type express from 'express'; import { compare, genSaltSync, hash } from 'bcryptjs'; -import Container from 'typedi'; +import { Container } from 'typedi'; import * as Db from '@/Db'; import * as ResponseHelper from '@/ResponseHelper'; @@ -11,15 +11,15 @@ import type { CurrentUser, PublicUser, WhereClause } from '@/Interfaces'; import type { User } from '@db/entities/User'; import { MAX_PASSWORD_LENGTH, MIN_PASSWORD_LENGTH } from '@db/entities/User'; import type { Role } from '@db/entities/Role'; +import { RoleRepository } from '@db/repositories'; import type { AuthenticatedRequest } from '@/requests'; import config from '@/config'; import { getWebhookBaseUrl } from '@/WebhookHelpers'; import { License } from '@/License'; -import { RoleService } from '@/role/role.service'; import type { PostHogClient } from '@/posthog'; export async function getWorkflowOwner(workflowId: string): Promise { - const workflowOwnerRole = await RoleService.get({ name: 'owner', scope: 'workflow' }); + const workflowOwnerRole = await Container.get(RoleRepository).findWorkflowOwnerRole(); const sharedWorkflow = await Db.collections.SharedWorkflow.findOneOrFail({ where: { workflowId, roleId: workflowOwnerRole?.id ?? undefined }, @@ -61,13 +61,9 @@ export function isSharingEnabled(): boolean { } export async function getRoleId(scope: Role['scope'], name: Role['name']): Promise { - return Db.collections.Role.findOneOrFail({ - select: ['id'], - where: { - name, - scope, - }, - }).then((role) => role.id); + return Container.get(RoleRepository) + .findRoleOrFail(scope, name) + .then((role) => role.id); } export async function getInstanceOwner(): Promise { diff --git a/packages/cli/src/WaitTracker.ts b/packages/cli/src/WaitTracker.ts index 1a58e135dda53..e04daee126ae5 100644 --- a/packages/cli/src/WaitTracker.ts +++ b/packages/cli/src/WaitTracker.ts @@ -131,7 +131,7 @@ export class WaitTracker { executionId, ResponseHelper.flattenExecutionData({ ...fullExecutionData, - }), + }) as IExecutionFlattedDb, ); return { diff --git a/packages/cli/src/WorkflowExecuteAdditionalData.ts b/packages/cli/src/WorkflowExecuteAdditionalData.ts index 178e748c767eb..d2344c80bff0a 100644 --- a/packages/cli/src/WorkflowExecuteAdditionalData.ts +++ b/packages/cli/src/WorkflowExecuteAdditionalData.ts @@ -71,7 +71,7 @@ import { PermissionChecker } from './UserManagement/PermissionChecker'; import { WorkflowsService } from './workflows/workflows.services'; import { Container } from 'typedi'; import { InternalHooks } from '@/InternalHooks'; -import type { ExecutionMetadata } from './databases/entities/ExecutionMetadata'; +import type { ExecutionMetadata } from '@db/entities/ExecutionMetadata'; const ERROR_TRIGGER_TYPE = config.getEnv('nodes.errorTriggerType'); diff --git a/packages/cli/src/WorkflowHelpers.ts b/packages/cli/src/WorkflowHelpers.ts index 58628b76a9c9d..e92d38603d102 100644 --- a/packages/cli/src/WorkflowHelpers.ts +++ b/packages/cli/src/WorkflowHelpers.ts @@ -30,6 +30,7 @@ import { WorkflowRunner } from '@/WorkflowRunner'; import config from '@/config'; import type { WorkflowEntity } from '@db/entities/WorkflowEntity'; import type { User } from '@db/entities/User'; +import { RoleRepository } from '@db/repositories'; import { whereClause } from '@/UserManagement/UserManagementHelper'; import omit from 'lodash.omit'; import { PermissionChecker } from './UserManagement/PermissionChecker'; @@ -389,17 +390,11 @@ export async function isBelowOnboardingThreshold(user: User): Promise { let belowThreshold = true; const skippedTypes = ['n8n-nodes-base.start', 'n8n-nodes-base.stickyNote']; - const workflowOwnerRoleId = await Db.collections.Role.findOne({ - select: ['id'], - where: { - name: 'owner', - scope: 'workflow', - }, - }).then((role) => role?.id); + const workflowOwnerRole = await Container.get(RoleRepository).findWorkflowOwnerRole(); const ownedWorkflowsIds = await Db.collections.SharedWorkflow.find({ where: { userId: user.id, - roleId: workflowOwnerRoleId, + roleId: workflowOwnerRole?.id, }, select: ['workflowId'], }).then((ownedWorkflows) => ownedWorkflows.map(({ workflowId }) => workflowId)); diff --git a/packages/cli/src/WorkflowRunnerProcess.ts b/packages/cli/src/WorkflowRunnerProcess.ts index 4d1f32a7dc58e..3b61ed11fab82 100644 --- a/packages/cli/src/WorkflowRunnerProcess.ts +++ b/packages/cli/src/WorkflowRunnerProcess.ts @@ -117,6 +117,9 @@ class WorkflowRunnerProcess { const externalHooks = Container.get(ExternalHooks); await externalHooks.init(); + // Init db since we need to read the license. + await Db.init(); + const instanceId = userSettings.instanceId ?? ''; await Container.get(PostHogClient).init(instanceId); await Container.get(InternalHooks).init(instanceId); @@ -124,9 +127,6 @@ class WorkflowRunnerProcess { const binaryDataConfig = config.getEnv('binaryDataManager'); await BinaryDataManager.init(binaryDataConfig); - // Init db since we need to read the license. - await Db.init(); - const license = Container.get(License); await license.init(instanceId); diff --git a/packages/cli/src/api/e2e.api.ts b/packages/cli/src/api/e2e.api.ts index 249bd769ba385..0de1f207bafbf 100644 --- a/packages/cli/src/api/e2e.api.ts +++ b/packages/cli/src/api/e2e.api.ts @@ -8,12 +8,13 @@ import { Router } from 'express'; import type { Request } from 'express'; import bodyParser from 'body-parser'; import { v4 as uuid } from 'uuid'; +import { Container } from 'typedi'; import config from '@/config'; import * as Db from '@/Db'; import type { Role } from '@db/entities/Role'; +import { RoleRepository } from '@db/repositories'; import { hashPassword } from '@/UserManagement/UserManagementHelper'; import { eventBus } from '@/eventbus/MessageEventBus/MessageEventBus'; -import Container from 'typedi'; import { License } from '../License'; import { LICENSE_FEATURES } from '@/constants'; @@ -55,7 +56,7 @@ const tablesToTruncate = [ ]; const truncateAll = async () => { - const { connection } = Db; + const connection = Db.getConnection(); for (const table of tablesToTruncate) { await connection.query( `DELETE FROM ${table}; DELETE FROM sqlite_sequence WHERE name=${table};`, @@ -64,7 +65,7 @@ const truncateAll = async () => { }; const setupUserManagement = async () => { - const { connection } = Db; + const connection = Db.getConnection(); await connection.query('INSERT INTO role (name, scope) VALUES ("owner", "global");'); const instanceOwnerRole = (await connection.query( 'SELECT last_insert_rowid() as insertId', @@ -116,13 +117,7 @@ e2eController.post('/db/setup-owner', bodyParser.json(), async (req, res) => { return; } - const globalRole = await Db.collections.Role.findOneOrFail({ - select: ['id'], - where: { - name: 'owner', - scope: 'global', - }, - }); + const globalRole = await Container.get(RoleRepository).findGlobalOwnerRoleOrFail(); const owner = await Db.collections.User.findOneByOrFail({ globalRoleId: globalRole.id }); diff --git a/packages/cli/src/api/workflowStats.api.ts b/packages/cli/src/api/workflowStats.api.ts index b8b23a9803dfe..ed16888efea6d 100644 --- a/packages/cli/src/api/workflowStats.api.ts +++ b/packages/cli/src/api/workflowStats.api.ts @@ -9,7 +9,7 @@ import type { IWorkflowStatisticsDataLoaded, IWorkflowStatisticsTimestamps, } from '@/Interfaces'; -import { StatisticsNames } from '../databases/entities/WorkflowStatistics'; +import { StatisticsNames } from '@db/entities/WorkflowStatistics'; import { getLogger } from '../Logger'; import type { ExecutionRequest } from '../requests'; diff --git a/packages/cli/src/commands/BaseCommand.ts b/packages/cli/src/commands/BaseCommand.ts index d50846cf34cc4..c8292b5408d83 100644 --- a/packages/cli/src/commands/BaseCommand.ts +++ b/packages/cli/src/commands/BaseCommand.ts @@ -51,13 +51,13 @@ export abstract class BaseCommand extends Command { const credentialTypes = Container.get(CredentialTypes); CredentialsOverwrites(credentialTypes); - this.instanceId = this.userSettings.instanceId ?? ''; - await Container.get(PostHogClient).init(this.instanceId); - await Container.get(InternalHooks).init(this.instanceId); - await Db.init().catch(async (error: Error) => this.exitWithCrash('There was an error initializing DB', error), ); + + this.instanceId = this.userSettings.instanceId ?? ''; + await Container.get(PostHogClient).init(this.instanceId); + await Container.get(InternalHooks).init(this.instanceId); } protected async stopProcess() { @@ -96,7 +96,7 @@ export abstract class BaseCommand extends Command { if (inTest || this.id === 'start') return; if (Db.isInitialized) { await sleep(100); // give any in-flight query some time to finish - await Db.connection.destroy(); + await Db.getConnection().destroy(); } const exitCode = error instanceof ExitError ? error.oclif.exit : error ? 1 : 0; this.exit(exitCode); diff --git a/packages/cli/src/commands/export/credentials.ts b/packages/cli/src/commands/export/credentials.ts index 110febe25e8d1..7d8734f13aa96 100644 --- a/packages/cli/src/commands/export/credentials.ts +++ b/packages/cli/src/commands/export/credentials.ts @@ -110,7 +110,7 @@ export class ExportCredentialsCommand extends BaseCommand { findQuery.id = flags.id; } - const credentials = await Db.collections.Credentials.findBy(findQuery); + const credentials: ICredentialsDb[] = await Db.collections.Credentials.findBy(findQuery); if (flags.decrypted) { const encryptionKey = await UserSettings.getEncryptionKey(); diff --git a/packages/cli/src/commands/import/credentials.ts b/packages/cli/src/commands/import/credentials.ts index 985c70fe8981e..27af3b12b63da 100644 --- a/packages/cli/src/commands/import/credentials.ts +++ b/packages/cli/src/commands/import/credentials.ts @@ -2,6 +2,7 @@ import { flags } from '@oclif/command'; import { Credentials } from 'n8n-core'; import fs from 'fs'; import glob from 'fast-glob'; +import { Container } from 'typedi'; import type { EntityManager } from 'typeorm'; import config from '@/config'; import * as Db from '@/Db'; @@ -9,6 +10,7 @@ import type { User } from '@db/entities/User'; import { SharedCredentials } from '@db/entities/SharedCredentials'; import type { Role } from '@db/entities/Role'; import { CredentialsEntity } from '@db/entities/CredentialsEntity'; +import { RoleRepository } from '@db/repositories'; import { disableAutoGeneratedIds } from '@db/utils/commandHelpers'; import { BaseCommand, UM_FIX_INSTRUCTION } from '../BaseCommand'; import type { ICredentialsEncrypted } from 'n8n-workflow'; @@ -146,9 +148,7 @@ export class ImportCredentialsCommand extends BaseCommand { } private async initOwnerCredentialRole() { - const ownerCredentialRole = await Db.collections.Role.findOne({ - where: { name: 'owner', scope: 'credential' }, - }); + const ownerCredentialRole = await Container.get(RoleRepository).findCredentialOwnerRole(); if (!ownerCredentialRole) { throw new Error(`Failed to find owner credential role. ${UM_FIX_INSTRUCTION}`); @@ -177,9 +177,7 @@ export class ImportCredentialsCommand extends BaseCommand { } private async getOwner() { - const ownerGlobalRole = await Db.collections.Role.findOne({ - where: { name: 'owner', scope: 'global' }, - }); + const ownerGlobalRole = await Container.get(RoleRepository).findGlobalOwnerRole(); const owner = ownerGlobalRole && diff --git a/packages/cli/src/commands/import/workflow.ts b/packages/cli/src/commands/import/workflow.ts index 96349112731f3..7faa184e5e541 100644 --- a/packages/cli/src/commands/import/workflow.ts +++ b/packages/cli/src/commands/import/workflow.ts @@ -3,6 +3,7 @@ import type { INode, INodeCredentialsDetails } from 'n8n-workflow'; import { jsonParse } from 'n8n-workflow'; import fs from 'fs'; import glob from 'fast-glob'; +import { Container } from 'typedi'; import type { EntityManager } from 'typeorm'; import { v4 as uuid } from 'uuid'; import config from '@/config'; @@ -12,8 +13,9 @@ import { WorkflowEntity } from '@db/entities/WorkflowEntity'; import type { Role } from '@db/entities/Role'; import type { User } from '@db/entities/User'; import { setTagsForImport } from '@/TagHelpers'; -import type { ICredentialsDb, IWorkflowToImport } from '@/Interfaces'; +import { RoleRepository } from '@db/repositories'; import { disableAutoGeneratedIds } from '@db/utils/commandHelpers'; +import type { ICredentialsDb, IWorkflowToImport } from '@/Interfaces'; import { replaceInvalidCredentials } from '@/WorkflowHelpers'; import { BaseCommand, UM_FIX_INSTRUCTION } from '../BaseCommand'; @@ -205,9 +207,7 @@ export class ImportWorkflowsCommand extends BaseCommand { } private async initOwnerWorkflowRole() { - const ownerWorkflowRole = await Db.collections.Role.findOne({ - where: { name: 'owner', scope: 'workflow' }, - }); + const ownerWorkflowRole = await Container.get(RoleRepository).findWorkflowOwnerRole(); if (!ownerWorkflowRole) { throw new Error(`Failed to find owner workflow role. ${UM_FIX_INSTRUCTION}`); @@ -236,9 +236,7 @@ export class ImportWorkflowsCommand extends BaseCommand { } private async getOwner() { - const ownerGlobalRole = await Db.collections.Role.findOne({ - where: { name: 'owner', scope: 'global' }, - }); + const ownerGlobalRole = await Container.get(RoleRepository).findGlobalOwnerRole(); const owner = ownerGlobalRole && diff --git a/packages/cli/src/commands/user-management/reset.ts b/packages/cli/src/commands/user-management/reset.ts index 8c3bac1a96dc6..66c8de0117cd5 100644 --- a/packages/cli/src/commands/user-management/reset.ts +++ b/packages/cli/src/commands/user-management/reset.ts @@ -1,7 +1,9 @@ +import { Container } from 'typedi'; import { Not } from 'typeorm'; import * as Db from '@/Db'; import type { CredentialsEntity } from '@db/entities/CredentialsEntity'; import { User } from '@db/entities/User'; +import { RoleRepository } from '@db/repositories'; import { BaseCommand } from '../BaseCommand'; const defaultUserProps = { @@ -20,15 +22,8 @@ export class Reset extends BaseCommand { async run(): Promise { const owner = await this.getInstanceOwner(); - const ownerWorkflowRole = await Db.collections.Role.findOneByOrFail({ - name: 'owner', - scope: 'workflow', - }); - - const ownerCredentialRole = await Db.collections.Role.findOneByOrFail({ - name: 'owner', - scope: 'credential', - }); + const ownerWorkflowRole = await Container.get(RoleRepository).findWorkflowOwnerRoleOrFail(); + const ownerCredentialRole = await Container.get(RoleRepository).findCredentialOwnerRoleOrFail(); await Db.collections.SharedWorkflow.update( { userId: Not(owner.id), roleId: ownerWorkflowRole.id }, @@ -44,10 +39,10 @@ export class Reset extends BaseCommand { await Db.collections.User.save(Object.assign(owner, defaultUserProps)); const danglingCredentials: CredentialsEntity[] = - (await Db.collections.Credentials.createQueryBuilder('credentials') + await Db.collections.Credentials.createQueryBuilder('credentials') .leftJoinAndSelect('credentials.shared', 'shared') .where('shared.credentialsId is null') - .getMany()) as CredentialsEntity[]; + .getMany(); const newSharedCredentials = danglingCredentials.map((credentials) => Db.collections.SharedCredentials.create({ credentials, @@ -70,10 +65,7 @@ export class Reset extends BaseCommand { } async getInstanceOwner(): Promise { - const globalRole = await Db.collections.Role.findOneByOrFail({ - name: 'owner', - scope: 'global', - }); + const globalRole = await Container.get(RoleRepository).findGlobalOwnerRoleOrFail(); const owner = await Db.collections.User.findOneBy({ globalRoleId: globalRole.id }); diff --git a/packages/cli/src/controllers/auth.controller.ts b/packages/cli/src/controllers/auth.controller.ts index 7a6405de36479..068522ce8700d 100644 --- a/packages/cli/src/controllers/auth.controller.ts +++ b/packages/cli/src/controllers/auth.controller.ts @@ -8,7 +8,6 @@ import { Request, Response } from 'express'; import type { ILogger } from 'n8n-workflow'; import type { User } from '@db/entities/User'; import { LoginRequest, UserRequest } from '@/requests'; -import type { Repository } from 'typeorm'; import { In } from 'typeorm'; import type { Config } from '@/config'; import type { @@ -23,6 +22,7 @@ import { isLdapCurrentAuthenticationMethod, isSamlCurrentAuthenticationMethod, } from '@/sso/ssoHelpers'; +import type { UserRepository } from '@db/repositories'; @RestController() export class AuthController { @@ -32,7 +32,7 @@ export class AuthController { private readonly internalHooks: IInternalHooksClass; - private readonly userRepository: Repository; + private readonly userRepository: UserRepository; private readonly postHog?: PostHogClient; diff --git a/packages/cli/src/controllers/me.controller.ts b/packages/cli/src/controllers/me.controller.ts index 3a942f3f24fe1..3cf1ba409b262 100644 --- a/packages/cli/src/controllers/me.controller.ts +++ b/packages/cli/src/controllers/me.controller.ts @@ -8,11 +8,11 @@ import { validatePassword, } from '@/UserManagement/UserManagementHelper'; import { BadRequestError } from '@/ResponseHelper'; -import type { User } from '@db/entities/User'; import { validateEntity } from '@/GenericHelpers'; import { issueCookie } from '@/auth/jwt'; +import type { User } from '@db/entities/User'; +import type { UserRepository } from '@db/repositories'; import { Response } from 'express'; -import type { Repository } from 'typeorm'; import type { ILogger } from 'n8n-workflow'; import { AuthenticatedRequest, @@ -38,7 +38,7 @@ export class MeController { private readonly internalHooks: IInternalHooksClass; - private readonly userRepository: Repository; + private readonly userRepository: UserRepository; constructor({ logger, diff --git a/packages/cli/src/controllers/owner.controller.ts b/packages/cli/src/controllers/owner.controller.ts index 8040c5aaac531..d83a1fecee17d 100644 --- a/packages/cli/src/controllers/owner.controller.ts +++ b/packages/cli/src/controllers/owner.controller.ts @@ -9,14 +9,16 @@ import { } from '@/UserManagement/UserManagementHelper'; import { issueCookie } from '@/auth/jwt'; import { Response } from 'express'; -import type { Repository } from 'typeorm'; import type { ILogger } from 'n8n-workflow'; import type { Config } from '@/config'; import { OwnerRequest } from '@/requests'; -import type { IDatabaseCollections, IInternalHooksClass, ICredentialsDb } from '@/Interfaces'; -import type { Settings } from '@db/entities/Settings'; -import type { User } from '@db/entities/User'; -import type { WorkflowEntity } from '@db/entities/WorkflowEntity'; +import type { IDatabaseCollections, IInternalHooksClass } from '@/Interfaces'; +import type { + CredentialsRepository, + SettingsRepository, + UserRepository, + WorkflowRepository, +} from '@db/repositories'; @RestController('/owner') export class OwnerController { @@ -26,13 +28,13 @@ export class OwnerController { private readonly internalHooks: IInternalHooksClass; - private readonly userRepository: Repository; + private readonly userRepository: UserRepository; - private readonly settingsRepository: Repository; + private readonly settingsRepository: SettingsRepository; - private readonly credentialsRepository: Repository; + private readonly credentialsRepository: CredentialsRepository; - private readonly workflowsRepository: Repository; + private readonly workflowsRepository: WorkflowRepository; constructor({ config, diff --git a/packages/cli/src/controllers/passwordReset.controller.ts b/packages/cli/src/controllers/passwordReset.controller.ts index fd9856bd4a3bb..d78424f6d7303 100644 --- a/packages/cli/src/controllers/passwordReset.controller.ts +++ b/packages/cli/src/controllers/passwordReset.controller.ts @@ -1,4 +1,3 @@ -import type { Repository } from 'typeorm'; import { IsNull, MoreThanOrEqual, Not } from 'typeorm'; import { v4 as uuid } from 'uuid'; import validator from 'validator'; @@ -20,7 +19,7 @@ import type { UserManagementMailer } from '@/UserManagement/email'; import { Response } from 'express'; import type { ILogger } from 'n8n-workflow'; import type { Config } from '@/config'; -import type { User } from '@db/entities/User'; +import type { UserRepository } from '@db/repositories'; import { PasswordResetRequest } from '@/requests'; import type { IDatabaseCollections, IExternalHooksClass, IInternalHooksClass } from '@/Interfaces'; import { issueCookie } from '@/auth/jwt'; @@ -39,7 +38,7 @@ export class PasswordResetController { private readonly mailer: UserManagementMailer; - private readonly userRepository: Repository; + private readonly userRepository: UserRepository; constructor({ config, diff --git a/packages/cli/src/controllers/tags.controller.ts b/packages/cli/src/controllers/tags.controller.ts index 154f003e5f263..d8ccb4a28ca9c 100644 --- a/packages/cli/src/controllers/tags.controller.ts +++ b/packages/cli/src/controllers/tags.controller.ts @@ -1,9 +1,9 @@ import { Request, Response, NextFunction } from 'express'; -import type { Repository } from 'typeorm'; import type { Config } from '@/config'; import { Delete, Get, Middleware, Patch, Post, RestController } from '@/decorators'; import type { IDatabaseCollections, IExternalHooksClass, ITagWithCountDb } from '@/Interfaces'; import { TagEntity } from '@db/entities/TagEntity'; +import type { TagRepository } from '@db/repositories'; import { validateEntity } from '@/GenericHelpers'; import { BadRequestError, UnauthorizedError } from '@/ResponseHelper'; import { TagsRequest } from '@/requests'; @@ -14,7 +14,7 @@ export class TagsController { private externalHooks: IExternalHooksClass; - private tagsRepository: Repository; + private tagsRepository: TagRepository; constructor({ config, diff --git a/packages/cli/src/controllers/users.controller.ts b/packages/cli/src/controllers/users.controller.ts index bb094f9ef1841..fc9faf06fd9e3 100644 --- a/packages/cli/src/controllers/users.controller.ts +++ b/packages/cli/src/controllers/users.controller.ts @@ -1,5 +1,4 @@ import validator from 'validator'; -import type { Repository } from 'typeorm'; import { In } from 'typeorm'; import type { ILogger } from 'n8n-workflow'; import { ErrorReporterProxy as ErrorReporter } from 'n8n-workflow'; @@ -23,7 +22,6 @@ import { Response } from 'express'; import type { Config } from '@/config'; import { UserRequest } from '@/requests'; import type { UserManagementMailer } from '@/UserManagement/email'; -import type { Role } from '@db/entities/Role'; import type { PublicUser, IDatabaseCollections, @@ -36,6 +34,12 @@ import { AuthIdentity } from '@db/entities/AuthIdentity'; import type { PostHogClient } from '@/posthog'; import { userManagementEnabledMiddleware } from '../middlewares/userManagementEnabled'; import { isSamlLicensedAndEnabled } from '../sso/saml/samlHelpers'; +import type { + RoleRepository, + SharedCredentialsRepository, + SharedWorkflowRepository, + UserRepository, +} from '@db/repositories'; @RestController('/users') export class UsersController { @@ -47,13 +51,13 @@ export class UsersController { private internalHooks: IInternalHooksClass; - private userRepository: Repository; + private userRepository: UserRepository; - private roleRepository: Repository; + private roleRepository: RoleRepository; - private sharedCredentialsRepository: Repository; + private sharedCredentialsRepository: SharedCredentialsRepository; - private sharedWorkflowRepository: Repository; + private sharedWorkflowRepository: SharedWorkflowRepository; private activeWorkflowRunner: ActiveWorkflowRunner; @@ -147,7 +151,7 @@ export class UsersController { createUsers[invite.email.toLowerCase()] = null; }); - const role = await this.roleRepository.findOneBy({ scope: 'global', name: 'member' }); + const role = await this.roleRepository.findGlobalMemberRole(); if (!role) { this.logger.error( @@ -396,8 +400,8 @@ export class UsersController { } const [workflowOwnerRole, credentialOwnerRole] = await Promise.all([ - this.roleRepository.findOneBy({ name: 'owner', scope: 'workflow' }), - this.roleRepository.findOneBy({ name: 'owner', scope: 'credential' }), + this.roleRepository.findWorkflowOwnerRole(), + this.roleRepository.findCredentialOwnerRole(), ]); if (transferId) { diff --git a/packages/cli/src/credentials/credentials.service.ts b/packages/cli/src/credentials/credentials.service.ts index 8d5d99455af1f..2eb0f559613c1 100644 --- a/packages/cli/src/credentials/credentials.service.ts +++ b/packages/cli/src/credentials/credentials.service.ts @@ -8,6 +8,7 @@ import type { INodeProperties, } from 'n8n-workflow'; import { deepCopy, LoggerProxy, NodeHelpers } from 'n8n-workflow'; +import { Container } from 'typedi'; import type { FindManyOptions, FindOptionsWhere } from 'typeorm'; import { In } from 'typeorm'; @@ -20,11 +21,10 @@ import { CredentialsEntity } from '@db/entities/CredentialsEntity'; import { SharedCredentials } from '@db/entities/SharedCredentials'; import { validateEntity } from '@/GenericHelpers'; import { ExternalHooks } from '@/ExternalHooks'; - import type { User } from '@db/entities/User'; +import { RoleRepository } from '@db/repositories'; import type { CredentialRequest } from '@/requests'; import { CredentialTypes } from '@/CredentialTypes'; -import { Container } from 'typedi'; export class CredentialsService { static async get( @@ -116,9 +116,7 @@ export class CredentialsService { // This saves us a merge but requires some type casting. These // types are compatible for this case. - const newCredentials = Db.collections.Credentials.create( - rest as ICredentialsDb, - ) as CredentialsEntity; + const newCredentials = Db.collections.Credentials.create(rest as ICredentialsDb); await validateEntity(newCredentials); @@ -140,10 +138,8 @@ export class CredentialsService { } // This saves us a merge but requires some type casting. These - // types are compatiable for this case. - const updateData = Db.collections.Credentials.create( - mergedData as ICredentialsDb, - ) as CredentialsEntity; + // types are compatible for this case. + const updateData = Db.collections.Credentials.create(mergedData as ICredentialsDb); await validateEntity(updateData); @@ -227,10 +223,7 @@ export class CredentialsService { await Container.get(ExternalHooks).run('credentials.create', [encryptedData]); - const role = await Db.collections.Role.findOneByOrFail({ - name: 'owner', - scope: 'credential', - }); + const role = await Container.get(RoleRepository).findCredentialOwnerRoleOrFail(); const result = await Db.transaction(async (transactionManager) => { const savedCredential = await transactionManager.save(newCredential); diff --git a/packages/cli/src/databases/entities/MessageEventBusDestinationEntity.ts b/packages/cli/src/databases/entities/EventDestinations.ts similarity index 100% rename from packages/cli/src/databases/entities/MessageEventBusDestinationEntity.ts rename to packages/cli/src/databases/entities/EventDestinations.ts diff --git a/packages/cli/src/databases/entities/index.ts b/packages/cli/src/databases/entities/index.ts index 51e6ab7555664..9440a96b24e24 100644 --- a/packages/cli/src/databases/entities/index.ts +++ b/packages/cli/src/databases/entities/index.ts @@ -2,7 +2,7 @@ import { AuthIdentity } from './AuthIdentity'; import { AuthProviderSyncHistory } from './AuthProviderSyncHistory'; import { CredentialsEntity } from './CredentialsEntity'; -import { EventDestinations } from './MessageEventBusDestinationEntity'; +import { EventDestinations } from './EventDestinations'; import { ExecutionEntity } from './ExecutionEntity'; import { InstalledNodes } from './InstalledNodes'; import { InstalledPackages } from './InstalledPackages'; diff --git a/packages/cli/src/databases/repositories/authIdentity.repository.ts b/packages/cli/src/databases/repositories/authIdentity.repository.ts new file mode 100644 index 0000000000000..6ec5fe2310b85 --- /dev/null +++ b/packages/cli/src/databases/repositories/authIdentity.repository.ts @@ -0,0 +1,10 @@ +import { Service } from 'typedi'; +import { DataSource, Repository } from 'typeorm'; +import { AuthIdentity } from '../entities/AuthIdentity'; + +@Service() +export class AuthIdentityRepository extends Repository { + constructor(dataSource: DataSource) { + super(AuthIdentity, dataSource.manager); + } +} diff --git a/packages/cli/src/databases/repositories/authProviderSyncHistory.repository.ts b/packages/cli/src/databases/repositories/authProviderSyncHistory.repository.ts new file mode 100644 index 0000000000000..092f4273ee00e --- /dev/null +++ b/packages/cli/src/databases/repositories/authProviderSyncHistory.repository.ts @@ -0,0 +1,10 @@ +import { Service } from 'typedi'; +import { DataSource, Repository } from 'typeorm'; +import { AuthProviderSyncHistory } from '../entities/AuthProviderSyncHistory'; + +@Service() +export class AuthProviderSyncHistoryRepository extends Repository { + constructor(dataSource: DataSource) { + super(AuthProviderSyncHistory, dataSource.manager); + } +} diff --git a/packages/cli/src/databases/repositories/credentials.repository.ts b/packages/cli/src/databases/repositories/credentials.repository.ts new file mode 100644 index 0000000000000..43bc18dfb128b --- /dev/null +++ b/packages/cli/src/databases/repositories/credentials.repository.ts @@ -0,0 +1,10 @@ +import { Service } from 'typedi'; +import { DataSource, Repository } from 'typeorm'; +import { CredentialsEntity } from '../entities/CredentialsEntity'; + +@Service() +export class CredentialsRepository extends Repository { + constructor(dataSource: DataSource) { + super(CredentialsEntity, dataSource.manager); + } +} diff --git a/packages/cli/src/databases/repositories/eventDestinations.repository.ts b/packages/cli/src/databases/repositories/eventDestinations.repository.ts new file mode 100644 index 0000000000000..627a9638f18e5 --- /dev/null +++ b/packages/cli/src/databases/repositories/eventDestinations.repository.ts @@ -0,0 +1,10 @@ +import { Service } from 'typedi'; +import { DataSource, Repository } from 'typeorm'; +import { EventDestinations } from '../entities/EventDestinations'; + +@Service() +export class EventDestinationsRepository extends Repository { + constructor(dataSource: DataSource) { + super(EventDestinations, dataSource.manager); + } +} diff --git a/packages/cli/src/databases/repositories/execution.repository.ts b/packages/cli/src/databases/repositories/execution.repository.ts new file mode 100644 index 0000000000000..bc332ed96609e --- /dev/null +++ b/packages/cli/src/databases/repositories/execution.repository.ts @@ -0,0 +1,10 @@ +import { Service } from 'typedi'; +import { DataSource, Repository } from 'typeorm'; +import { ExecutionEntity } from '../entities/ExecutionEntity'; + +@Service() +export class ExecutionRepository extends Repository { + constructor(dataSource: DataSource) { + super(ExecutionEntity, dataSource.manager); + } +} diff --git a/packages/cli/src/databases/repositories/executionMetadata.repository.ts b/packages/cli/src/databases/repositories/executionMetadata.repository.ts new file mode 100644 index 0000000000000..917ce755c81c0 --- /dev/null +++ b/packages/cli/src/databases/repositories/executionMetadata.repository.ts @@ -0,0 +1,10 @@ +import { Service } from 'typedi'; +import { DataSource, Repository } from 'typeorm'; +import { ExecutionMetadata } from '../entities/ExecutionMetadata'; + +@Service() +export class ExecutionMetadataRepository extends Repository { + constructor(dataSource: DataSource) { + super(ExecutionMetadata, dataSource.manager); + } +} diff --git a/packages/cli/src/databases/repositories/index.ts b/packages/cli/src/databases/repositories/index.ts new file mode 100644 index 0000000000000..e1e7cb02ed17f --- /dev/null +++ b/packages/cli/src/databases/repositories/index.ts @@ -0,0 +1,18 @@ +export { AuthIdentityRepository } from './authIdentity.repository'; +export { AuthProviderSyncHistoryRepository } from './authProviderSyncHistory.repository'; +export { CredentialsRepository } from './credentials.repository'; +export { EventDestinationsRepository } from './eventDestinations.repository'; +export { ExecutionMetadataRepository } from './executionMetadata.repository'; +export { ExecutionRepository } from './execution.repository'; +export { InstalledNodesRepository } from './installedNodes.repository'; +export { InstalledPackagesRepository } from './installedPackages.repository'; +export { RoleRepository } from './role.repository'; +export { SettingsRepository } from './settings.repository'; +export { SharedCredentialsRepository } from './sharedCredentials.repository'; +export { SharedWorkflowRepository } from './sharedWorkflow.repository'; +export { TagRepository } from './tag.repository'; +export { UserRepository } from './user.repository'; +export { WebhookRepository } from './webhook.repository'; +export { WorkflowRepository } from './workflow.repository'; +export { WorkflowStatisticsRepository } from './workflowStatistics.repository'; +export { WorkflowTagMappingRepository } from './workflowTagMapping.repository'; diff --git a/packages/cli/src/databases/repositories/installedNodes.repository.ts b/packages/cli/src/databases/repositories/installedNodes.repository.ts new file mode 100644 index 0000000000000..021535c31fd88 --- /dev/null +++ b/packages/cli/src/databases/repositories/installedNodes.repository.ts @@ -0,0 +1,10 @@ +import { Service } from 'typedi'; +import { DataSource, Repository } from 'typeorm'; +import { InstalledNodes } from '../entities/InstalledNodes'; + +@Service() +export class InstalledNodesRepository extends Repository { + constructor(dataSource: DataSource) { + super(InstalledNodes, dataSource.manager); + } +} diff --git a/packages/cli/src/databases/repositories/installedPackages.repository.ts b/packages/cli/src/databases/repositories/installedPackages.repository.ts new file mode 100644 index 0000000000000..7a0a1fff29277 --- /dev/null +++ b/packages/cli/src/databases/repositories/installedPackages.repository.ts @@ -0,0 +1,10 @@ +import { Service } from 'typedi'; +import { DataSource, Repository } from 'typeorm'; +import { InstalledPackages } from '../entities/InstalledPackages'; + +@Service() +export class InstalledPackagesRepository extends Repository { + constructor(dataSource: DataSource) { + super(InstalledPackages, dataSource.manager); + } +} diff --git a/packages/cli/src/databases/repositories/role.repository.ts b/packages/cli/src/databases/repositories/role.repository.ts new file mode 100644 index 0000000000000..6b2537a26de74 --- /dev/null +++ b/packages/cli/src/databases/repositories/role.repository.ts @@ -0,0 +1,59 @@ +import { Service } from 'typedi'; +import { DataSource, Repository } from 'typeorm'; +import type { RoleNames, RoleScopes } from '../entities/Role'; +import { Role } from '../entities/Role'; + +@Service() +export class RoleRepository extends Repository { + constructor(dataSource: DataSource) { + super(Role, dataSource.manager); + } + + async findGlobalOwnerRole(): Promise { + return this.findRole('global', 'owner'); + } + + async findGlobalOwnerRoleOrFail(): Promise { + return this.findRoleOrFail('global', 'owner'); + } + + async findGlobalMemberRole(): Promise { + return this.findRole('global', 'member'); + } + + async findGlobalMemberRoleOrFail(): Promise { + return this.findRoleOrFail('global', 'member'); + } + + async findWorkflowOwnerRole(): Promise { + return this.findRole('workflow', 'owner'); + } + + async findWorkflowOwnerRoleOrFail(): Promise { + return this.findRoleOrFail('workflow', 'owner'); + } + + async findWorkflowEditorRoleOrFail(): Promise { + return this.findRoleOrFail('workflow', 'editor'); + } + + async findCredentialOwnerRole(): Promise { + return this.findRole('credential', 'owner'); + } + + async findCredentialOwnerRoleOrFail(): Promise { + return this.findRoleOrFail('credential', 'owner'); + } + + async findCredentialUserRole(): Promise { + return this.findRole('credential', 'user'); + } + + async findRole(scope: RoleScopes, name: RoleNames): Promise { + return this.findOne({ where: { scope, name } }); + } + + async findRoleOrFail(scope: RoleScopes, name: RoleNames): Promise { + return this.findOneOrFail({ where: { scope, name } }); + } +} diff --git a/packages/cli/src/databases/repositories/settings.repository.ts b/packages/cli/src/databases/repositories/settings.repository.ts new file mode 100644 index 0000000000000..d0ae091ce69e8 --- /dev/null +++ b/packages/cli/src/databases/repositories/settings.repository.ts @@ -0,0 +1,10 @@ +import { Service } from 'typedi'; +import { DataSource, Repository } from 'typeorm'; +import { Settings } from '../entities/Settings'; + +@Service() +export class SettingsRepository extends Repository { + constructor(dataSource: DataSource) { + super(Settings, dataSource.manager); + } +} diff --git a/packages/cli/src/databases/repositories/sharedCredentials.repository.ts b/packages/cli/src/databases/repositories/sharedCredentials.repository.ts new file mode 100644 index 0000000000000..29e473b885455 --- /dev/null +++ b/packages/cli/src/databases/repositories/sharedCredentials.repository.ts @@ -0,0 +1,10 @@ +import { Service } from 'typedi'; +import { DataSource, Repository } from 'typeorm'; +import { SharedCredentials } from '../entities/SharedCredentials'; + +@Service() +export class SharedCredentialsRepository extends Repository { + constructor(dataSource: DataSource) { + super(SharedCredentials, dataSource.manager); + } +} diff --git a/packages/cli/src/databases/repositories/sharedWorkflow.repository.ts b/packages/cli/src/databases/repositories/sharedWorkflow.repository.ts new file mode 100644 index 0000000000000..e8c21df37985c --- /dev/null +++ b/packages/cli/src/databases/repositories/sharedWorkflow.repository.ts @@ -0,0 +1,10 @@ +import { Service } from 'typedi'; +import { DataSource, Repository } from 'typeorm'; +import { SharedWorkflow } from '../entities/SharedWorkflow'; + +@Service() +export class SharedWorkflowRepository extends Repository { + constructor(dataSource: DataSource) { + super(SharedWorkflow, dataSource.manager); + } +} diff --git a/packages/cli/src/databases/repositories/tag.repository.ts b/packages/cli/src/databases/repositories/tag.repository.ts new file mode 100644 index 0000000000000..3eb848446d996 --- /dev/null +++ b/packages/cli/src/databases/repositories/tag.repository.ts @@ -0,0 +1,10 @@ +import { Service } from 'typedi'; +import { DataSource, Repository } from 'typeorm'; +import { TagEntity } from '../entities/TagEntity'; + +@Service() +export class TagRepository extends Repository { + constructor(dataSource: DataSource) { + super(TagEntity, dataSource.manager); + } +} diff --git a/packages/cli/src/databases/repositories/user.repository.ts b/packages/cli/src/databases/repositories/user.repository.ts new file mode 100644 index 0000000000000..863a797a45d84 --- /dev/null +++ b/packages/cli/src/databases/repositories/user.repository.ts @@ -0,0 +1,10 @@ +import { Service } from 'typedi'; +import { DataSource, Repository } from 'typeorm'; +import { User } from '../entities/User'; + +@Service() +export class UserRepository extends Repository { + constructor(dataSource: DataSource) { + super(User, dataSource.manager); + } +} diff --git a/packages/cli/src/databases/repositories/webhook.repository.ts b/packages/cli/src/databases/repositories/webhook.repository.ts new file mode 100644 index 0000000000000..64bb49a643ecb --- /dev/null +++ b/packages/cli/src/databases/repositories/webhook.repository.ts @@ -0,0 +1,10 @@ +import { Service } from 'typedi'; +import { DataSource, Repository } from 'typeorm'; +import { WebhookEntity } from '../entities/WebhookEntity'; + +@Service() +export class WebhookRepository extends Repository { + constructor(dataSource: DataSource) { + super(WebhookEntity, dataSource.manager); + } +} diff --git a/packages/cli/src/databases/repositories/workflow.repository.ts b/packages/cli/src/databases/repositories/workflow.repository.ts new file mode 100644 index 0000000000000..5085f47be4fda --- /dev/null +++ b/packages/cli/src/databases/repositories/workflow.repository.ts @@ -0,0 +1,10 @@ +import { Service } from 'typedi'; +import { DataSource, Repository } from 'typeorm'; +import { WorkflowEntity } from '../entities/WorkflowEntity'; + +@Service() +export class WorkflowRepository extends Repository { + constructor(dataSource: DataSource) { + super(WorkflowEntity, dataSource.manager); + } +} diff --git a/packages/cli/src/databases/repositories/workflowStatistics.repository.ts b/packages/cli/src/databases/repositories/workflowStatistics.repository.ts new file mode 100644 index 0000000000000..90ebd510d0e74 --- /dev/null +++ b/packages/cli/src/databases/repositories/workflowStatistics.repository.ts @@ -0,0 +1,10 @@ +import { Service } from 'typedi'; +import { DataSource, Repository } from 'typeorm'; +import { WorkflowStatistics } from '../entities/WorkflowStatistics'; + +@Service() +export class WorkflowStatisticsRepository extends Repository { + constructor(dataSource: DataSource) { + super(WorkflowStatistics, dataSource.manager); + } +} diff --git a/packages/cli/src/databases/repositories/workflowTagMapping.repository.ts b/packages/cli/src/databases/repositories/workflowTagMapping.repository.ts new file mode 100644 index 0000000000000..c3a45e862459f --- /dev/null +++ b/packages/cli/src/databases/repositories/workflowTagMapping.repository.ts @@ -0,0 +1,10 @@ +import { Service } from 'typedi'; +import { DataSource, Repository } from 'typeorm'; +import { WorkflowTagMapping } from '../entities/WorkflowTagMapping'; + +@Service() +export class WorkflowTagMappingRepository extends Repository { + constructor(dataSource: DataSource) { + super(WorkflowTagMapping, dataSource.manager); + } +} diff --git a/packages/cli/src/eventbus/MessageEventBusDestination/Helpers.ee.ts b/packages/cli/src/eventbus/MessageEventBusDestination/Helpers.ee.ts index 301c38c213624..f69c417516ead 100644 --- a/packages/cli/src/eventbus/MessageEventBusDestination/Helpers.ee.ts +++ b/packages/cli/src/eventbus/MessageEventBusDestination/Helpers.ee.ts @@ -1,4 +1,4 @@ -import type { EventDestinations } from '@/databases/entities/MessageEventBusDestinationEntity'; +import type { EventDestinations } from '@db/entities/EventDestinations'; import { promClient } from '@/metrics'; import { EventMessageTypeNames, diff --git a/packages/cli/src/executions/executions.service.ts b/packages/cli/src/executions/executions.service.ts index 1d308ddb9d3d7..41372335f04e2 100644 --- a/packages/cli/src/executions/executions.service.ts +++ b/packages/cli/src/executions/executions.service.ts @@ -39,7 +39,7 @@ import { getStatusUsingPreviousExecutionStatusMethod, isAdvancedExecutionFiltersEnabled, } from './executionHelpers'; -import { ExecutionMetadata } from '@/databases/entities/ExecutionMetadata'; +import { ExecutionMetadata } from '@db/entities/ExecutionMetadata'; import { DateUtils } from 'typeorm/util/DateUtils'; interface IGetExecutionsQueryFilter { diff --git a/packages/cli/src/middlewares/auth.ts b/packages/cli/src/middlewares/auth.ts index 1299f474084b6..bd0f580c8a73c 100644 --- a/packages/cli/src/middlewares/auth.ts +++ b/packages/cli/src/middlewares/auth.ts @@ -16,9 +16,8 @@ import { isPostUsersId, isUserManagementEnabled, } from '@/UserManagement/UserManagementHelper'; -import type { Repository } from 'typeorm'; -import type { User } from '@db/entities/User'; import { SamlUrls } from '@/sso/saml/constants'; +import type { UserRepository } from '@db/repositories'; const jwtFromRequest = (req: Request) => { // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access @@ -74,7 +73,7 @@ export const setupAuthMiddlewares = ( app: Application, ignoredEndpoints: Readonly, restEndpoint: string, - userRepository: Repository, + userRepository: UserRepository, ) => { // needed for testing; not adding overhead since it directly returns if req.cookies exists app.use(cookieParser()); diff --git a/packages/cli/src/role/role.service.ts b/packages/cli/src/role/role.service.ts index 6ba367e1b1565..d0d52cba35f05 100644 --- a/packages/cli/src/role/role.service.ts +++ b/packages/cli/src/role/role.service.ts @@ -1,18 +1,18 @@ +import { Service } from 'typedi'; import type { EntityManager, FindOptionsWhere } from 'typeorm'; -import * as Db from '@/Db'; import { Role } from '@db/entities/Role'; +import { SharedWorkflowRepository } from '@db/repositories'; +@Service() export class RoleService { - static async get(role: FindOptionsWhere): Promise { - return Db.collections.Role.findOneBy(role); - } + constructor(private sharedWorkflowRepository: SharedWorkflowRepository) {} static async trxGet(transaction: EntityManager, role: FindOptionsWhere) { return transaction.findOneBy(Role, role); } - static async getUserRoleForWorkflow(userId: string, workflowId: string) { - const shared = await Db.collections.SharedWorkflow.findOne({ + async getUserRoleForWorkflow(userId: string, workflowId: string) { + const shared = await this.sharedWorkflowRepository.findOne({ where: { workflowId, userId }, relations: ['role'], }); diff --git a/packages/cli/src/sso/saml/saml.service.ee.ts b/packages/cli/src/sso/saml/saml.service.ee.ts index 436aae8e03404..7607a71ee3f40 100644 --- a/packages/cli/src/sso/saml/saml.service.ee.ts +++ b/packages/cli/src/sso/saml/saml.service.ee.ts @@ -1,7 +1,7 @@ import type express from 'express'; import { Service } from 'typedi'; import * as Db from '@/Db'; -import type { User } from '@/databases/entities/User'; +import type { User } from '@db/entities/User'; import { jsonParse, LoggerProxy } from 'n8n-workflow'; import { AuthError, BadRequestError } from '@/ResponseHelper'; import { getServiceProviderInstance } from './serviceProvider.ee'; @@ -20,7 +20,7 @@ import { setSamlLoginLabel, updateUserFromSamlAttributes, } from './samlHelpers'; -import type { Settings } from '@/databases/entities/Settings'; +import type { Settings } from '@db/entities/Settings'; import axios from 'axios'; import https from 'https'; import type { SamlLoginBinding } from './types'; diff --git a/packages/cli/src/sso/saml/samlHelpers.ts b/packages/cli/src/sso/saml/samlHelpers.ts index e401d0457a30d..55efbedd6c839 100644 --- a/packages/cli/src/sso/saml/samlHelpers.ts +++ b/packages/cli/src/sso/saml/samlHelpers.ts @@ -3,6 +3,7 @@ import config from '@/config'; import * as Db from '@/Db'; import { AuthIdentity } from '@db/entities/AuthIdentity'; import { User } from '@db/entities/User'; +import { RoleRepository } from '@db/repositories'; import { License } from '@/License'; import { AuthError, InternalServerError } from '@/ResponseHelper'; import { hashPassword, isUserManagementEnabled } from '@/UserManagement/UserManagementHelper'; @@ -98,9 +99,7 @@ export async function createUserFromSamlAttributes(attributes: SamlUserAttribute user.email = attributes.email; user.firstName = attributes.firstName; user.lastName = attributes.lastName; - user.globalRole = await Db.collections.Role.findOneOrFail({ - where: { name: 'member', scope: 'global' }, - }); + user.globalRole = await Container.get(RoleRepository).findGlobalMemberRoleOrFail(); // generates a password that is not used or known to the user user.password = await hashPassword(generatePassword()); authIdentity.providerId = attributes.userPrincipalName; diff --git a/packages/cli/src/sso/ssoHelpers.ts b/packages/cli/src/sso/ssoHelpers.ts index 70c375b6c44a4..30636c533d6c9 100644 --- a/packages/cli/src/sso/ssoHelpers.ts +++ b/packages/cli/src/sso/ssoHelpers.ts @@ -1,6 +1,6 @@ import config from '@/config'; import * as Db from '@/Db'; -import type { AuthProviderType } from '@/databases/entities/AuthIdentity'; +import type { AuthProviderType } from '@db/entities/AuthIdentity'; /** * Only one authentication method can be active at a time. This function sets the current authentication method diff --git a/packages/cli/src/workflows/workflows.controller.ee.ts b/packages/cli/src/workflows/workflows.controller.ee.ts index 833b319a28eb4..399886cae2ee2 100644 --- a/packages/cli/src/workflows/workflows.controller.ee.ts +++ b/packages/cli/src/workflows/workflows.controller.ee.ts @@ -11,6 +11,7 @@ import { isSharingEnabled, rightDiff } from '@/UserManagement/UserManagementHelp import { EEWorkflowsService as EEWorkflows } from './workflows.services.ee'; import { ExternalHooks } from '@/ExternalHooks'; import { SharedWorkflow } from '@db/entities/SharedWorkflow'; +import { RoleRepository } from '@db/repositories'; import { LoggerProxy } from 'n8n-workflow'; import * as TagHelpers from '@/TagHelpers'; import { EECredentialsService as EECredentials } from '../credentials/credentials.service.ee'; @@ -162,10 +163,7 @@ EEWorkflowController.post( await Db.transaction(async (transactionManager) => { savedWorkflow = await transactionManager.save(newWorkflow); - const role = await Db.collections.Role.findOneByOrFail({ - name: 'owner', - scope: 'workflow', - }); + const role = await Container.get(RoleRepository).findWorkflowOwnerRoleOrFail(); const newSharedWorkflow = new SharedWorkflow(); @@ -206,10 +204,7 @@ EEWorkflowController.get( ResponseHelper.send(async (req: WorkflowRequest.GetAll) => { const [workflows, workflowOwnerRole] = await Promise.all([ EEWorkflows.getMany(req.user, req.query.filter), - Db.collections.Role.findOneOrFail({ - select: ['id'], - where: { name: 'owner', scope: 'workflow' }, - }), + Container.get(RoleRepository).findWorkflowOwnerRoleOrFail(), ]); return workflows.map((workflow) => { diff --git a/packages/cli/src/workflows/workflows.controller.ts b/packages/cli/src/workflows/workflows.controller.ts index 979f4b852fc96..99a46c78af009 100644 --- a/packages/cli/src/workflows/workflows.controller.ts +++ b/packages/cli/src/workflows/workflows.controller.ts @@ -14,6 +14,7 @@ import config from '@/config'; import * as TagHelpers from '@/TagHelpers'; import { SharedWorkflow } from '@db/entities/SharedWorkflow'; import { WorkflowEntity } from '@db/entities/WorkflowEntity'; +import { RoleRepository } from '@db/repositories'; import { validateEntity } from '@/GenericHelpers'; import { ExternalHooks } from '@/ExternalHooks'; import { getLogger } from '@/Logger'; @@ -80,10 +81,7 @@ workflowsController.post( await Db.transaction(async (transactionManager) => { savedWorkflow = await transactionManager.save(newWorkflow); - const role = await Db.collections.Role.findOneByOrFail({ - name: 'owner', - scope: 'workflow', - }); + const role = await Container.get(RoleRepository).findWorkflowOwnerRoleOrFail(); const newSharedWorkflow = new SharedWorkflow(); diff --git a/packages/cli/test/integration/eventbus.test.ts b/packages/cli/test/integration/eventbus.test.ts index 52a5642bf9f11..a5b712321747d 100644 --- a/packages/cli/test/integration/eventbus.test.ts +++ b/packages/cli/test/integration/eventbus.test.ts @@ -3,7 +3,7 @@ import config from '@/config'; import axios from 'axios'; import syslog from 'syslog-client'; import { v4 as uuid } from 'uuid'; -import Container from 'typedi'; +import { Container } from 'typedi'; import type { SuperAgentTest } from 'supertest'; import * as utils from './shared/utils'; import * as testDb from './shared/testDb'; @@ -112,7 +112,6 @@ beforeAll(async () => { afterAll(async () => { jest.mock('@/eventbus/MessageEventBus/MessageEventBus'); - Container.reset(); await testDb.terminate(); await eventBus.close(); }); diff --git a/packages/cli/test/integration/ldap/ldap.api.test.ts b/packages/cli/test/integration/ldap/ldap.api.test.ts index 3814cbe4c1310..2bb766be937b9 100644 --- a/packages/cli/test/integration/ldap/ldap.api.test.ts +++ b/packages/cli/test/integration/ldap/ldap.api.test.ts @@ -1,7 +1,7 @@ import express from 'express'; import type { Entry as LdapUser } from 'ldapts'; import { Not } from 'typeorm'; -import Container from 'typedi'; +import { Container } from 'typedi'; import { jsonParse } from 'n8n-workflow'; import config from '@/config'; import * as Db from '@/Db'; @@ -83,7 +83,6 @@ beforeEach(async () => { }); afterAll(async () => { - Container.reset(); await testDb.terminate(); }); diff --git a/packages/cli/test/integration/saml/saml.api.test.ts b/packages/cli/test/integration/saml/saml.api.test.ts index 40f19995c1305..fa52693b1b325 100644 --- a/packages/cli/test/integration/saml/saml.api.test.ts +++ b/packages/cli/test/integration/saml/saml.api.test.ts @@ -1,4 +1,4 @@ -import Container from 'typedi'; +import { Container } from 'typedi'; import type { SuperAgentTest } from 'supertest'; import type { User } from '@db/entities/User'; import { setSamlLoginEnabled } from '@/sso/saml/samlHelpers'; @@ -24,7 +24,6 @@ beforeAll(async () => { }); afterAll(async () => { - Container.reset(); await testDb.terminate(); }); diff --git a/packages/cli/test/integration/shared/random.ts b/packages/cli/test/integration/shared/random.ts index 866ef8492ea70..876670720d47f 100644 --- a/packages/cli/test/integration/shared/random.ts +++ b/packages/cli/test/integration/shared/random.ts @@ -18,6 +18,8 @@ export function randomApiKey() { const chooseRandomly = (array: T[]) => array[Math.floor(Math.random() * array.length)]; +export const randomInteger = (max = 1000) => Math.floor(Math.random() * max); + export const randomDigit = () => Math.floor(Math.random() * 10); export const randomPositiveDigit = (): number => { diff --git a/packages/cli/test/integration/shared/testDb.ts b/packages/cli/test/integration/shared/testDb.ts index f6072dc75b14b..0f60f6f52a9a2 100644 --- a/packages/cli/test/integration/shared/testDb.ts +++ b/packages/cli/test/integration/shared/testDb.ts @@ -1,9 +1,6 @@ import { UserSettings } from 'n8n-core'; -import { - DataSource as Connection, - DataSourceOptions as ConnectionOptions, - Repository, -} from 'typeorm'; +import { DataSource as Connection, DataSourceOptions as ConnectionOptions } from 'typeorm'; +import { Container } from 'typedi'; import config from '@/config'; import * as Db from '@/Db'; @@ -14,7 +11,7 @@ import { mysqlMigrations } from '@db/migrations/mysqldb'; import { postgresMigrations } from '@db/migrations/postgresdb'; import { sqliteMigrations } from '@db/migrations/sqlite'; import { hashPassword } from '@/UserManagement/UserManagementHelper'; -import { AuthIdentity } from '@/databases/entities/AuthIdentity'; +import { AuthIdentity } from '@db/entities/AuthIdentity'; import type { ExecutionEntity } from '@db/entities/ExecutionEntity'; import { InstalledNodes } from '@db/entities/InstalledNodes'; import { InstalledPackages } from '@db/entities/InstalledPackages'; @@ -22,6 +19,7 @@ import type { Role } from '@db/entities/Role'; import type { TagEntity } from '@db/entities/TagEntity'; import type { User } from '@db/entities/User'; import type { WorkflowEntity } from '@db/entities/WorkflowEntity'; +import { RoleRepository } from '@db/repositories'; import { ICredentialsDb } from '@/Interfaces'; import { DB_INITIALIZATION_TIMEOUT } from './constants'; @@ -104,8 +102,7 @@ export async function terminate() { */ export async function truncate(collections: CollectionName[]) { for (const collection of collections) { - const repository: Repository = Db.collections[collection]; - await repository.delete({}); + await Db.collections[collection].clear(); } } @@ -142,7 +139,7 @@ export async function saveCredential( } export async function shareCredentialWithUsers(credential: CredentialsEntity, users: User[]) { - const role = await Db.collections.Role.findOneBy({ scope: 'credential', name: 'user' }); + const role = await Container.get(RoleRepository).findCredentialUserRole(); const newSharedCredentials = users.map((user) => Db.collections.SharedCredentials.create({ userId: user.id, @@ -266,38 +263,23 @@ export async function addApiKey(user: User): Promise { // ---------------------------------- export async function getGlobalOwnerRole() { - return Db.collections.Role.findOneByOrFail({ - name: 'owner', - scope: 'global', - }); + return Container.get(RoleRepository).findGlobalOwnerRoleOrFail(); } export async function getGlobalMemberRole() { - return Db.collections.Role.findOneByOrFail({ - name: 'member', - scope: 'global', - }); + return Container.get(RoleRepository).findGlobalMemberRoleOrFail(); } export async function getWorkflowOwnerRole() { - return Db.collections.Role.findOneByOrFail({ - name: 'owner', - scope: 'workflow', - }); + return Container.get(RoleRepository).findWorkflowOwnerRoleOrFail(); } export async function getWorkflowEditorRole() { - return Db.collections.Role.findOneByOrFail({ - name: 'editor', - scope: 'workflow', - }); + return Container.get(RoleRepository).findWorkflowEditorRoleOrFail(); } export async function getCredentialOwnerRole() { - return Db.collections.Role.findOneByOrFail({ - name: 'owner', - scope: 'credential', - }); + return Container.get(RoleRepository).findCredentialOwnerRoleOrFail(); } export async function getAllRoles() { diff --git a/packages/cli/test/integration/workflows.controller.ee.test.ts b/packages/cli/test/integration/workflows.controller.ee.test.ts index 22e90558e79c0..25809bcec3778 100644 --- a/packages/cli/test/integration/workflows.controller.ee.test.ts +++ b/packages/cli/test/integration/workflows.controller.ee.test.ts @@ -1,4 +1,4 @@ -import Container from 'typedi'; +import { Container } from 'typedi'; import type { SuperAgentTest } from 'supertest'; import { v4 as uuid } from 'uuid'; import type { INode } from 'n8n-workflow'; @@ -51,7 +51,6 @@ beforeEach(async () => { }); afterAll(async () => { - Container.reset(); await testDb.terminate(); }); diff --git a/packages/cli/test/unit/ActiveWorkflowRunner.test.ts b/packages/cli/test/unit/ActiveWorkflowRunner.test.ts index e3290446e0f4f..bb4927d1762b1 100644 --- a/packages/cli/test/unit/ActiveWorkflowRunner.test.ts +++ b/packages/cli/test/unit/ActiveWorkflowRunner.test.ts @@ -11,10 +11,10 @@ import { import { ActiveWorkflowRunner } from '@/ActiveWorkflowRunner'; import * as Db from '@/Db'; -import { WorkflowEntity } from '@/databases/entities/WorkflowEntity'; -import { SharedWorkflow } from '@/databases/entities/SharedWorkflow'; -import { Role } from '@/databases/entities/Role'; -import { User } from '@/databases/entities/User'; +import { WorkflowEntity } from '@db/entities/WorkflowEntity'; +import { SharedWorkflow } from '@db/entities/SharedWorkflow'; +import { Role } from '@db/entities/Role'; +import { User } from '@db/entities/User'; import { getLogger } from '@/Logger'; import { randomEmail, randomName } from '../integration/shared/random'; import * as Helpers from './Helpers'; diff --git a/packages/cli/test/unit/Events.test.ts b/packages/cli/test/unit/Events.test.ts index 22891422ff58d..d68aa19216389 100644 --- a/packages/cli/test/unit/Events.test.ts +++ b/packages/cli/test/unit/Events.test.ts @@ -1,11 +1,12 @@ import { IRun, LoggerProxy, WorkflowExecuteMode } from 'n8n-workflow'; -import { QueryFailedError, Repository } from 'typeorm'; +import { QueryFailedError } from 'typeorm'; import { mock } from 'jest-mock-extended'; import config from '@/config'; import * as Db from '@/Db'; import { User } from '@db/entities/User'; import { WorkflowStatistics } from '@db/entities/WorkflowStatistics'; +import { WorkflowStatisticsRepository } from '@db/repositories'; import { nodeFetchedData, workflowExecutionCompleted } from '@/events/WorkflowStatistics'; import * as UserManagementHelper from '@/UserManagement/UserManagementHelper'; import { getLogger } from '@/Logger'; @@ -14,7 +15,6 @@ import { InternalHooks } from '@/InternalHooks'; import { mockInstance } from '../integration/shared/utils'; import { UserService } from '@/user/user.service'; -type WorkflowStatisticsRepository = Repository; jest.mock('@/Db', () => { return { collections: { diff --git a/packages/cli/test/unit/controllers/me.controller.test.ts b/packages/cli/test/unit/controllers/me.controller.test.ts index 4acfbaa5092ba..20b8fdfb92d5e 100644 --- a/packages/cli/test/unit/controllers/me.controller.test.ts +++ b/packages/cli/test/unit/controllers/me.controller.test.ts @@ -5,6 +5,7 @@ import { mock, anyObject, captor } from 'jest-mock-extended'; import type { ILogger } from 'n8n-workflow'; import type { IExternalHooksClass, IInternalHooksClass } from '@/Interfaces'; import type { User } from '@db/entities/User'; +import { UserRepository } from '@db/repositories'; import { MeController } from '@/controllers'; import { AUTH_COOKIE_NAME } from '@/constants'; import { BadRequestError } from '@/ResponseHelper'; @@ -15,7 +16,7 @@ describe('MeController', () => { const logger = mock(); const externalHooks = mock(); const internalHooks = mock(); - const userRepository = mock>(); + const userRepository = mock(); const controller = new MeController({ logger, externalHooks, diff --git a/packages/cli/test/unit/controllers/owner.controller.test.ts b/packages/cli/test/unit/controllers/owner.controller.test.ts index a460dc8bc5330..0ef8036e07b8e 100644 --- a/packages/cli/test/unit/controllers/owner.controller.test.ts +++ b/packages/cli/test/unit/controllers/owner.controller.test.ts @@ -1,12 +1,15 @@ -import type { Repository } from 'typeorm'; import type { CookieOptions, Response } from 'express'; import { anyObject, captor, mock } from 'jest-mock-extended'; import type { ILogger } from 'n8n-workflow'; import jwt from 'jsonwebtoken'; -import type { ICredentialsDb, IInternalHooksClass } from '@/Interfaces'; +import type { IInternalHooksClass } from '@/Interfaces'; import type { User } from '@db/entities/User'; -import type { Settings } from '@db/entities/Settings'; -import type { WorkflowEntity } from '@db/entities/WorkflowEntity'; +import type { + CredentialsRepository, + SettingsRepository, + UserRepository, + WorkflowRepository, +} from '@db/repositories'; import type { Config } from '@/config'; import { BadRequestError } from '@/ResponseHelper'; import type { OwnerRequest } from '@/requests'; @@ -18,10 +21,10 @@ describe('OwnerController', () => { const config = mock(); const logger = mock(); const internalHooks = mock(); - const userRepository = mock>(); - const settingsRepository = mock>(); - const credentialsRepository = mock>(); - const workflowsRepository = mock>(); + const userRepository = mock(); + const settingsRepository = mock(); + const credentialsRepository = mock(); + const workflowsRepository = mock(); const controller = new OwnerController({ config, logger, diff --git a/packages/cli/test/unit/repositories/role.repository.test.ts b/packages/cli/test/unit/repositories/role.repository.test.ts new file mode 100644 index 0000000000000..ad37f797fc0c7 --- /dev/null +++ b/packages/cli/test/unit/repositories/role.repository.test.ts @@ -0,0 +1,47 @@ +import { Container } from 'typedi'; +import { DataSource, EntityManager } from 'typeorm'; +import { mock } from 'jest-mock-extended'; +import { Role, RoleNames, RoleScopes } from '@db/entities/Role'; +import { RoleRepository } from '@db/repositories/role.repository'; +import { mockInstance } from '../../integration/shared/utils'; +import { randomInteger } from '../../integration/shared/random'; + +describe('RoleRepository', () => { + const entityManager = mockInstance(EntityManager); + const dataSource = mockInstance(DataSource, { manager: entityManager }); + dataSource.getMetadata.mockReturnValue(mock()); + Object.assign(entityManager, { connection: dataSource }); + const roleRepository = Container.get(RoleRepository); + + describe('findRole', () => { + test('should return the role when present', async () => { + entityManager.findOne.mockResolvedValueOnce(createRole('global', 'owner')); + const role = await roleRepository.findRole('global', 'owner'); + expect(role?.name).toEqual('owner'); + expect(role?.scope).toEqual('global'); + }); + + test('should return null otherwise', async () => { + entityManager.findOne.mockResolvedValueOnce(null); + const role = await roleRepository.findRole('global', 'owner'); + expect(role).toEqual(null); + }); + }); + + describe('findRoleOrFail', () => { + test('should return the role when present', async () => { + entityManager.findOneOrFail.mockResolvedValueOnce(createRole('global', 'owner')); + const role = await roleRepository.findRoleOrFail('global', 'owner'); + expect(role?.name).toEqual('owner'); + expect(role?.scope).toEqual('global'); + }); + + test('should throw otherwise', async () => { + entityManager.findOneOrFail.mockRejectedValueOnce(new Error()); + expect(() => roleRepository.findRoleOrFail('global', 'owner')).rejects.toThrow(); + }); + }); + + const createRole = (scope: RoleScopes, name: RoleNames) => + Object.assign(new Role(), { name, scope, id: `${randomInteger()}` }); +}); diff --git a/packages/cli/test/unit/services/role.service.test.ts b/packages/cli/test/unit/services/role.service.test.ts new file mode 100644 index 0000000000000..483be386ae0a6 --- /dev/null +++ b/packages/cli/test/unit/services/role.service.test.ts @@ -0,0 +1,28 @@ +import { SharedWorkflowRepository } from '@db/repositories/sharedWorkflow.repository'; +import { Role } from '@db/entities/Role'; +import { SharedWorkflow } from '@db/entities/SharedWorkflow'; +import { RoleService } from '@/role/role.service'; +import { mockInstance } from '../../integration/shared/utils'; + +describe('RoleService', () => { + const sharedWorkflowRepository = mockInstance(SharedWorkflowRepository); + const roleService = new RoleService(sharedWorkflowRepository); + + const userId = '1'; + const workflowId = '42'; + + describe('getUserRoleForWorkflow', () => { + test('should return the role if a shared workflow is found', async () => { + const sharedWorkflow = Object.assign(new SharedWorkflow(), { role: new Role() }); + sharedWorkflowRepository.findOne.mockResolvedValueOnce(sharedWorkflow); + const role = await roleService.getUserRoleForWorkflow(userId, workflowId); + expect(role).toBe(sharedWorkflow.role); + }); + + test('should return undefined if no shared workflow is found', async () => { + sharedWorkflowRepository.findOne.mockResolvedValueOnce(null); + const role = await roleService.getUserRoleForWorkflow(userId, workflowId); + expect(role).toBeUndefined(); + }); + }); +});