Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(core): Use injectable classes for db repositories (part-1) (no-changelog) #5953

Merged
merged 7 commits into from
Apr 12, 2023
77 changes: 43 additions & 34 deletions packages/cli/src/Db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -25,24 +19,38 @@ 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!;

export async function transaction<T>(fn: (entityManager: EntityManager) => Promise<T>): Promise<T> {
return connection.transaction(fn);
}

export function linkRepository<Entity extends ObjectLiteral>(
entityClass: EntityTarget<Entity>,
): Repository<Entity> {
return connection.getRepository(entityClass);
}

export function getConnectionOptions(dbType: DatabaseType): ConnectionOptions {
switch (dbType) {
case 'postgresdb':
Expand Down Expand Up @@ -114,6 +122,7 @@ export async function init(
});

connection = new Connection(connectionOptions);
Container.set(Connection, connection);
await connection.initialize();

if (dbType === 'postgresdb') {
Expand Down Expand Up @@ -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;

Expand Down
71 changes: 40 additions & 31 deletions packages/cli/src/Interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -76,24 +85,24 @@ export interface ICredentialsOverwrite {
}

export interface IDatabaseCollections {
AuthIdentity: Repository<AuthIdentity>;
AuthProviderSyncHistory: Repository<AuthProviderSyncHistory>;
Credentials: Repository<ICredentialsDb>;
Execution: Repository<IExecutionFlattedDb>;
Workflow: Repository<WorkflowEntity>;
Webhook: Repository<WebhookEntity>;
Tag: Repository<TagEntity>;
WorkflowTagMapping: Repository<WorkflowTagMapping>;
Role: Repository<Role>;
User: Repository<User>;
SharedCredentials: Repository<SharedCredentials>;
SharedWorkflow: Repository<SharedWorkflow>;
Settings: Repository<Settings>;
InstalledPackages: Repository<InstalledPackages>;
InstalledNodes: Repository<InstalledNodes>;
WorkflowStatistics: Repository<WorkflowStatistics>;
EventDestinations: Repository<EventDestinations>;
ExecutionMetadata: Repository<ExecutionMetadata>;
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;
}

// ----------------------------------
Expand Down
11 changes: 7 additions & 4 deletions packages/cli/src/InternalHooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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';
}
Expand Down Expand Up @@ -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';
}
Expand Down
3 changes: 2 additions & 1 deletion packages/cli/src/Ldap/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -93,7 +94,7 @@ export const randomPassword = (): string => {
* Return the user role to be assigned to LDAP users
*/
export const getLdapUserRole = async (): Promise<Role> => {
return Db.collections.Role.findOneByOrFail({ scope: 'global', name: 'member' });
return Container.get(RoleRepository).findGlobalMemberRoleOrFail();
};

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -58,10 +59,7 @@ export async function saveCredential(
user: User,
encryptedData: ICredentialsDb,
): Promise<CredentialsEntity> {
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]);

Expand Down
8 changes: 3 additions & 5 deletions packages/cli/src/PublicApi/v1/handlers/users/users.service.ts
Original file line number Diff line number Diff line change
@@ -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';

Expand All @@ -7,8 +8,5 @@ export function isInstanceOwner(user: User): boolean {
}

export async function getWorkflowOwnerRole(): Promise<Role> {
return Db.collections.Role.findOneByOrFail({
name: 'owner',
scope: 'workflow',
});
return Container.get(RoleRepository).findWorkflowOwnerRoleOrFail();
}
16 changes: 6 additions & 10 deletions packages/cli/src/UserManagement/UserManagementHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,23 @@
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';
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<User> {
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 },
Expand Down Expand Up @@ -61,13 +61,9 @@ export function isSharingEnabled(): boolean {
}

export async function getRoleId(scope: Role['scope'], name: Role['name']): Promise<Role['id']> {
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<User> {
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/WaitTracker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ export class WaitTracker {
executionId,
ResponseHelper.flattenExecutionData({
...fullExecutionData,
}),
}) as IExecutionFlattedDb,
);

return {
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/WorkflowExecuteAdditionalData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');

Expand Down
11 changes: 3 additions & 8 deletions packages/cli/src/WorkflowHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -389,17 +390,11 @@ export async function isBelowOnboardingThreshold(user: User): Promise<boolean> {
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));
Expand Down
Loading