refactor: Upgrade typeorm to 0.3.x (#5151)

This commit is contained in:
कारतोफ्फेलस्क्रिप्ट™ 2023-01-13 18:12:22 +01:00 committed by GitHub
parent 6608e69457
commit 0a5ab560b1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
85 changed files with 579 additions and 636 deletions

View file

@ -161,7 +161,7 @@
"lodash.uniqby": "^4.7.0", "lodash.uniqby": "^4.7.0",
"lodash.unset": "^4.5.2", "lodash.unset": "^4.5.2",
"luxon": "^3.1.0", "luxon": "^3.1.0",
"mysql2": "~2.3.0", "mysql2": "~2.3.3",
"n8n-core": "~0.151.0", "n8n-core": "~0.151.0",
"n8n-editor-ui": "~0.177.0", "n8n-editor-ui": "~0.177.0",
"n8n-nodes-base": "~0.209.0", "n8n-nodes-base": "~0.209.0",
@ -175,7 +175,7 @@
"passport": "^0.6.0", "passport": "^0.6.0",
"passport-cookie": "^1.0.9", "passport-cookie": "^1.0.9",
"passport-jwt": "^4.0.0", "passport-jwt": "^4.0.0",
"pg": "^8.3.0", "pg": "^8.8.0",
"picocolors": "^1.0.0", "picocolors": "^1.0.0",
"posthog-node": "^2.2.2", "posthog-node": "^2.2.2",
"prom-client": "^13.1.0", "prom-client": "^13.1.0",
@ -184,12 +184,12 @@
"semver": "^7.3.8", "semver": "^7.3.8",
"shelljs": "^0.8.5", "shelljs": "^0.8.5",
"source-map-support": "^0.5.21", "source-map-support": "^0.5.21",
"sqlite3": "^5.1.2", "sqlite3": "^5.1.4",
"sse-channel": "^4.0.0", "sse-channel": "^4.0.0",
"swagger-ui-express": "^4.3.0", "swagger-ui-express": "^4.3.0",
"syslog-client": "^1.1.1", "syslog-client": "^1.1.1",
"tslib": "1.14.1", "tslib": "1.14.1",
"typeorm": "0.2.45", "typeorm": "0.3.11",
"uuid": "^8.3.2", "uuid": "^8.3.2",
"validator": "13.7.0", "validator": "13.7.0",
"winston": "^3.3.3", "winston": "^3.3.3",

View file

@ -6,7 +6,6 @@ import bodyParser from 'body-parser';
import bodyParserXml from 'body-parser-xml'; import bodyParserXml from 'body-parser-xml';
import compression from 'compression'; import compression from 'compression';
import parseUrl from 'parseurl'; import parseUrl from 'parseurl';
import { getConnectionManager } from 'typeorm';
import type { RedisOptions } from 'ioredis'; import type { RedisOptions } from 'ioredis';
import { import {
@ -162,10 +161,10 @@ export abstract class AbstractServer {
this.app.get('/healthz', async (req, res) => { this.app.get('/healthz', async (req, res) => {
Logger.debug('Health check started!'); Logger.debug('Health check started!');
const connection = getConnectionManager().get(); const connection = Db.getConnection();
try { try {
if (!connection.isConnected) { if (!connection.isInitialized) {
// Connection is not active // Connection is not active
throw new ServiceUnavailableError('No active database connection!'); throw new ServiceUnavailableError('No active database connection!');
} }

View file

@ -200,18 +200,18 @@ export class ActiveWorkflowRunner {
path = path.slice(0, -1); path = path.slice(0, -1);
} }
let webhook = await Db.collections.Webhook.findOne({ let webhook = await Db.collections.Webhook.findOneBy({
webhookPath: path, webhookPath: path,
method: httpMethod, method: httpMethod,
}); });
let webhookId: string | undefined; let webhookId: string | undefined;
// check if path is dynamic // check if path is dynamic
if (webhook === undefined) { if (webhook === null) {
// check if a dynamic webhook path exists // check if a dynamic webhook path exists
const pathElements = path.split('/'); const pathElements = path.split('/');
webhookId = pathElements.shift(); webhookId = pathElements.shift();
const dynamicWebhooks = await Db.collections.Webhook.find({ const dynamicWebhooks = await Db.collections.Webhook.findBy({
webhookId, webhookId,
method: httpMethod, method: httpMethod,
pathLength: pathElements.length, pathLength: pathElements.length,
@ -243,7 +243,7 @@ export class ActiveWorkflowRunner {
webhook = dynamicWebhook; webhook = dynamicWebhook;
} }
}); });
if (webhook === undefined) { if (webhook === null) {
throw new ResponseHelper.NotFoundError( throw new ResponseHelper.NotFoundError(
`The requested webhook "${httpMethod} ${path}" is not registered.`, `The requested webhook "${httpMethod} ${path}" is not registered.`,
WEBHOOK_PROD_UNREGISTERED_HINT, WEBHOOK_PROD_UNREGISTERED_HINT,
@ -263,10 +263,11 @@ export class ActiveWorkflowRunner {
}); });
} }
const workflowData = await Db.collections.Workflow.findOne(webhook.workflowId, { const workflowData = await Db.collections.Workflow.findOne({
where: { id: webhook.workflowId },
relations: ['shared', 'shared.user', 'shared.user.globalRole'], relations: ['shared', 'shared.user', 'shared.user.globalRole'],
}); });
if (workflowData === undefined) { if (workflowData === null) {
throw new ResponseHelper.NotFoundError( throw new ResponseHelper.NotFoundError(
`Could not find workflow with id "${webhook.workflowId}"`, `Could not find workflow with id "${webhook.workflowId}"`,
); );
@ -331,20 +332,19 @@ export class ActiveWorkflowRunner {
/** /**
* Gets all request methods associated with a single webhook * Gets all request methods associated with a single webhook
*
* @param {string} path webhook path
*/ */
async getWebhookMethods(path: string): Promise<string[]> { async getWebhookMethods(path: string): Promise<string[]> {
const webhooks = await Db.collections.Webhook.find({ webhookPath: path }); const webhooks = await Db.collections.Webhook.find({
select: ['method'],
where: { webhookPath: path },
});
// Gather all request methods in string array // Gather all request methods in string array
const webhookMethods: string[] = webhooks.map((webhook) => webhook.method); return webhooks.map((webhook) => webhook.method);
return webhookMethods;
} }
/** /**
* Returns the ids of the currently active workflows * Returns the ids of the currently active workflows
*
*/ */
async getActiveWorkflows(user?: User): Promise<IWorkflowDb[]> { async getActiveWorkflows(user?: User): Promise<IWorkflowDb[]> {
let activeWorkflows: WorkflowEntity[] = []; let activeWorkflows: WorkflowEntity[] = [];
@ -378,7 +378,10 @@ export class ActiveWorkflowRunner {
* @param {string} id The id of the workflow to check * @param {string} id The id of the workflow to check
*/ */
async isActive(id: string): Promise<boolean> { async isActive(id: string): Promise<boolean> {
const workflow = await Db.collections.Workflow.findOne(id); const workflow = await Db.collections.Workflow.findOne({
select: ['active'],
where: { id },
});
return !!workflow?.active; return !!workflow?.active;
} }
@ -434,6 +437,7 @@ export class ActiveWorkflowRunner {
try { try {
// eslint-disable-next-line no-await-in-loop // eslint-disable-next-line no-await-in-loop
// TODO: this should happen in a transaction, that way we don't need to manually remove this in `catch`
await Db.collections.Webhook.insert(webhook); await Db.collections.Webhook.insert(webhook);
const webhookExists = await workflow.runWebhookMethod( const webhookExists = await workflow.runWebhookMethod(
'checkExists', 'checkExists',
@ -503,10 +507,11 @@ export class ActiveWorkflowRunner {
* *
*/ */
async removeWorkflowWebhooks(workflowId: string): Promise<void> { async removeWorkflowWebhooks(workflowId: string): Promise<void> {
const workflowData = await Db.collections.Workflow.findOne(workflowId, { const workflowData = await Db.collections.Workflow.findOne({
where: { id: workflowId },
relations: ['shared', 'shared.user', 'shared.user.globalRole'], relations: ['shared', 'shared.user', 'shared.user.globalRole'],
}); });
if (workflowData === undefined) { if (workflowData === null) {
throw new Error(`Could not find workflow with id "${workflowId}"`); throw new Error(`Could not find workflow with id "${workflowId}"`);
} }
@ -772,7 +777,8 @@ export class ActiveWorkflowRunner {
let workflowInstance: Workflow; let workflowInstance: Workflow;
try { try {
if (workflowData === undefined) { if (workflowData === undefined) {
workflowData = (await Db.collections.Workflow.findOne(workflowId, { workflowData = (await Db.collections.Workflow.findOne({
where: { id: workflowId },
relations: ['shared', 'shared.user', 'shared.user.globalRole'], relations: ['shared', 'shared.user', 'shared.user.globalRole'],
})) as IWorkflowDb; })) as IWorkflowDb;
} }
@ -883,7 +889,7 @@ export class ActiveWorkflowRunner {
/** /**
* Add a workflow to the activation queue. * Add a workflow to the activation queue.
* Meaning it will keep on trying to activate it in regular * Meaning it will keep on trying to activate it in regular
* amounts indefinetly. * amounts indefinitely.
*/ */
addQueuedWorkflowActivation( addQueuedWorkflowActivation(
activationMode: WorkflowActivateMode, activationMode: WorkflowActivateMode,
@ -962,6 +968,7 @@ export class ActiveWorkflowRunner {
* *
* @param {string} workflowId The id of the workflow to deactivate * @param {string} workflowId The id of the workflow to deactivate
*/ */
// TODO: this should happen in a transaction
async remove(workflowId: string): Promise<void> { async remove(workflowId: string): Promise<void> {
if (this.activeWorkflows !== null) { if (this.activeWorkflows !== null) {
// Remove all the webhooks of the workflow // Remove all the webhooks of the workflow

View file

@ -3,10 +3,11 @@ import * as Db from '@/Db';
import { InstalledNodes } from '@db/entities/InstalledNodes'; import { InstalledNodes } from '@db/entities/InstalledNodes';
import { InstalledPackages } from '@db/entities/InstalledPackages'; import { InstalledPackages } from '@db/entities/InstalledPackages';
export async function findInstalledPackage( export async function findInstalledPackage(packageName: string): Promise<InstalledPackages | null> {
packageName: string, return Db.collections.InstalledPackages.findOne({
): Promise<InstalledPackages | undefined> { where: { packageName },
return Db.collections.InstalledPackages.findOne(packageName, { relations: ['installedNodes'] }); relations: ['installedNodes'],
});
} }
export async function isPackageInstalled(packageName: string): Promise<boolean> { export async function isPackageInstalled(packageName: string): Promise<boolean> {

View file

@ -270,7 +270,7 @@ export class CredentialsHelper extends ICredentialsHelper {
relations: ['credentials'], relations: ['credentials'],
where: { credentials: { id: nodeCredential.id, type }, userId }, where: { credentials: { id: nodeCredential.id, type }, userId },
}).then((shared) => shared.credentials) }).then((shared) => shared.credentials)
: await Db.collections.Credentials.findOneOrFail({ id: nodeCredential.id, type }); : await Db.collections.Credentials.findOneByOrFail({ id: nodeCredential.id, type });
if (!credential) { if (!credential) {
throw new Error( throw new Error(
@ -765,8 +765,8 @@ export async function getCredentialForUser(
*/ */
export async function getCredentialWithoutUser( export async function getCredentialWithoutUser(
credentialId: string, credentialId: string,
): Promise<ICredentialsDb | undefined> { ): Promise<ICredentialsDb | null> {
return Db.collections.Credentials.findOne(credentialId); return Db.collections.Credentials.findOneBy({ id: credentialId });
} }
export function createCredentialsFromCredentialsEntity( export function createCredentialsFromCredentialsEntity(

View file

@ -4,12 +4,10 @@
/* eslint-disable no-case-declarations */ /* eslint-disable no-case-declarations */
/* eslint-disable @typescript-eslint/naming-convention */ /* eslint-disable @typescript-eslint/naming-convention */
import { import {
Connection, DataSource as Connection,
ConnectionOptions, DataSourceOptions as ConnectionOptions,
createConnection,
EntityManager, EntityManager,
EntityTarget, EntityTarget,
getRepository,
LoggerOptions, LoggerOptions,
ObjectLiteral, ObjectLiteral,
Repository, Repository,
@ -34,6 +32,8 @@ export const collections = {} as IDatabaseCollections;
export let connection: Connection; export let connection: Connection;
export const getConnection = () => connection!;
export async function transaction<T>(fn: (entityManager: EntityManager) => Promise<T>): Promise<T> { export async function transaction<T>(fn: (entityManager: EntityManager) => Promise<T>): Promise<T> {
return connection.transaction(fn); return connection.transaction(fn);
} }
@ -41,7 +41,7 @@ export async function transaction<T>(fn: (entityManager: EntityManager) => Promi
export function linkRepository<Entity extends ObjectLiteral>( export function linkRepository<Entity extends ObjectLiteral>(
entityClass: EntityTarget<Entity>, entityClass: EntityTarget<Entity>,
): Repository<Entity> { ): Repository<Entity> {
return getRepository(entityClass, connection.name); return connection.getRepository(entityClass);
} }
export async function getConnectionOptions(dbType: DatabaseType): Promise<ConnectionOptions> { export async function getConnectionOptions(dbType: DatabaseType): Promise<ConnectionOptions> {
@ -124,7 +124,8 @@ export async function init(
migrationsTransactionMode: 'each', migrationsTransactionMode: 'each',
}); });
connection = await createConnection(connectionOptions); connection = new Connection(connectionOptions);
await connection.initialize();
if (!testConnectionOptions && dbType === 'sqlite') { if (!testConnectionOptions && dbType === 'sqlite') {
// This specific migration changes database metadata. // This specific migration changes database metadata.
@ -146,8 +147,9 @@ export async function init(
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
if (migrations.length === 0) { if (migrations.length === 0) {
await connection.close(); await connection.destroy();
connection = await createConnection(connectionOptions); connection = new Connection(connectionOptions);
await connection.initialize();
} }
} }

View file

@ -86,7 +86,7 @@ async function createApiRouter(
_scopes: unknown, _scopes: unknown,
schema: OpenAPIV3.ApiKeySecurityScheme, schema: OpenAPIV3.ApiKeySecurityScheme,
): Promise<boolean> => { ): Promise<boolean> => {
const apiKey = req.headers[schema.name.toLowerCase()]; const apiKey = req.headers[schema.name.toLowerCase()] as string;
const user = await Db.collections.User.findOne({ const user = await Db.collections.User.findOne({
where: { apiKey }, where: { apiKey },
relations: ['globalRole'], relations: ['globalRole'],

View file

@ -1,4 +1,3 @@
import type { FindConditions } from 'typeorm';
import { UserSettings, Credentials } from 'n8n-core'; import { UserSettings, Credentials } from 'n8n-core';
import { IDataObject, INodeProperties, INodePropertyOptions } from 'n8n-workflow'; import { IDataObject, INodeProperties, INodePropertyOptions } from 'n8n-workflow';
import * as Db from '@/Db'; import * as Db from '@/Db';
@ -10,17 +9,22 @@ import { ExternalHooks } from '@/ExternalHooks';
import { IDependency, IJsonSchema } from '../../../types'; import { IDependency, IJsonSchema } from '../../../types';
import { CredentialRequest } from '@/requests'; import { CredentialRequest } from '@/requests';
export async function getCredentials(credentialId: string): Promise<ICredentialsDb | undefined> { export async function getCredentials(credentialId: string): Promise<ICredentialsDb | null> {
return Db.collections.Credentials.findOne(credentialId); return Db.collections.Credentials.findOneBy({ id: credentialId });
} }
export async function getSharedCredentials( export async function getSharedCredentials(
userId: string, userId: string,
credentialId: string, credentialId: string,
relations?: string[], relations?: string[],
): Promise<SharedCredentials | undefined> { ): Promise<SharedCredentials | null> {
const where: FindConditions<SharedCredentials> = { userId, credentialsId: credentialId }; return Db.collections.SharedCredentials.findOne({
return Db.collections.SharedCredentials.findOne({ where, relations }); where: {
userId,
credentialsId: credentialId,
},
relations,
});
} }
export async function createCredential( export async function createCredential(
@ -53,7 +57,7 @@ export async function saveCredential(
user: User, user: User,
encryptedData: ICredentialsDb, encryptedData: ICredentialsDb,
): Promise<CredentialsEntity> { ): Promise<CredentialsEntity> {
const role = await Db.collections.Role.findOneOrFail({ const role = await Db.collections.Role.findOneByOrFail({
name: 'owner', name: 'owner',
scope: 'credential', scope: 'credential',
}); });

View file

@ -1,13 +1,12 @@
import { parse } from 'flatted'; import { parse } from 'flatted';
import { In, Not, Raw, LessThan, IsNull, FindOperator } from 'typeorm'; import { In, Not, Raw, LessThan, IsNull, FindOptionsWhere } from 'typeorm';
import * as Db from '@/Db'; import * as Db from '@/Db';
import type { IExecutionFlattedDb, IExecutionResponseApi } from '@/Interfaces'; import type { IExecutionFlattedDb, IExecutionResponseApi } from '@/Interfaces';
import { ExecutionEntity } from '@db/entities/ExecutionEntity'; import type { ExecutionStatus } from '@/PublicApi/types';
import { ExecutionStatus } from '@/PublicApi/types';
function prepareExecutionData( function prepareExecutionData(
execution: IExecutionFlattedDb | undefined, execution: IExecutionFlattedDb | null,
): IExecutionResponseApi | undefined { ): IExecutionResponseApi | undefined {
if (!execution) return undefined; if (!execution) return undefined;
@ -21,11 +20,10 @@ function prepareExecutionData(
} }
function getStatusCondition(status: ExecutionStatus) { function getStatusCondition(status: ExecutionStatus) {
const condition: { const condition: Pick<
finished?: boolean; FindOptionsWhere<IExecutionFlattedDb>,
waitTill?: FindOperator<ExecutionEntity>; 'finished' | 'waitTill' | 'stoppedAt'
stoppedAt?: FindOperator<ExecutionEntity>; > = {};
} = {};
if (status === 'success') { if (status === 'success') {
condition.finished = true; condition.finished = true;
@ -65,12 +63,7 @@ export async function getExecutions(params: {
status?: ExecutionStatus; status?: ExecutionStatus;
excludedExecutionsIds?: string[]; excludedExecutionsIds?: string[];
}): Promise<IExecutionResponseApi[]> { }): Promise<IExecutionResponseApi[]> {
type WhereClause = Record< let where: FindOptionsWhere<IExecutionFlattedDb> = {};
string,
string | boolean | FindOperator<string | Partial<ExecutionEntity>>
>;
let where: WhereClause = {};
if (params.lastId && params.excludedExecutionsIds?.length) { if (params.lastId && params.excludedExecutionsIds?.length) {
where.id = Raw((id) => `${id} < :lastId AND ${id} NOT IN (:...excludedExecutionsIds)`, { where.id = Raw((id) => `${id} < :lastId AND ${id} NOT IN (:...excludedExecutionsIds)`, {

View file

@ -7,7 +7,7 @@ export function isInstanceOwner(user: User): boolean {
} }
export async function getWorkflowOwnerRole(): Promise<Role> { export async function getWorkflowOwnerRole(): Promise<Role> {
return Db.collections.Role.findOneOrFail({ return Db.collections.Role.findOneByOrFail({
name: 'owner', name: 'owner',
scope: 'workflow', scope: 'workflow',
}); });

View file

@ -1,6 +1,6 @@
import express from 'express'; import express from 'express';
import { FindConditions, FindManyOptions, In } from 'typeorm'; import { FindManyOptions, FindOptionsWhere, In } from 'typeorm';
import * as ActiveWorkflowRunner from '@/ActiveWorkflowRunner'; import * as ActiveWorkflowRunner from '@/ActiveWorkflowRunner';
import config from '@/config'; import config from '@/config';
@ -100,7 +100,7 @@ export = {
let workflows: WorkflowEntity[]; let workflows: WorkflowEntity[];
let count: number; let count: number;
const where: FindConditions<WorkflowEntity> = { const where: FindOptionsWhere<WorkflowEntity> = {
...(active !== undefined && { active }), ...(active !== undefined && { active }),
}; };
const query: FindManyOptions<WorkflowEntity> = { const query: FindManyOptions<WorkflowEntity> = {

View file

@ -17,7 +17,7 @@ function insertIf(condition: boolean, elements: string[]): string[] {
export async function getSharedWorkflowIds(user: User): Promise<string[]> { export async function getSharedWorkflowIds(user: User): Promise<string[]> {
const sharedWorkflows = await Db.collections.SharedWorkflow.find({ const sharedWorkflows = await Db.collections.SharedWorkflow.find({
where: { user }, where: { userId: user.id },
}); });
return sharedWorkflows.map(({ workflowId }) => workflowId); return sharedWorkflows.map(({ workflowId }) => workflowId);
@ -26,10 +26,10 @@ export async function getSharedWorkflowIds(user: User): Promise<string[]> {
export async function getSharedWorkflow( export async function getSharedWorkflow(
user: User, user: User,
workflowId?: string | undefined, workflowId?: string | undefined,
): Promise<SharedWorkflow | undefined> { ): Promise<SharedWorkflow | null> {
return Db.collections.SharedWorkflow.findOne({ return Db.collections.SharedWorkflow.findOne({
where: { where: {
...(!isInstanceOwner(user) && { user }), ...(!isInstanceOwner(user) && { userId: user.id }),
...(workflowId && { workflowId }), ...(workflowId && { workflowId }),
}, },
relations: [...insertIf(!config.getEnv('workflowTagsDisabled'), ['workflow.tags']), 'workflow'], relations: [...insertIf(!config.getEnv('workflowTagsDisabled'), ['workflow.tags']), 'workflow'],
@ -45,14 +45,14 @@ export async function getSharedWorkflows(
): Promise<SharedWorkflow[]> { ): Promise<SharedWorkflow[]> {
return Db.collections.SharedWorkflow.find({ return Db.collections.SharedWorkflow.find({
where: { where: {
...(!isInstanceOwner(user) && { user }), ...(!isInstanceOwner(user) && { userId: user.id }),
...(options.workflowIds && { workflowId: In(options.workflowIds) }), ...(options.workflowIds && { workflowId: In(options.workflowIds) }),
}, },
...(options.relations && { relations: options.relations }), ...(options.relations && { relations: options.relations }),
}); });
} }
export async function getWorkflowById(id: string): Promise<WorkflowEntity | undefined> { export async function getWorkflowById(id: string): Promise<WorkflowEntity | null> {
return Db.collections.Workflow.findOne({ return Db.collections.Workflow.findOne({
where: { id }, where: { id },
}); });

View file

@ -1246,9 +1246,9 @@ class Server extends AbstractServer {
await queue.stopJob(job); await queue.stopJob(job);
} }
const executionDb = (await Db.collections.Execution.findOne( const executionDb = (await Db.collections.Execution.findOneBy({
req.params.id, id: req.params.id,
)) as IExecutionFlattedDb; })) as IExecutionFlattedDb;
const fullExecutionData = ResponseHelper.unflattenExecutionData(executionDb); const fullExecutionData = ResponseHelper.unflattenExecutionData(executionDb);
const returnData: IExecutionsStopData = { const returnData: IExecutionsStopData = {
@ -1452,9 +1452,10 @@ export async function start(): Promise<void> {
// Set up event handling // Set up event handling
initEvents(); initEvents();
const workflow = await Db.collections.Workflow!.findOne({ const workflow = await Db.collections.Workflow.findOne({
select: ['createdAt'], select: ['createdAt'],
order: { createdAt: 'ASC' }, order: { createdAt: 'ASC' },
where: {},
}); });
await InternalHooksManager.getInstance().onServerStarted(diagnosticInfo, workflow?.createdAt); await InternalHooksManager.getInstance().onServerStarted(diagnosticInfo, workflow?.createdAt);
} }

View file

@ -1,10 +1,10 @@
/* eslint-disable no-param-reassign */ /* eslint-disable no-param-reassign */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ /* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { EntityManager, getConnection } from 'typeorm'; import type { EntityManager } from 'typeorm';
import { getConnection } from '@/Db';
import { TagEntity } from '@db/entities/TagEntity'; import { TagEntity } from '@db/entities/TagEntity';
import type { ITagToImport, ITagWithCountDb, IWorkflowToImport } from '@/Interfaces';
import { ITagToImport, ITagWithCountDb, IWorkflowToImport } from '@/Interfaces';
// ---------------------------------- // ----------------------------------
// utils // utils

View file

@ -5,11 +5,11 @@ import {
Workflow, Workflow,
WorkflowOperationError, WorkflowOperationError,
} from 'n8n-workflow'; } from 'n8n-workflow';
import { FindConditions, In } from 'typeorm'; import { FindOptionsWhere, In } from 'typeorm';
import * as Db from '@/Db'; import * as Db from '@/Db';
import config from '@/config'; import config from '@/config';
import type { SharedCredentials } from '@db/entities/SharedCredentials'; import type { SharedCredentials } from '@db/entities/SharedCredentials';
import { getRole, getWorkflowOwner, isSharingEnabled } from './UserManagementHelper'; import { getRoleId, getWorkflowOwner, isSharingEnabled } from './UserManagementHelper';
import { WorkflowsService } from '@/workflows/workflows.services'; import { WorkflowsService } from '@/workflows/workflows.services';
import { UserService } from '@/user/user.service'; import { UserService } from '@/user/user.service';
@ -28,7 +28,8 @@ export class PermissionChecker {
// allow if requesting user is instance owner // allow if requesting user is instance owner
const user = await Db.collections.User.findOneOrFail(userId, { const user = await Db.collections.User.findOneOrFail({
where: { id: userId },
relations: ['globalRole'], relations: ['globalRole'],
}); });
@ -47,11 +48,11 @@ export class PermissionChecker {
workflowUserIds = workflowSharings.map((s) => s.userId); workflowUserIds = workflowSharings.map((s) => s.userId);
} }
const credentialsWhere: FindConditions<SharedCredentials> = { userId: In(workflowUserIds) }; const credentialsWhere: FindOptionsWhere<SharedCredentials> = { userId: In(workflowUserIds) };
if (!isSharingEnabled()) { if (!isSharingEnabled()) {
// If credential sharing is not enabled, get only credentials owned by this user // If credential sharing is not enabled, get only credentials owned by this user
credentialsWhere.role = await getRole('credential', 'owner'); credentialsWhere.roleId = await getRoleId('credential', 'owner');
} }
const credentialSharings = await Db.collections.SharedCredentials.find({ const credentialSharings = await Db.collections.SharedCredentials.find({

View file

@ -1,6 +1,5 @@
/* eslint-disable @typescript-eslint/no-unused-vars */ /* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable @typescript-eslint/no-non-null-assertion */ /* eslint-disable @typescript-eslint/no-non-null-assertion */
import { INode, NodeOperationError, Workflow } from 'n8n-workflow';
import { In } from 'typeorm'; import { In } from 'typeorm';
import express from 'express'; import express from 'express';
import { compare, genSaltSync, hash } from 'bcryptjs'; import { compare, genSaltSync, hash } from 'bcryptjs';
@ -21,7 +20,7 @@ export async function getWorkflowOwner(workflowId: string): Promise<User> {
const workflowOwnerRole = await RoleService.get({ name: 'owner', scope: 'workflow' }); const workflowOwnerRole = await RoleService.get({ name: 'owner', scope: 'workflow' });
const sharedWorkflow = await Db.collections.SharedWorkflow.findOneOrFail({ const sharedWorkflow = await Db.collections.SharedWorkflow.findOneOrFail({
where: { workflowId, role: workflowOwnerRole }, where: { workflowId, roleId: workflowOwnerRole?.id ?? undefined },
relations: ['user', 'user.globalRole'], relations: ['user', 'user.globalRole'],
}); });
@ -59,35 +58,26 @@ export function isUserManagementDisabled(): boolean {
); );
} }
async function getInstanceOwnerRole(): Promise<Role> { export async function getRoleId(scope: Role['scope'], name: Role['name']): Promise<Role['id']> {
const ownerRole = await Db.collections.Role.findOneOrFail({
where: {
name: 'owner',
scope: 'global',
},
});
return ownerRole;
}
export async function getInstanceOwner(): Promise<User> {
const ownerRole = await getInstanceOwnerRole();
const owner = await Db.collections.User.findOneOrFail({
relations: ['globalRole'],
where: {
globalRole: ownerRole,
},
});
return owner;
}
export async function getRole(scope: Role['scope'], name: Role['name']): Promise<Role> {
return Db.collections.Role.findOneOrFail({ return Db.collections.Role.findOneOrFail({
select: ['id'],
where: { where: {
name, name,
scope, scope,
}, },
}).then((role) => role.id);
}
export async function getInstanceOwner(): Promise<User> {
const ownerRoleId = await getRoleId('global', 'owner');
const owner = await Db.collections.User.findOneOrFail({
relations: ['globalRole'],
where: {
globalRoleId: ownerRoleId,
},
}); });
return owner;
} }
/** /**
@ -168,7 +158,8 @@ export function addInviteLinktoUser(user: PublicUser, inviterId: string): Public
} }
export async function getUserById(userId: string): Promise<User> { export async function getUserById(userId: string): Promise<User> {
const user = await Db.collections.User.findOneOrFail(userId, { const user = await Db.collections.User.findOneOrFail({
where: { id: userId },
relations: ['globalRole'], relations: ['globalRole'],
}); });
return user; return user;

View file

@ -36,7 +36,8 @@ export function issueJWT(user: User): JwtToken {
} }
export async function resolveJwtContent(jwtPayload: JwtPayload): Promise<User> { export async function resolveJwtContent(jwtPayload: JwtPayload): Promise<User> {
const user = await Db.collections.User.findOne(jwtPayload.id, { const user = await Db.collections.User.findOne({
where: { id: jwtPayload.id },
relations: ['globalRole'], relations: ['globalRole'],
}); });

View file

@ -30,14 +30,12 @@ export function authenticationMethods(this: N8nApp): void {
throw new Error('Password is required to log in'); throw new Error('Password is required to log in');
} }
let user: User | undefined; let user: User | null;
try { try {
user = await Db.collections.User.findOne( user = await Db.collections.User.findOne({
{ email }, where: { email },
{ relations: ['globalRole'],
relations: ['globalRole'], });
},
);
} catch (error) { } catch (error) {
throw new Error('Unable to access database.'); throw new Error('Unable to access database.');
} }
@ -77,7 +75,7 @@ export function authenticationMethods(this: N8nApp): void {
} }
try { try {
user = await Db.collections.User.findOneOrFail({ relations: ['globalRole'] }); user = await Db.collections.User.findOneOrFail({ relations: ['globalRole'], where: {} });
} catch (error) { } catch (error) {
throw new ResponseHelper.InternalServerError( throw new ResponseHelper.InternalServerError(
'No users found in database - did you wipe the users table? Create at least one user.', 'No users found in database - did you wipe the users table? Create at least one user.',

View file

@ -54,12 +54,10 @@ export function addRoutes(this: N8nApp, ignoredEndpoints: string[], restEndpoint
// skip authentication if user management is disabled // skip authentication if user management is disabled
if (isUserManagementDisabled()) { if (isUserManagementDisabled()) {
req.user = await Db.collections.User.findOneOrFail( req.user = await Db.collections.User.findOneOrFail({
{}, relations: ['globalRole'],
{ where: {},
relations: ['globalRole'], });
},
);
return next(); return next();
} }

View file

@ -52,8 +52,9 @@ export function ownerNamespace(this: N8nApp): void {
throw new ResponseHelper.BadRequestError('First and last names are mandatory'); throw new ResponseHelper.BadRequestError('First and last names are mandatory');
} }
let owner = await Db.collections.User.findOne(userId, { let owner = await Db.collections.User.findOne({
relations: ['globalRole'], relations: ['globalRole'],
where: { id: userId },
}); });
if (!owner || (owner.globalRole.scope === 'global' && owner.globalRole.name !== 'owner')) { if (!owner || (owner.globalRole.scope === 'global' && owner.globalRole.name !== 'owner')) {

View file

@ -52,7 +52,7 @@ export function passwordResetNamespace(this: N8nApp): void {
} }
// User should just be able to reset password if one is already present // User should just be able to reset password if one is already present
const user = await Db.collections.User.findOne({ email, password: Not(IsNull()) }); const user = await Db.collections.User.findOneBy({ email, password: Not(IsNull()) });
if (!user?.password) { if (!user?.password) {
Logger.debug( Logger.debug(
@ -133,7 +133,7 @@ export function passwordResetNamespace(this: N8nApp): void {
// Timestamp is saved in seconds // Timestamp is saved in seconds
const currentTimestamp = Math.floor(Date.now() / 1000); const currentTimestamp = Math.floor(Date.now() / 1000);
const user = await Db.collections.User.findOne({ const user = await Db.collections.User.findOneBy({
id, id,
resetPasswordToken, resetPasswordToken,
resetPasswordTokenExpiration: MoreThanOrEqual(currentTimestamp), resetPasswordTokenExpiration: MoreThanOrEqual(currentTimestamp),
@ -184,7 +184,7 @@ export function passwordResetNamespace(this: N8nApp): void {
// Timestamp is saved in seconds // Timestamp is saved in seconds
const currentTimestamp = Math.floor(Date.now() / 1000); const currentTimestamp = Math.floor(Date.now() / 1000);
const user = await Db.collections.User.findOne({ const user = await Db.collections.User.findOneBy({
id: userId, id: userId,
resetPasswordToken, resetPasswordToken,
resetPasswordTokenExpiration: MoreThanOrEqual(currentTimestamp), resetPasswordTokenExpiration: MoreThanOrEqual(currentTimestamp),

View file

@ -85,7 +85,7 @@ export function usersNamespace(this: N8nApp): void {
createUsers[invite.email.toLowerCase()] = null; createUsers[invite.email.toLowerCase()] = null;
}); });
const role = await Db.collections.Role.findOne({ scope: 'global', name: 'member' }); const role = await Db.collections.Role.findOneBy({ scope: 'global', name: 'member' });
if (!role) { if (!role) {
Logger.error( Logger.error(
@ -434,7 +434,7 @@ export function usersNamespace(this: N8nApp): void {
.getRepository(SharedWorkflow) .getRepository(SharedWorkflow)
.find({ .find({
select: ['workflowId'], select: ['workflowId'],
where: { userId: userToDelete.id, role: workflowOwnerRole }, where: { userId: userToDelete.id, roleId: workflowOwnerRole?.id },
}) })
.then((sharedWorkflows) => sharedWorkflows.map(({ workflowId }) => workflowId)); .then((sharedWorkflows) => sharedWorkflows.map(({ workflowId }) => workflowId));
@ -459,7 +459,7 @@ export function usersNamespace(this: N8nApp): void {
.getRepository(SharedCredentials) .getRepository(SharedCredentials)
.find({ .find({
select: ['credentialsId'], select: ['credentialsId'],
where: { user: userToDelete, role: credentialOwnerRole }, where: { userId: userToDelete.id, roleId: credentialOwnerRole?.id },
}) })
.then((sharedCredentials) => .then((sharedCredentials) =>
sharedCredentials.map(({ credentialsId }) => credentialsId), sharedCredentials.map(({ credentialsId }) => credentialsId),
@ -495,11 +495,11 @@ export function usersNamespace(this: N8nApp): void {
const [ownedSharedWorkflows, ownedSharedCredentials] = await Promise.all([ const [ownedSharedWorkflows, ownedSharedCredentials] = await Promise.all([
Db.collections.SharedWorkflow.find({ Db.collections.SharedWorkflow.find({
relations: ['workflow'], relations: ['workflow'],
where: { user: userToDelete, role: workflowOwnerRole }, where: { userId: userToDelete.id, roleId: workflowOwnerRole?.id },
}), }),
Db.collections.SharedCredentials.find({ Db.collections.SharedCredentials.find({
relations: ['credentials'], relations: ['credentials'],
where: { user: userToDelete, role: credentialOwnerRole }, where: { userId: userToDelete.id, roleId: credentialOwnerRole?.id },
}), }),
]); ]);
@ -546,7 +546,7 @@ export function usersNamespace(this: N8nApp): void {
); );
} }
const reinvitee = await Db.collections.User.findOne({ id: idToReinvite }); const reinvitee = await Db.collections.User.findOneBy({ id: idToReinvite });
if (!reinvitee) { if (!reinvitee) {
Logger.debug( Logger.debug(

View file

@ -107,9 +107,9 @@ export class WaitTrackerClass {
} }
// Also check in database // Also check in database
const execution = await Db.collections.Execution.findOne(executionId); const execution = await Db.collections.Execution.findOneBy({ id: executionId });
if (execution === undefined || !execution.waitTill) { if (execution === null || !execution.waitTill) {
throw new Error(`The execution ID "${executionId}" could not be found.`); throw new Error(`The execution ID "${executionId}" could not be found.`);
} }
@ -146,9 +146,11 @@ export class WaitTrackerClass {
(async () => { (async () => {
// Get the data to execute // Get the data to execute
const fullExecutionDataFlatted = await Db.collections.Execution.findOne(executionId); const fullExecutionDataFlatted = await Db.collections.Execution.findOneBy({
id: executionId,
});
if (fullExecutionDataFlatted === undefined) { if (fullExecutionDataFlatted === null) {
throw new Error(`The execution with the id "${executionId}" does not exist.`); throw new Error(`The execution with the id "${executionId}" does not exist.`);
} }

View file

@ -41,9 +41,9 @@ export class WaitingWebhooks {
const executionId = pathParts.shift(); const executionId = pathParts.shift();
const path = pathParts.join('/'); const path = pathParts.join('/');
const execution = await Db.collections.Execution.findOne(executionId); const execution = await Db.collections.Execution.findOneBy({ id: executionId });
if (execution === undefined) { if (execution === null) {
throw new ResponseHelper.NotFoundError(`The execution "${executionId} does not exist.`); throw new ResponseHelper.NotFoundError(`The execution "${executionId} does not exist.`);
} }

View file

@ -33,7 +33,7 @@ export async function WorkflowCredentials(nodes: INode[]): Promise<IWorkflowCred
if (!returnCredentials[type][nodeCredentials.id]) { if (!returnCredentials[type][nodeCredentials.id]) {
// eslint-disable-next-line no-await-in-loop // eslint-disable-next-line no-await-in-loop
foundCredentials = await Db.collections.Credentials.findOne({ foundCredentials = await Db.collections.Credentials.findOneBy({
id: nodeCredentials.id, id: nodeCredentials.id,
type, type,
}); });

View file

@ -402,9 +402,9 @@ export function hookFunctionsPreExecute(parentProcessMode?: string): IWorkflowEx
{ executionId: this.executionId, nodeName }, { executionId: this.executionId, nodeName },
); );
const execution = await Db.collections.Execution.findOne(this.executionId); const execution = await Db.collections.Execution.findOneBy({ id: this.executionId });
if (execution === undefined) { if (execution === null) {
// Something went badly wrong if this happens. // Something went badly wrong if this happens.
// This check is here mostly to make typescript happy. // This check is here mostly to make typescript happy.
return; return;
@ -829,7 +829,7 @@ export async function getWorkflowData(
); );
} }
let workflowData: IWorkflowBase | undefined; let workflowData: IWorkflowBase | null;
if (workflowInfo.id !== undefined) { if (workflowInfo.id !== undefined) {
if (!Db.isInitialized) { if (!Db.isInitialized) {
// The first time executeWorkflow gets called the Database has // The first time executeWorkflow gets called the Database has
@ -845,7 +845,7 @@ export async function getWorkflowData(
throw new Error(`The workflow with the id "${workflowInfo.id}" does not exist.`); throw new Error(`The workflow with the id "${workflowInfo.id}" does not exist.`);
} }
} else { } else {
workflowData = workflowInfo.code; workflowData = workflowInfo.code ?? null;
if (workflowData) { if (workflowData) {
if (!workflowData.id) { if (!workflowData.id) {
workflowData.id = parentWorkflowId; workflowData.id = parentWorkflowId;

View file

@ -88,7 +88,7 @@ export async function executeErrorWorkflow(
): Promise<void> { ): Promise<void> {
// Wrap everything in try/catch to make sure that no errors bubble up and all get caught here // Wrap everything in try/catch to make sure that no errors bubble up and all get caught here
try { try {
let workflowData; let workflowData: WorkflowEntity | null = null;
if (workflowId !== workflowErrorData.workflow.id) { if (workflowId !== workflowErrorData.workflow.id) {
// To make this code easier to understand, we split it in 2 parts: // To make this code easier to understand, we split it in 2 parts:
// 1) Fetch the owner of the errored workflows and then // 1) Fetch the owner of the errored workflows and then
@ -99,7 +99,7 @@ export async function executeErrorWorkflow(
const user = await getWorkflowOwner(workflowErrorData.workflow.id!); const user = await getWorkflowOwner(workflowErrorData.workflow.id!);
if (user.globalRole.name === 'owner') { if (user.globalRole.name === 'owner') {
workflowData = await Db.collections.Workflow.findOne({ id: workflowId }); workflowData = await Db.collections.Workflow.findOneBy({ id: workflowId });
} else { } else {
const sharedWorkflowData = await Db.collections.SharedWorkflow.findOne({ const sharedWorkflowData = await Db.collections.SharedWorkflow.findOne({
where: { workflowId, userId: user.id }, where: { workflowId, userId: user.id },
@ -110,10 +110,10 @@ export async function executeErrorWorkflow(
} }
} }
} else { } else {
workflowData = await Db.collections.Workflow.findOne({ id: workflowId }); workflowData = await Db.collections.Workflow.findOneBy({ id: workflowId });
} }
if (workflowData === undefined) { if (workflowData === null) {
// The error workflow could not be found // The error workflow could not be found
Logger.error( Logger.error(
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
@ -254,21 +254,13 @@ export async function saveStaticDataById(
/** /**
* Returns the static data of workflow * Returns the static data of workflow
*
* @param {(string)} workflowId The id of the workflow to get static data of
*/ */
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
export async function getStaticDataById(workflowId: string) { export async function getStaticDataById(workflowId: string) {
const workflowData = await Db.collections.Workflow.findOne(workflowId, { const workflowData = await Db.collections.Workflow.findOne({
select: ['staticData'], select: ['staticData'],
where: { id: workflowId },
}); });
return workflowData?.staticData ?? {};
if (workflowData === undefined) {
return {};
}
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
return workflowData.staticData || {};
} }
/** /**
@ -312,7 +304,7 @@ export async function replaceInvalidCredentials(workflow: WorkflowEntity): Promi
credentialsByName[nodeCredentialType] = {}; credentialsByName[nodeCredentialType] = {};
} }
if (credentialsByName[nodeCredentialType][name] === undefined) { if (credentialsByName[nodeCredentialType][name] === undefined) {
const credentials = await Db.collections.Credentials.find({ const credentials = await Db.collections.Credentials.findBy({
name, name,
type: nodeCredentialType, type: nodeCredentialType,
}); });
@ -348,7 +340,7 @@ export async function replaceInvalidCredentials(workflow: WorkflowEntity): Promi
// check if credentials for ID-type are not yet cached // check if credentials for ID-type are not yet cached
if (credentialsById[nodeCredentialType][nodeCredentials.id] === undefined) { if (credentialsById[nodeCredentialType][nodeCredentials.id] === undefined) {
// check first if ID-type combination exists // check first if ID-type combination exists
const credentials = await Db.collections.Credentials.findOne({ const credentials = await Db.collections.Credentials.findOneBy({
id: nodeCredentials.id, id: nodeCredentials.id,
type: nodeCredentialType, type: nodeCredentialType,
}); });
@ -362,7 +354,7 @@ export async function replaceInvalidCredentials(workflow: WorkflowEntity): Promi
continue; continue;
} }
// no credentials found for ID, check if some exist for name // no credentials found for ID, check if some exist for name
const credsByName = await Db.collections.Credentials.find({ const credsByName = await Db.collections.Credentials.findBy({
name: nodeCredentials.name, name: nodeCredentials.name,
type: nodeCredentialType, type: nodeCredentialType,
}); });
@ -414,14 +406,17 @@ export async function isBelowOnboardingThreshold(user: User): Promise<boolean> {
let belowThreshold = true; let belowThreshold = true;
const skippedTypes = ['n8n-nodes-base.start', 'n8n-nodes-base.stickyNote']; const skippedTypes = ['n8n-nodes-base.start', 'n8n-nodes-base.stickyNote'];
const workflowOwnerRole = await Db.collections.Role.findOne({ const workflowOwnerRoleId = await Db.collections.Role.findOne({
name: 'owner', select: ['id'],
scope: 'workflow', where: {
}); name: 'owner',
scope: 'workflow',
},
}).then((role) => role?.id);
const ownedWorkflowsIds = await Db.collections.SharedWorkflow.find({ const ownedWorkflowsIds = await Db.collections.SharedWorkflow.find({
where: { where: {
user, userId: user.id,
role: workflowOwnerRole, roleId: workflowOwnerRoleId,
}, },
select: ['workflowId'], select: ['workflowId'],
}).then((ownedWorkflows) => ownedWorkflows.map(({ workflowId }) => workflowId)); }).then((ownedWorkflows) => ownedWorkflows.map(({ workflowId }) => workflowId));

View file

@ -527,9 +527,9 @@ export class WorkflowRunner {
reject(error); reject(error);
} }
const executionDb = (await Db.collections.Execution.findOne( const executionDb = (await Db.collections.Execution.findOneBy({
executionId, id: executionId,
)) as IExecutionFlattedDb; })) as IExecutionFlattedDb;
const fullExecutionData = ResponseHelper.unflattenExecutionData(executionDb); const fullExecutionData = ResponseHelper.unflattenExecutionData(executionDb);
const runData = { const runData = {
data: fullExecutionData.data, data: fullExecutionData.data,

View file

@ -99,11 +99,14 @@ e2eController.post('/db/setup-owner', bodyParser.json(), async (req, res) => {
} }
const globalRole = await Db.collections.Role.findOneOrFail({ const globalRole = await Db.collections.Role.findOneOrFail({
name: 'owner', select: ['id'],
scope: 'global', where: {
name: 'owner',
scope: 'global',
},
}); });
const owner = await Db.collections.User.findOneOrFail({ globalRole }); const owner = await Db.collections.User.findOneByOrFail({ globalRoleId: globalRole.id });
await Db.collections.User.update(owner.id, { await Db.collections.User.update(owner.id, {
email: req.body.email, email: req.body.email,

View file

@ -1,4 +1,4 @@
import { MoreThanOrEqual } from 'typeorm'; import { FindOperator, MoreThanOrEqual } from 'typeorm';
import { DateUtils } from 'typeorm/util/DateUtils'; import { DateUtils } from 'typeorm/util/DateUtils';
import * as Db from '@/Db'; import * as Db from '@/Db';
import config from '@/config'; import config from '@/config';
@ -46,7 +46,7 @@ async function getExecutionsInPastDays(days: number) {
return Db.collections.Execution.find({ return Db.collections.Execution.find({
select: ['workflowData'], select: ['workflowData'],
where: { where: {
startedAt: MoreThanOrEqual(utcDate), startedAt: MoreThanOrEqual(utcDate) as unknown as FindOperator<Date>,
}, },
}); });
} }

View file

@ -38,12 +38,12 @@ export abstract class BaseCommand extends Command {
}; };
async getInstanceOwner(): Promise<User> { async getInstanceOwner(): Promise<User> {
const globalRole = await Db.collections.Role.findOneOrFail({ const globalRole = await Db.collections.Role.findOneByOrFail({
name: 'owner', name: 'owner',
scope: 'global', scope: 'global',
}); });
const owner = await Db.collections.User.findOne({ globalRole }); const owner = await Db.collections.User.findOneBy({ globalRoleId: globalRole.id });
if (owner) return owner; if (owner) return owner;
@ -53,6 +53,6 @@ export abstract class BaseCommand extends Command {
await Db.collections.User.save(user); await Db.collections.User.save(user);
return Db.collections.User.findOneOrFail({ globalRole }); return Db.collections.User.findOneByOrFail({ globalRoleId: globalRole.id });
} }
} }

View file

@ -1,7 +1,7 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable no-console */ /* eslint-disable no-console */
import { Command, flags } from '@oclif/command'; import { Command, flags } from '@oclif/command';
import { Connection, ConnectionOptions, createConnection } from 'typeorm'; import { DataSource as Connection, DataSourceOptions as ConnectionOptions } from 'typeorm';
import { LoggerProxy } from 'n8n-workflow'; import { LoggerProxy } from 'n8n-workflow';
import { getLogger } from '@/Logger'; import { getLogger } from '@/Logger';
@ -34,11 +34,12 @@ export class DbRevertMigrationCommand extends Command {
dropSchema: false, dropSchema: false,
logging: ['query', 'error', 'schema'], logging: ['query', 'error', 'schema'],
}; };
connection = await createConnection(connectionOptions); connection = new Connection(connectionOptions);
await connection.initialize();
await connection.undoLastMigration(); await connection.undoLastMigration();
await connection.close(); await connection.destroy();
} catch (error) { } catch (error) {
if (connection) await connection.close(); if (connection?.isInitialized) await connection.destroy();
console.error('Error reverting last migration. See log messages for details.'); console.error('Error reverting last migration. See log messages for details.');
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument // eslint-disable-next-line @typescript-eslint/no-unsafe-argument

View file

@ -72,7 +72,7 @@ export class Execute extends Command {
} }
let workflowId: string | undefined; let workflowId: string | undefined;
let workflowData: IWorkflowBase | undefined; let workflowData: IWorkflowBase | null = null;
if (flags.file) { if (flags.file) {
// Path to workflow is given // Path to workflow is given
try { try {
@ -91,7 +91,7 @@ export class Execute extends Command {
// Do a basic check if the data in the file looks right // Do a basic check if the data in the file looks right
// TODO: Later check with the help of TypeScript data if it is valid or not // TODO: Later check with the help of TypeScript data if it is valid or not
if ( if (
workflowData === undefined || workflowData === null ||
workflowData.nodes === undefined || workflowData.nodes === undefined ||
workflowData.connections === undefined workflowData.connections === undefined
) { ) {
@ -108,8 +108,8 @@ export class Execute extends Command {
if (flags.id) { if (flags.id) {
// Id of workflow is given // Id of workflow is given
workflowId = flags.id; workflowId = flags.id;
workflowData = await Db.collections.Workflow.findOne(workflowId); workflowData = await Db.collections.Workflow.findOneBy({ id: workflowId });
if (workflowData === undefined) { if (workflowData === null) {
console.info(`The workflow with the id "${workflowId}" does not exist.`); console.info(`The workflow with the id "${workflowId}" does not exist.`);
process.exit(1); process.exit(1);
} }

View file

@ -14,7 +14,7 @@ import { LoggerProxy } from 'n8n-workflow';
import fs from 'fs'; import fs from 'fs';
import glob from 'fast-glob'; import glob from 'fast-glob';
import { EntityManager, getConnection } from 'typeorm'; import type { EntityManager } from 'typeorm';
import { getLogger } from '@/Logger'; import { getLogger } from '@/Logger';
import * as Db from '@/Db'; import * as Db from '@/Db';
import { User } from '@db/entities/User'; import { User } from '@db/entities/User';
@ -100,7 +100,7 @@ export class ImportCredentialsCommand extends Command {
totalImported = files.length; totalImported = files.length;
await getConnection().transaction(async (transactionManager) => { await Db.getConnection().transaction(async (transactionManager) => {
this.transactionManager = transactionManager; this.transactionManager = transactionManager;
for (const file of files) { for (const file of files) {
const credential = JSON.parse(fs.readFileSync(file, { encoding: 'utf8' })); const credential = JSON.parse(fs.readFileSync(file, { encoding: 'utf8' }));
@ -128,7 +128,7 @@ export class ImportCredentialsCommand extends Command {
); );
} }
await getConnection().transaction(async (transactionManager) => { await Db.getConnection().transaction(async (transactionManager) => {
this.transactionManager = transactionManager; this.transactionManager = transactionManager;
for (const credential of credentials) { for (const credential of credentials) {
if (typeof credential.data === 'object') { if (typeof credential.data === 'object') {
@ -187,7 +187,9 @@ export class ImportCredentialsCommand extends Command {
where: { name: 'owner', scope: 'global' }, where: { name: 'owner', scope: 'global' },
}); });
const owner = await Db.collections.User.findOne({ globalRole: ownerGlobalRole }); const owner =
ownerGlobalRole &&
(await Db.collections.User.findOneBy({ globalRoleId: ownerGlobalRole.id }));
if (!owner) { if (!owner) {
throw new Error(`Failed to find owner. ${FIX_INSTRUCTION}`); throw new Error(`Failed to find owner. ${FIX_INSTRUCTION}`);
@ -197,7 +199,7 @@ export class ImportCredentialsCommand extends Command {
} }
private async getAssignee(userId: string) { private async getAssignee(userId: string) {
const user = await Db.collections.User.findOne(userId); const user = await Db.collections.User.findOneBy({ id: userId });
if (!user) { if (!user) {
throw new Error(`Failed to find user with ID ${userId}`); throw new Error(`Failed to find user with ID ${userId}`);

View file

@ -16,7 +16,7 @@ import { INode, INodeCredentialsDetails, LoggerProxy } from 'n8n-workflow';
import fs from 'fs'; import fs from 'fs';
import glob from 'fast-glob'; import glob from 'fast-glob';
import { UserSettings } from 'n8n-core'; import { UserSettings } from 'n8n-core';
import { EntityManager, getConnection } from 'typeorm'; import type { EntityManager } from 'typeorm';
import { v4 as uuid } from 'uuid'; import { v4 as uuid } from 'uuid';
import { getLogger } from '@/Logger'; import { getLogger } from '@/Logger';
import * as Db from '@/Db'; import * as Db from '@/Db';
@ -123,7 +123,7 @@ export class ImportWorkflowsCommand extends Command {
totalImported = files.length; totalImported = files.length;
await getConnection().transaction(async (transactionManager) => { await Db.getConnection().transaction(async (transactionManager) => {
this.transactionManager = transactionManager; this.transactionManager = transactionManager;
for (const file of files) { for (const file of files) {
@ -158,7 +158,7 @@ export class ImportWorkflowsCommand extends Command {
totalImported = workflows.length; totalImported = workflows.length;
await getConnection().transaction(async (transactionManager) => { await Db.getConnection().transaction(async (transactionManager) => {
this.transactionManager = transactionManager; this.transactionManager = transactionManager;
for (const workflow of workflows) { for (const workflow of workflows) {
@ -229,7 +229,9 @@ export class ImportWorkflowsCommand extends Command {
where: { name: 'owner', scope: 'global' }, where: { name: 'owner', scope: 'global' },
}); });
const owner = await Db.collections.User.findOne({ globalRole: ownerGlobalRole }); const owner =
ownerGlobalRole &&
(await Db.collections.User.findOneBy({ globalRoleId: ownerGlobalRole?.id }));
if (!owner) { if (!owner) {
throw new Error(`Failed to find owner. ${FIX_INSTRUCTION}`); throw new Error(`Failed to find owner. ${FIX_INSTRUCTION}`);
@ -239,7 +241,7 @@ export class ImportWorkflowsCommand extends Command {
} }
private async getAssignee(userId: string) { private async getAssignee(userId: string) {
const user = await Db.collections.User.findOne(userId); const user = await Db.collections.User.findOneBy({ id: userId });
if (!user) { if (!user) {
throw new Error(`Failed to find user with ID ${userId}`); throw new Error(`Failed to find user with ID ${userId}`);

View file

@ -304,7 +304,7 @@ export class Start extends Command {
await UserSettings.getEncryptionKey(); await UserSettings.getEncryptionKey();
// Load settings from database and set them to config. // Load settings from database and set them to config.
const databaseSettings = await Db.collections.Settings.find({ loadOnStartup: true }); const databaseSettings = await Db.collections.Settings.findBy({ loadOnStartup: true });
databaseSettings.forEach((setting) => { databaseSettings.forEach((setting) => {
config.set(setting.key, JSON.parse(setting.value)); config.set(setting.key, JSON.parse(setting.value));
}); });

View file

@ -9,23 +9,23 @@ export class Reset extends BaseCommand {
async run(): Promise<void> { async run(): Promise<void> {
const owner = await this.getInstanceOwner(); const owner = await this.getInstanceOwner();
const ownerWorkflowRole = await Db.collections.Role.findOneOrFail({ const ownerWorkflowRole = await Db.collections.Role.findOneByOrFail({
name: 'owner', name: 'owner',
scope: 'workflow', scope: 'workflow',
}); });
const ownerCredentialRole = await Db.collections.Role.findOneOrFail({ const ownerCredentialRole = await Db.collections.Role.findOneByOrFail({
name: 'owner', name: 'owner',
scope: 'credential', scope: 'credential',
}); });
await Db.collections.SharedWorkflow.update( await Db.collections.SharedWorkflow.update(
{ user: { id: Not(owner.id) }, role: ownerWorkflowRole }, { userId: Not(owner.id), roleId: ownerWorkflowRole.id },
{ user: owner }, { user: owner },
); );
await Db.collections.SharedCredentials.update( await Db.collections.SharedCredentials.update(
{ user: { id: Not(owner.id) }, role: ownerCredentialRole }, { userId: Not(owner.id), roleId: ownerCredentialRole.id },
{ user: owner }, { user: owner },
); );

View file

@ -20,8 +20,6 @@ import {
sleep, sleep,
} from 'n8n-workflow'; } from 'n8n-workflow';
import { FindOneOptions, getConnectionManager } from 'typeorm';
import { CredentialsOverwrites } from '@/CredentialsOverwrites'; import { CredentialsOverwrites } from '@/CredentialsOverwrites';
import { CredentialTypes } from '@/CredentialTypes'; import { CredentialTypes } from '@/CredentialTypes';
import * as Db from '@/Db'; import * as Db from '@/Db';
@ -125,7 +123,7 @@ export class Worker extends Command {
async runJob(job: Queue.Job, nodeTypes: INodeTypes): Promise<Queue.JobResponse> { async runJob(job: Queue.Job, nodeTypes: INodeTypes): Promise<Queue.JobResponse> {
const { executionId, loadStaticData } = job.data; const { executionId, loadStaticData } = job.data;
const executionDb = await Db.collections.Execution.findOne(executionId); const executionDb = await Db.collections.Execution.findOneBy({ id: executionId });
if (!executionDb) { if (!executionDb) {
LoggerProxy.error( LoggerProxy.error(
@ -145,14 +143,13 @@ export class Worker extends Command {
let { staticData } = currentExecutionDb.workflowData; let { staticData } = currentExecutionDb.workflowData;
if (loadStaticData) { if (loadStaticData) {
const findOptions = { const workflowData = await Db.collections.Workflow.findOne({
select: ['id', 'staticData'], select: ['id', 'staticData'],
} as FindOneOptions; where: {
const workflowData = await Db.collections.Workflow.findOne( id: currentExecutionDb.workflowData.id,
currentExecutionDb.workflowData.id, },
findOptions, });
); if (workflowData === null) {
if (workflowData === undefined) {
LoggerProxy.error( LoggerProxy.error(
'Worker execution failed because workflow could not be found in database.', 'Worker execution failed because workflow could not be found in database.',
{ {
@ -384,10 +381,10 @@ export class Worker extends Command {
async (req: express.Request, res: express.Response) => { async (req: express.Request, res: express.Response) => {
LoggerProxy.debug('Health check started!'); LoggerProxy.debug('Health check started!');
const connection = getConnectionManager().get(); const connection = Db.getConnection();
try { try {
if (!connection.isConnected) { if (!connection.isInitialized) {
// Connection is not active // Connection is not active
throw new Error('No active database connection!'); throw new Error('No active database connection!');
} }

View file

@ -177,7 +177,7 @@ credentialsController.patch(
const responseData = await CredentialsService.update(credentialId, newCredentialData); const responseData = await CredentialsService.update(credentialId, newCredentialData);
if (responseData === undefined) { if (responseData === null) {
throw new ResponseHelper.NotFoundError( throw new ResponseHelper.NotFoundError(
`Credential ID "${credentialId}" could not be found to be updated.`, `Credential ID "${credentialId}" could not be found to be updated.`,
); );

View file

@ -1,5 +1,5 @@
/* eslint-disable no-param-reassign */ /* eslint-disable no-param-reassign */
import { DeleteResult, EntityManager, FindConditions, In, Not } from 'typeorm'; import { DeleteResult, EntityManager, FindOptionsWhere, In, Not } from 'typeorm';
import * as Db from '@/Db'; import * as Db from '@/Db';
import { RoleService } from '@/role/role.service'; import { RoleService } from '@/role/role.service';
import { CredentialsEntity } from '@db/entities/CredentialsEntity'; import { CredentialsEntity } from '@db/entities/CredentialsEntity';
@ -33,14 +33,14 @@ export class EECredentialsService extends CredentialsService {
credentialId: string, credentialId: string,
relations: string[] = ['credentials'], relations: string[] = ['credentials'],
{ allowGlobalOwner } = { allowGlobalOwner: true }, { allowGlobalOwner } = { allowGlobalOwner: true },
): Promise<SharedCredentials | undefined> { ): Promise<SharedCredentials | null> {
const where: FindConditions<SharedCredentials> = { credentialsId: credentialId }; const where: FindOptionsWhere<SharedCredentials> = { credentialsId: credentialId };
// Omit user from where if the requesting user is the global // Omit user from where if the requesting user is the global
// owner. This allows the global owner to view and delete // owner. This allows the global owner to view and delete
// credentials they don't own. // credentials they don't own.
if (!allowGlobalOwner || user.globalRole.name !== 'owner') { if (!allowGlobalOwner || user.globalRole.name !== 'owner') {
where.user = { id: user.id }; where.userId = user.id;
} }
return Db.collections.SharedCredentials.findOne({ return Db.collections.SharedCredentials.findOne({
@ -65,7 +65,7 @@ export class EECredentialsService extends CredentialsService {
credentialId: string, credentialId: string,
userIds: string[], userIds: string[],
): Promise<DeleteResult> { ): Promise<DeleteResult> {
const conditions: FindConditions<SharedCredentials> = { const conditions: FindOptionsWhere<SharedCredentials> = {
credentialsId: credentialId, credentialsId: credentialId,
userId: Not(In(userIds)), userId: Not(In(userIds)),
}; };
@ -86,9 +86,9 @@ export class EECredentialsService extends CredentialsService {
.filter((user) => !user.isPending) .filter((user) => !user.isPending)
.map((user) => .map((user) =>
Db.collections.SharedCredentials.create({ Db.collections.SharedCredentials.create({
credentials: credential, credentialsId: credential.id,
user, userId: user.id,
role, roleId: role?.id,
}), }),
); );

View file

@ -10,7 +10,7 @@ import {
LoggerProxy, LoggerProxy,
NodeHelpers, NodeHelpers,
} from 'n8n-workflow'; } from 'n8n-workflow';
import { FindConditions, FindManyOptions, In } from 'typeorm'; import { FindManyOptions, FindOptionsWhere, In } from 'typeorm';
import * as Db from '@/Db'; import * as Db from '@/Db';
import * as ResponseHelper from '@/ResponseHelper'; import * as ResponseHelper from '@/ResponseHelper';
@ -28,11 +28,12 @@ import { CredentialTypes } from '@/CredentialTypes';
export class CredentialsService { export class CredentialsService {
static async get( static async get(
credential: Partial<ICredentialsDb>, where: FindOptionsWhere<ICredentialsDb>,
options?: { relations: string[] }, options?: { relations: string[] },
): Promise<ICredentialsDb | undefined> { ): Promise<ICredentialsDb | null> {
return Db.collections.Credentials.findOne(credential, { return Db.collections.Credentials.findOne({
relations: options?.relations, relations: options?.relations,
where,
}); });
} }
@ -88,8 +89,8 @@ export class CredentialsService {
credentialId: string, credentialId: string,
relations: string[] = ['credentials'], relations: string[] = ['credentials'],
{ allowGlobalOwner } = { allowGlobalOwner: true }, { allowGlobalOwner } = { allowGlobalOwner: true },
): Promise<SharedCredentials | undefined> { ): Promise<SharedCredentials | null> {
const where: FindConditions<SharedCredentials> = { credentialsId: credentialId }; const where: FindOptionsWhere<SharedCredentials> = { credentialsId: credentialId };
// Omit user from where if the requesting user is the global // Omit user from where if the requesting user is the global
// owner. This allows the global owner to view and delete // owner. This allows the global owner to view and delete
@ -204,7 +205,7 @@ export class CredentialsService {
static async update( static async update(
credentialId: string, credentialId: string,
newCredentialData: ICredentialsDb, newCredentialData: ICredentialsDb,
): Promise<ICredentialsDb | undefined> { ): Promise<ICredentialsDb | null> {
await ExternalHooks().run('credentials.update', [newCredentialData]); await ExternalHooks().run('credentials.update', [newCredentialData]);
// Update the credentials in DB // Update the credentials in DB
@ -212,7 +213,7 @@ export class CredentialsService {
// We sadly get nothing back from "update". Neither if it updated a record // We sadly get nothing back from "update". Neither if it updated a record
// nor the new value. So query now the updated entry. // nor the new value. So query now the updated entry.
return Db.collections.Credentials.findOne(credentialId); return Db.collections.Credentials.findOneBy({ id: credentialId });
} }
static async save( static async save(
@ -226,7 +227,7 @@ export class CredentialsService {
await ExternalHooks().run('credentials.create', [encryptedData]); await ExternalHooks().run('credentials.create', [encryptedData]);
const role = await Db.collections.Role.findOneOrFail({ const role = await Db.collections.Role.findOneByOrFail({
name: 'owner', name: 'owner',
scope: 'credential', scope: 'credential',
}); });

View file

@ -1,10 +1,11 @@
import { Column, Entity, OneToMany, PrimaryGeneratedColumn, Unique } from 'typeorm'; import { Column, Entity, OneToMany, PrimaryColumn, Unique } from 'typeorm';
import { IsString, Length } from 'class-validator'; import { IsString, Length } from 'class-validator';
import type { User } from './User'; import type { User } from './User';
import type { SharedWorkflow } from './SharedWorkflow'; import type { SharedWorkflow } from './SharedWorkflow';
import type { SharedCredentials } from './SharedCredentials'; import type { SharedCredentials } from './SharedCredentials';
import { AbstractEntity } from './AbstractEntity'; import { AbstractEntity } from './AbstractEntity';
import { idStringifier } from '../utils/transformers';
export type RoleNames = 'owner' | 'member' | 'user' | 'editor'; export type RoleNames = 'owner' | 'member' | 'user' | 'editor';
export type RoleScopes = 'global' | 'workflow' | 'credential'; export type RoleScopes = 'global' | 'workflow' | 'credential';
@ -12,8 +13,8 @@ export type RoleScopes = 'global' | 'workflow' | 'credential';
@Entity() @Entity()
@Unique(['scope', 'name']) @Unique(['scope', 'name'])
export class Role extends AbstractEntity { export class Role extends AbstractEntity {
@PrimaryGeneratedColumn() @PrimaryColumn({ transformer: idStringifier })
id: number; id: string;
@Column({ length: 32 }) @Column({ length: 32 })
@IsString({ message: 'Role name must be of type string.' }) @IsString({ message: 'Role name must be of type string.' })

View file

@ -1,4 +1,4 @@
import { Entity, ManyToOne, PrimaryColumn, RelationId } from 'typeorm'; import { Column, Entity, ManyToOne, PrimaryColumn } from 'typeorm';
import type { CredentialsEntity } from './CredentialsEntity'; import type { CredentialsEntity } from './CredentialsEntity';
import type { User } from './User'; import type { User } from './User';
import type { Role } from './Role'; import type { Role } from './Role';
@ -10,20 +10,18 @@ export class SharedCredentials extends AbstractEntity {
@ManyToOne('Role', 'sharedCredentials', { nullable: false }) @ManyToOne('Role', 'sharedCredentials', { nullable: false })
role: Role; role: Role;
@ManyToOne('User', 'sharedCredentials', { primary: true }) @Column()
roleId: string;
@ManyToOne('User', 'sharedCredentials')
user: User; user: User;
@PrimaryColumn() @PrimaryColumn()
@RelationId((sharedCredential: SharedCredentials) => sharedCredential.user)
userId: string; userId: string;
@ManyToOne('CredentialsEntity', 'shared', { @ManyToOne('CredentialsEntity', 'shared')
primary: true,
onDelete: 'CASCADE',
})
credentials: CredentialsEntity; credentials: CredentialsEntity;
@PrimaryColumn({ transformer: idStringifier }) @PrimaryColumn({ transformer: idStringifier })
@RelationId((sharedCredential: SharedCredentials) => sharedCredential.credentials)
credentialsId: string; credentialsId: string;
} }

View file

@ -1,4 +1,4 @@
import { Entity, ManyToOne, PrimaryColumn, RelationId } from 'typeorm'; import { Column, Entity, ManyToOne, PrimaryColumn } from 'typeorm';
import type { WorkflowEntity } from './WorkflowEntity'; import type { WorkflowEntity } from './WorkflowEntity';
import type { User } from './User'; import type { User } from './User';
import type { Role } from './Role'; import type { Role } from './Role';
@ -10,20 +10,18 @@ export class SharedWorkflow extends AbstractEntity {
@ManyToOne('Role', 'sharedWorkflows', { nullable: false }) @ManyToOne('Role', 'sharedWorkflows', { nullable: false })
role: Role; role: Role;
@ManyToOne('User', 'sharedWorkflows', { primary: true }) @Column()
roleId: string;
@ManyToOne('User', 'sharedWorkflows')
user: User; user: User;
@PrimaryColumn() @PrimaryColumn()
@RelationId((sharedWorkflow: SharedWorkflow) => sharedWorkflow.user)
userId: string; userId: string;
@ManyToOne('WorkflowEntity', 'shared', { @ManyToOne('WorkflowEntity', 'shared')
primary: true,
onDelete: 'CASCADE',
})
workflow: WorkflowEntity; workflow: WorkflowEntity;
@PrimaryColumn({ transformer: idStringifier }) @PrimaryColumn({ transformer: idStringifier })
@RelationId((sharedWorkflow: SharedWorkflow) => sharedWorkflow.workflow)
workflowId: string; workflowId: string;
} }

View file

@ -74,12 +74,12 @@ export class User extends AbstractEntity implements IUser {
}) })
settings: IUserSettings | null; settings: IUserSettings | null;
@ManyToOne('Role', 'globalForUsers', { @ManyToOne('Role', 'globalForUsers', { nullable: false })
cascade: true,
nullable: false,
})
globalRole: Role; globalRole: Role;
@Column()
globalRoleId: string;
@OneToMany('SharedWorkflow', 'user') @OneToMany('SharedWorkflow', 'user')
sharedWorkflows: SharedWorkflow[]; sharedWorkflows: SharedWorkflow[];

View file

@ -1,4 +1,4 @@
import { Column, Entity, RelationId, ManyToOne, PrimaryColumn } from 'typeorm'; import { Column, Entity, ManyToOne, PrimaryColumn } from 'typeorm';
import { idStringifier } from '../utils/transformers'; import { idStringifier } from '../utils/transformers';
import { datetimeColumnType } from './AbstractEntity'; import { datetimeColumnType } from './AbstractEntity';
import type { WorkflowEntity } from './WorkflowEntity'; import type { WorkflowEntity } from './WorkflowEntity';
@ -22,13 +22,9 @@ export class WorkflowStatistics {
@PrimaryColumn({ length: 128 }) @PrimaryColumn({ length: 128 })
name: StatisticsNames; name: StatisticsNames;
@ManyToOne('WorkflowEntity', 'shared', { @ManyToOne('WorkflowEntity', 'shared')
primary: true,
onDelete: 'CASCADE',
})
workflow: WorkflowEntity; workflow: WorkflowEntity;
@PrimaryColumn({ transformer: idStringifier }) @PrimaryColumn({ transformer: idStringifier })
@RelationId((workflowStatistics: WorkflowStatistics) => workflowStatistics.workflow)
workflowId: string; workflowId: string;
} }

View file

@ -1,5 +1,5 @@
import { LoggerProxy, MessageEventBusDestinationOptions } from 'n8n-workflow'; import { LoggerProxy, MessageEventBusDestinationOptions } from 'n8n-workflow';
import { DeleteResult } from 'typeorm'; import type { DeleteResult } from 'typeorm';
import { EventMessageTypes } from '../EventMessageClasses/'; import { EventMessageTypes } from '../EventMessageClasses/';
import type { MessageEventBusDestination } from '../MessageEventBusDestination/MessageEventBusDestination.ee'; import type { MessageEventBusDestination } from '../MessageEventBusDestination/MessageEventBusDestination.ee';
import { MessageEventBusLogWriter } from '../MessageEventBusWriter/MessageEventBusLogWriter'; import { MessageEventBusLogWriter } from '../MessageEventBusWriter/MessageEventBusLogWriter';

View file

@ -9,7 +9,7 @@ import * as Db from '@/Db';
import { AbstractEventMessage } from '../EventMessageClasses/AbstractEventMessage'; import { AbstractEventMessage } from '../EventMessageClasses/AbstractEventMessage';
import { EventMessageTypes } from '../EventMessageClasses'; import { EventMessageTypes } from '../EventMessageClasses';
import { eventBus } from '..'; import { eventBus } from '..';
import { DeleteResult, InsertResult } from 'typeorm'; import type { DeleteResult, InsertResult } from 'typeorm';
export abstract class MessageEventBusDestination implements MessageEventBusDestinationOptions { export abstract class MessageEventBusDestination implements MessageEventBusDestinationOptions {
// Since you can't have static abstract functions - this just serves as a reminder that you need to implement these. Please. // Since you can't have static abstract functions - this just serves as a reminder that you need to implement these. Please.

View file

@ -12,7 +12,7 @@ import {
jsonParse, jsonParse,
Workflow, Workflow,
} from 'n8n-workflow'; } from 'n8n-workflow';
import { FindConditions, FindOperator, In, IsNull, LessThanOrEqual, Not, Raw } from 'typeorm'; import { FindOperator, FindOptionsWhere, In, IsNull, LessThanOrEqual, Not, Raw } from 'typeorm';
import * as ActiveExecutions from '@/ActiveExecutions'; import * as ActiveExecutions from '@/ActiveExecutions';
import config from '@/config'; import config from '@/config';
import type { User } from '@/databases/entities/User'; import type { User } from '@/databases/entities/User';
@ -200,7 +200,7 @@ export class ExecutionsService {
.map(({ id }) => id), .map(({ id }) => id),
); );
const findWhere: FindConditions<ExecutionEntity> = { workflowId: In(sharedWorkflowIds) }; const findWhere: FindOptionsWhere<ExecutionEntity> = { workflowId: In(sharedWorkflowIds) };
const rangeQuery: string[] = []; const rangeQuery: string[] = [];
const rangeQueryParams: { const rangeQueryParams: {
@ -370,7 +370,9 @@ export class ExecutionsService {
// Loads the currently saved workflow to execute instead of the // Loads the currently saved workflow to execute instead of the
// one saved at the time of the execution. // one saved at the time of the execution.
const workflowId = fullExecutionData.workflowData.id as string; const workflowId = fullExecutionData.workflowData.id as string;
const workflowData = (await Db.collections.Workflow.findOne(workflowId)) as IWorkflowBase; const workflowData = (await Db.collections.Workflow.findOneBy({
id: workflowId,
})) as IWorkflowBase;
if (workflowData === undefined) { if (workflowData === undefined) {
throw new Error( throw new Error(
@ -453,7 +455,7 @@ export class ExecutionsService {
throw new Error('Either "deleteBefore" or "ids" must be present in the request body'); throw new Error('Either "deleteBefore" or "ids" must be present in the request body');
} }
const where: FindConditions<ExecutionEntity> = { workflowId: In(sharedWorkflowIds) }; const where: FindOptionsWhere<ExecutionEntity> = { workflowId: In(sharedWorkflowIds) };
if (deleteBefore) { if (deleteBefore) {
// delete executions by date, if user may access the underlying workflows // delete executions by date, if user may access the underlying workflows

View file

@ -1,14 +1,14 @@
import { EntityManager } from 'typeorm'; import type { EntityManager, FindOptionsWhere } from 'typeorm';
import * as Db from '@/Db'; import * as Db from '@/Db';
import { Role } from '@db/entities/Role'; import { Role } from '@db/entities/Role';
export class RoleService { export class RoleService {
static async get(role: Partial<Role>): Promise<Role | undefined> { static async get(role: FindOptionsWhere<Role>): Promise<Role | null> {
return Db.collections.Role.findOne(role); return Db.collections.Role.findOneBy(role);
} }
static async trxGet(transaction: EntityManager, role: Partial<Role>) { static async trxGet(transaction: EntityManager, role: FindOptionsWhere<Role>) {
return transaction.findOne(Role, role); return transaction.findOneBy(Role, role);
} }
static async getUserRoleForWorkflow(userId: string, workflowId: string) { static async getUserRoleForWorkflow(userId: string, workflowId: string) {

View file

@ -1,15 +1,16 @@
import { EntityManager, In } from 'typeorm'; import { EntityManager, FindOptionsWhere, In } from 'typeorm';
import * as Db from '@/Db'; import * as Db from '@/Db';
import { User } from '@db/entities/User'; import { User } from '@db/entities/User';
export class UserService { export class UserService {
static async get(user: Partial<User>): Promise<User | undefined> { static async get(where: FindOptionsWhere<User>): Promise<User | null> {
return Db.collections.User.findOne(user, { return Db.collections.User.findOne({
relations: ['globalRole'], relations: ['globalRole'],
where,
}); });
} }
static async getByIds(transaction: EntityManager, ids: string[]) { static async getByIds(transaction: EntityManager, ids: string[]) {
return transaction.find(User, { id: In(ids) }); return transaction.find(User, { where: { id: In(ids) } });
} }
} }

View file

@ -17,6 +17,7 @@ import * as TagHelpers from '@/TagHelpers';
import { EECredentialsService as EECredentials } from '../credentials/credentials.service.ee'; import { EECredentialsService as EECredentials } from '../credentials/credentials.service.ee';
import { IExecutionPushResponse } from '@/Interfaces'; import { IExecutionPushResponse } from '@/Interfaces';
import * as GenericHelpers from '@/GenericHelpers'; import * as GenericHelpers from '@/GenericHelpers';
import { In } from 'typeorm';
// eslint-disable-next-line @typescript-eslint/naming-convention // eslint-disable-next-line @typescript-eslint/naming-convention
export const EEWorkflowController = express.Router(); export const EEWorkflowController = express.Router();
@ -130,8 +131,11 @@ EEWorkflowController.post(
const { tags: tagIds } = req.body; const { tags: tagIds } = req.body;
if (tagIds?.length && !config.getEnv('workflowTagsDisabled')) { if (tagIds?.length && !config.getEnv('workflowTagsDisabled')) {
newWorkflow.tags = await Db.collections.Tag.findByIds(tagIds, { newWorkflow.tags = await Db.collections.Tag.find({
select: ['id', 'name'], select: ['id', 'name'],
where: {
id: In(tagIds),
},
}); });
} }
@ -157,7 +161,7 @@ EEWorkflowController.post(
await Db.transaction(async (transactionManager) => { await Db.transaction(async (transactionManager) => {
savedWorkflow = await transactionManager.save<WorkflowEntity>(newWorkflow); savedWorkflow = await transactionManager.save<WorkflowEntity>(newWorkflow);
const role = await Db.collections.Role.findOneOrFail({ const role = await Db.collections.Role.findOneByOrFail({
name: 'owner', name: 'owner',
scope: 'workflow', scope: 'workflow',
}); });

View file

@ -23,6 +23,7 @@ import { isBelowOnboardingThreshold } from '@/WorkflowHelpers';
import { EEWorkflowController } from './workflows.controller.ee'; import { EEWorkflowController } from './workflows.controller.ee';
import { WorkflowsService } from './workflows.services'; import { WorkflowsService } from './workflows.services';
import { whereClause } from '@/UserManagement/UserManagementHelper'; import { whereClause } from '@/UserManagement/UserManagementHelper';
import { In } from 'typeorm';
export const workflowsController = express.Router(); export const workflowsController = express.Router();
@ -61,8 +62,11 @@ workflowsController.post(
const { tags: tagIds } = req.body; const { tags: tagIds } = req.body;
if (tagIds?.length && !config.getEnv('workflowTagsDisabled')) { if (tagIds?.length && !config.getEnv('workflowTagsDisabled')) {
newWorkflow.tags = await Db.collections.Tag.findByIds(tagIds, { newWorkflow.tags = await Db.collections.Tag.find({
select: ['id', 'name'], select: ['id', 'name'],
where: {
id: In(tagIds),
},
}); });
} }
@ -75,7 +79,7 @@ workflowsController.post(
await Db.transaction(async (transactionManager) => { await Db.transaction(async (transactionManager) => {
savedWorkflow = await transactionManager.save<WorkflowEntity>(newWorkflow); savedWorkflow = await transactionManager.save<WorkflowEntity>(newWorkflow);
const role = await Db.collections.Role.findOneOrFail({ const role = await Db.collections.Role.findOneByOrFail({
name: 'owner', name: 'owner',
scope: 'workflow', scope: 'workflow',
}); });

View file

@ -74,13 +74,12 @@ export class EEWorkflowsService extends WorkflowsService {
if (user.isPending) { if (user.isPending) {
return acc; return acc;
} }
acc.push( const entity: Partial<SharedWorkflow> = {
Db.collections.SharedWorkflow.create({ workflowId: workflow.id,
workflow, userId: user.id,
user, roleId: role?.id,
role, };
}), acc.push(Db.collections.SharedWorkflow.create(entity));
);
return acc; return acc;
}, []); }, []);

View file

@ -1,6 +1,6 @@
import { validate as jsonSchemaValidate } from 'jsonschema'; import { validate as jsonSchemaValidate } from 'jsonschema';
import { INode, IPinData, JsonObject, jsonParse, LoggerProxy, Workflow } from 'n8n-workflow'; import { INode, IPinData, JsonObject, jsonParse, LoggerProxy, Workflow } from 'n8n-workflow';
import { FindConditions, In } from 'typeorm'; import { FindOptionsWhere, In } from 'typeorm';
import pick from 'lodash.pick'; import pick from 'lodash.pick';
import { v4 as uuid } from 'uuid'; import { v4 as uuid } from 'uuid';
import * as ActiveWorkflowRunner from '@/ActiveWorkflowRunner'; import * as ActiveWorkflowRunner from '@/ActiveWorkflowRunner';
@ -48,8 +48,8 @@ export class WorkflowsService {
workflowId: string, workflowId: string,
relations: string[] = ['workflow'], relations: string[] = ['workflow'],
{ allowGlobalOwner } = { allowGlobalOwner: true }, { allowGlobalOwner } = { allowGlobalOwner: true },
): Promise<SharedWorkflow | undefined> { ): Promise<SharedWorkflow | null> {
const where: FindConditions<SharedWorkflow> = { workflowId }; const where: FindOptionsWhere<SharedWorkflow> = { workflowId };
// Omit user from where if the requesting user is the global // Omit user from where if the requesting user is the global
// owner. This allows the global owner to view and delete // owner. This allows the global owner to view and delete
@ -103,8 +103,8 @@ export class WorkflowsService {
return pinnedTriggers.find((pt) => pt.name === checkNodeName) ?? null; // partial execution return pinnedTriggers.find((pt) => pt.name === checkNodeName) ?? null; // partial execution
} }
static async get(workflow: Partial<WorkflowEntity>, options?: { relations: string[] }) { static async get(workflow: FindOptionsWhere<WorkflowEntity>, options?: { relations: string[] }) {
return Db.collections.Workflow.findOne(workflow, options); return Db.collections.Workflow.findOne({ where: workflow, relations: options?.relations });
} }
// Warning: this function is overridden by EE to disregard role list. // Warning: this function is overridden by EE to disregard role list.
@ -299,9 +299,12 @@ export class WorkflowsService {
// We sadly get nothing back from "update". Neither if it updated a record // We sadly get nothing back from "update". Neither if it updated a record
// nor the new value. So query now the hopefully updated entry. // nor the new value. So query now the hopefully updated entry.
const updatedWorkflow = await Db.collections.Workflow.findOne(workflowId, { relations }); const updatedWorkflow = await Db.collections.Workflow.findOne({
where: { id: workflowId },
relations,
});
if (updatedWorkflow === undefined) { if (updatedWorkflow === null) {
throw new ResponseHelper.BadRequestError( throw new ResponseHelper.BadRequestError(
`Workflow with ID "${workflowId}" could not be found to be updated.`, `Workflow with ID "${workflowId}" could not be found to be updated.`,
); );

View file

@ -6,19 +6,16 @@ import { CREDENTIALS_REPORT } from '@/audit/constants';
import { getRiskSection } from './utils'; import { getRiskSection } from './utils';
import * as testDb from '../shared/testDb'; import * as testDb from '../shared/testDb';
let testDbName = '';
beforeAll(async () => { beforeAll(async () => {
const initResult = await testDb.init(); await testDb.init();
testDbName = initResult.testDbName;
}); });
beforeEach(async () => { beforeEach(async () => {
await testDb.truncate(['Workflow', 'Credentials', 'Execution'], testDbName); await testDb.truncate(['Workflow', 'Credentials', 'Execution']);
}); });
afterAll(async () => { afterAll(async () => {
await testDb.terminate(testDbName); await testDb.terminate();
}); });
test('should report credentials not in any use', async () => { test('should report credentials not in any use', async () => {

View file

@ -9,19 +9,16 @@ import {
import { getRiskSection, saveManualTriggerWorkflow } from './utils'; import { getRiskSection, saveManualTriggerWorkflow } from './utils';
import * as testDb from '../shared/testDb'; import * as testDb from '../shared/testDb';
let testDbName = '';
beforeAll(async () => { beforeAll(async () => {
const initResult = await testDb.init(); await testDb.init();
testDbName = initResult.testDbName;
}); });
beforeEach(async () => { beforeEach(async () => {
await testDb.truncate(['Workflow'], testDbName); await testDb.truncate(['Workflow']);
}); });
afterAll(async () => { afterAll(async () => {
await testDb.terminate(testDbName); await testDb.terminate();
}); });
test('should report expressions in queries', async () => { test('should report expressions in queries', async () => {

View file

@ -5,19 +5,16 @@ import { FILESYSTEM_INTERACTION_NODE_TYPES, FILESYSTEM_REPORT } from '@/audit/co
import { getRiskSection, saveManualTriggerWorkflow } from './utils'; import { getRiskSection, saveManualTriggerWorkflow } from './utils';
import * as testDb from '../shared/testDb'; import * as testDb from '../shared/testDb';
let testDbName = '';
beforeAll(async () => { beforeAll(async () => {
const initResult = await testDb.init(); await testDb.init();
testDbName = initResult.testDbName;
}); });
beforeEach(async () => { beforeEach(async () => {
await testDb.truncate(['Workflow'], testDbName); await testDb.truncate(['Workflow']);
}); });
afterAll(async () => { afterAll(async () => {
await testDb.terminate(testDbName); await testDb.terminate();
}); });
test('should report filesystem interaction nodes', async () => { test('should report filesystem interaction nodes', async () => {

View file

@ -13,21 +13,18 @@ import * as testDb from '../shared/testDb';
import { toReportTitle } from '@/audit/utils'; import { toReportTitle } from '@/audit/utils';
import config from '@/config'; import config from '@/config';
let testDbName = '';
beforeAll(async () => { beforeAll(async () => {
const initResult = await testDb.init(); await testDb.init();
testDbName = initResult.testDbName;
simulateUpToDateInstance(); simulateUpToDateInstance();
}); });
beforeEach(async () => { beforeEach(async () => {
await testDb.truncate(['Workflow'], testDbName); await testDb.truncate(['Workflow']);
}); });
afterAll(async () => { afterAll(async () => {
await testDb.terminate(testDbName); await testDb.terminate();
}); });
test('should report webhook lacking authentication', async () => { test('should report webhook lacking authentication', async () => {

View file

@ -7,19 +7,16 @@ import { getRiskSection, MOCK_PACKAGE, saveManualTriggerWorkflow } from './utils
import * as testDb from '../shared/testDb'; import * as testDb from '../shared/testDb';
import { toReportTitle } from '@/audit/utils'; import { toReportTitle } from '@/audit/utils';
let testDbName = '';
beforeAll(async () => { beforeAll(async () => {
const initResult = await testDb.init(); await testDb.init();
testDbName = initResult.testDbName;
}); });
beforeEach(async () => { beforeEach(async () => {
await testDb.truncate(['Workflow'], testDbName); await testDb.truncate(['Workflow']);
}); });
afterAll(async () => { afterAll(async () => {
await testDb.terminate(testDbName); await testDb.terminate();
}); });
test('should report risky official nodes', async () => { test('should report risky official nodes', async () => {

View file

@ -11,15 +11,13 @@ import type { AuthAgent } from './shared/types';
import * as utils from './shared/utils'; import * as utils from './shared/utils';
let app: express.Application; let app: express.Application;
let testDbName = '';
let globalOwnerRole: Role; let globalOwnerRole: Role;
let globalMemberRole: Role; let globalMemberRole: Role;
let authAgent: AuthAgent; let authAgent: AuthAgent;
beforeAll(async () => { beforeAll(async () => {
app = await utils.initTestServer({ endpointGroups: ['auth'], applyAuth: true }); app = await utils.initTestServer({ endpointGroups: ['auth'], applyAuth: true });
const initResult = await testDb.init(); await testDb.init();
testDbName = initResult.testDbName;
globalOwnerRole = await testDb.getGlobalOwnerRole(); globalOwnerRole = await testDb.getGlobalOwnerRole();
globalMemberRole = await testDb.getGlobalMemberRole(); globalMemberRole = await testDb.getGlobalMemberRole();
@ -31,7 +29,7 @@ beforeAll(async () => {
}); });
beforeEach(async () => { beforeEach(async () => {
await testDb.truncate(['User'], testDbName); await testDb.truncate(['User']);
config.set('userManagement.isInstanceOwnerSetUp', true); config.set('userManagement.isInstanceOwnerSetUp', true);
@ -42,7 +40,7 @@ beforeEach(async () => {
}); });
afterAll(async () => { afterAll(async () => {
await testDb.terminate(testDbName); await testDb.terminate();
}); });
test('POST /login should log user in', async () => { test('POST /login should log user in', async () => {

View file

@ -12,7 +12,6 @@ import type { AuthAgent } from './shared/types';
import * as utils from './shared/utils'; import * as utils from './shared/utils';
let app: express.Application; let app: express.Application;
let testDbName = '';
let globalMemberRole: Role; let globalMemberRole: Role;
let authAgent: AuthAgent; let authAgent: AuthAgent;
@ -21,8 +20,7 @@ beforeAll(async () => {
applyAuth: true, applyAuth: true,
endpointGroups: ['me', 'auth', 'owner', 'users'], endpointGroups: ['me', 'auth', 'owner', 'users'],
}); });
const initResult = await testDb.init(); await testDb.init();
testDbName = initResult.testDbName;
globalMemberRole = await testDb.getGlobalMemberRole(); globalMemberRole = await testDb.getGlobalMemberRole();
@ -33,7 +31,7 @@ beforeAll(async () => {
}); });
afterAll(async () => { afterAll(async () => {
await testDb.terminate(testDbName); await testDb.terminate();
}); });
ROUTES_REQUIRING_AUTHENTICATION.concat(ROUTES_REQUIRING_AUTHORIZATION).forEach((route) => { ROUTES_REQUIRING_AUTHENTICATION.concat(ROUTES_REQUIRING_AUTHORIZATION).forEach((route) => {

View file

@ -7,23 +7,21 @@ import * as utils from '../shared/utils';
import * as testDb from '../shared/testDb'; import * as testDb from '../shared/testDb';
let app: express.Application; let app: express.Application;
let testDbName = '';
let globalOwnerRole: Role; let globalOwnerRole: Role;
beforeAll(async () => { beforeAll(async () => {
app = await utils.initTestServer({ endpointGroups: ['owner'], applyAuth: true }); app = await utils.initTestServer({ endpointGroups: ['owner'], applyAuth: true });
const initResult = await testDb.init(); await testDb.init();
testDbName = initResult.testDbName;
globalOwnerRole = await testDb.getGlobalOwnerRole(); globalOwnerRole = await testDb.getGlobalOwnerRole();
}); });
beforeEach(async () => { beforeEach(async () => {
await testDb.truncate(['User'], testDbName); await testDb.truncate(['User']);
}); });
afterAll(async () => { afterAll(async () => {
await testDb.terminate(testDbName); await testDb.terminate();
}); });
test('user-management:reset should reset DB to default user state', async () => { test('user-management:reset should reset DB to default user state', async () => {
@ -31,7 +29,7 @@ test('user-management:reset should reset DB to default user state', async () =>
await Reset.run(); await Reset.run();
const user = await Db.collections.User.findOne({ globalRole: globalOwnerRole }); const user = await Db.collections.User.findOneBy({ globalRoleId: globalOwnerRole.id });
if (!user) { if (!user) {
fail('No owner found after DB reset to default user state'); fail('No owner found after DB reset to default user state');

View file

@ -14,7 +14,6 @@ import * as utils from './shared/utils';
import type { IUser } from 'n8n-workflow'; import type { IUser } from 'n8n-workflow';
let app: express.Application; let app: express.Application;
let testDbName = '';
let globalOwnerRole: Role; let globalOwnerRole: Role;
let globalMemberRole: Role; let globalMemberRole: Role;
let credentialOwnerRole: Role; let credentialOwnerRole: Role;
@ -27,8 +26,7 @@ beforeAll(async () => {
endpointGroups: ['credentials'], endpointGroups: ['credentials'],
applyAuth: true, applyAuth: true,
}); });
const initResult = await testDb.init(); await testDb.init();
testDbName = initResult.testDbName;
utils.initConfigFile(); utils.initConfigFile();
@ -46,11 +44,11 @@ beforeAll(async () => {
}); });
beforeEach(async () => { beforeEach(async () => {
await testDb.truncate(['User', 'SharedCredentials', 'Credentials'], testDbName); await testDb.truncate(['User', 'SharedCredentials', 'Credentials']);
}); });
afterAll(async () => { afterAll(async () => {
await testDb.terminate(testDbName); await testDb.terminate();
}); });
// ---------------------------------------- // ----------------------------------------
@ -380,7 +378,7 @@ test('PUT /credentials/:id/share should share the credential with the provided u
const sharedCredentials = await Db.collections.SharedCredentials.find({ const sharedCredentials = await Db.collections.SharedCredentials.find({
relations: ['role'], relations: ['role'],
where: { credentials: savedCredential }, where: { credentialsId: savedCredential.id },
}); });
// check that sharings have been removed/added correctly // check that sharings have been removed/added correctly
@ -420,7 +418,7 @@ test('PUT /credentials/:id/share should share the credential with the provided u
// check that sharings got correctly set in DB // check that sharings got correctly set in DB
const sharedCredentials = await Db.collections.SharedCredentials.find({ const sharedCredentials = await Db.collections.SharedCredentials.find({
relations: ['role'], relations: ['role'],
where: { credentials: savedCredential, user: { id: In([...memberIds]) } }, where: { credentialsId: savedCredential.id, userId: In([...memberIds]) },
}); });
expect(sharedCredentials.length).toBe(memberIds.length); expect(sharedCredentials.length).toBe(memberIds.length);
@ -433,7 +431,7 @@ test('PUT /credentials/:id/share should share the credential with the provided u
// check that owner still exists // check that owner still exists
const ownerSharedCredential = await Db.collections.SharedCredentials.findOneOrFail({ const ownerSharedCredential = await Db.collections.SharedCredentials.findOneOrFail({
relations: ['role'], relations: ['role'],
where: { credentials: savedCredential, user: owner }, where: { credentialsId: savedCredential.id, userId: owner.id },
}); });
expect(ownerSharedCredential.role.name).toBe('owner'); expect(ownerSharedCredential.role.name).toBe('owner');
@ -475,7 +473,7 @@ test('PUT /credentials/:id/share should ignore pending sharee', async () => {
expect(response.statusCode).toBe(200); expect(response.statusCode).toBe(200);
const sharedCredentials = await Db.collections.SharedCredentials.find({ const sharedCredentials = await Db.collections.SharedCredentials.find({
where: { credentials: savedCredential }, where: { credentialsId: savedCredential.id },
}); });
expect(sharedCredentials).toHaveLength(1); expect(sharedCredentials).toHaveLength(1);
@ -493,7 +491,7 @@ test('PUT /credentials/:id/share should ignore non-existing sharee', async () =>
expect(response.statusCode).toBe(200); expect(response.statusCode).toBe(200);
const sharedCredentials = await Db.collections.SharedCredentials.find({ const sharedCredentials = await Db.collections.SharedCredentials.find({
where: { credentials: savedCredential }, where: { credentialsId: savedCredential.id },
}); });
expect(sharedCredentials).toHaveLength(1); expect(sharedCredentials).toHaveLength(1);
@ -535,7 +533,7 @@ test('PUT /credentials/:id/share should unshare the credential', async () => {
expect(response.statusCode).toBe(200); expect(response.statusCode).toBe(200);
const sharedCredentials = await Db.collections.SharedCredentials.find({ const sharedCredentials = await Db.collections.SharedCredentials.find({
where: { credentials: savedCredential }, where: { credentialsId: savedCredential.id },
}); });
expect(sharedCredentials).toHaveLength(1); expect(sharedCredentials).toHaveLength(1);

View file

@ -19,7 +19,6 @@ const mockIsCredentialsSharingEnabled = jest.spyOn(UserManagementHelpers, 'isSha
mockIsCredentialsSharingEnabled.mockReturnValue(false); mockIsCredentialsSharingEnabled.mockReturnValue(false);
let app: express.Application; let app: express.Application;
let testDbName = '';
let globalOwnerRole: Role; let globalOwnerRole: Role;
let globalMemberRole: Role; let globalMemberRole: Role;
let saveCredential: SaveCredentialFunction; let saveCredential: SaveCredentialFunction;
@ -30,8 +29,7 @@ beforeAll(async () => {
endpointGroups: ['credentials'], endpointGroups: ['credentials'],
applyAuth: true, applyAuth: true,
}); });
const initResult = await testDb.init(); await testDb.init();
testDbName = initResult.testDbName;
utils.initConfigFile(); utils.initConfigFile();
@ -48,11 +46,11 @@ beforeAll(async () => {
}); });
beforeEach(async () => { beforeEach(async () => {
await testDb.truncate(['User', 'SharedCredentials', 'Credentials'], testDbName); await testDb.truncate(['User', 'SharedCredentials', 'Credentials']);
}); });
afterAll(async () => { afterAll(async () => {
await testDb.terminate(testDbName); await testDb.terminate();
}); });
// ---------------------------------------- // ----------------------------------------
@ -124,7 +122,7 @@ test('POST /credentials should create cred', async () => {
expect(nodesAccess[0].nodeType).toBe(payload.nodesAccess[0].nodeType); expect(nodesAccess[0].nodeType).toBe(payload.nodesAccess[0].nodeType);
expect(encryptedData).not.toBe(payload.data); expect(encryptedData).not.toBe(payload.data);
const credential = await Db.collections.Credentials.findOneOrFail(id); const credential = await Db.collections.Credentials.findOneByOrFail({ id });
expect(credential.name).toBe(payload.name); expect(credential.name).toBe(payload.name);
expect(credential.type).toBe(payload.type); expect(credential.type).toBe(payload.type);
@ -133,7 +131,7 @@ test('POST /credentials should create cred', async () => {
const sharedCredential = await Db.collections.SharedCredentials.findOneOrFail({ const sharedCredential = await Db.collections.SharedCredentials.findOneOrFail({
relations: ['user', 'credentials'], relations: ['user', 'credentials'],
where: { credentials: credential }, where: { credentialsId: credential.id },
}); });
expect(sharedCredential.user.id).toBe(ownerShell.id); expect(sharedCredential.user.id).toBe(ownerShell.id);
@ -191,13 +189,13 @@ test('DELETE /credentials/:id should delete owned cred for owner', async () => {
expect(response.statusCode).toBe(200); expect(response.statusCode).toBe(200);
expect(response.body).toEqual({ data: true }); expect(response.body).toEqual({ data: true });
const deletedCredential = await Db.collections.Credentials.findOne(savedCredential.id); const deletedCredential = await Db.collections.Credentials.findOneBy({ id: savedCredential.id });
expect(deletedCredential).toBeUndefined(); // deleted expect(deletedCredential).toBeNull(); // deleted
const deletedSharedCredential = await Db.collections.SharedCredentials.findOne(); const deletedSharedCredential = await Db.collections.SharedCredentials.findOneBy({});
expect(deletedSharedCredential).toBeUndefined(); // deleted expect(deletedSharedCredential).toBeNull(); // deleted
}); });
test('DELETE /credentials/:id should delete non-owned cred for owner', async () => { test('DELETE /credentials/:id should delete non-owned cred for owner', async () => {
@ -210,13 +208,13 @@ test('DELETE /credentials/:id should delete non-owned cred for owner', async ()
expect(response.statusCode).toBe(200); expect(response.statusCode).toBe(200);
expect(response.body).toEqual({ data: true }); expect(response.body).toEqual({ data: true });
const deletedCredential = await Db.collections.Credentials.findOne(savedCredential.id); const deletedCredential = await Db.collections.Credentials.findOneBy({ id: savedCredential.id });
expect(deletedCredential).toBeUndefined(); // deleted expect(deletedCredential).toBeNull(); // deleted
const deletedSharedCredential = await Db.collections.SharedCredentials.findOne(); const deletedSharedCredential = await Db.collections.SharedCredentials.findOneBy({});
expect(deletedSharedCredential).toBeUndefined(); // deleted expect(deletedSharedCredential).toBeNull(); // deleted
}); });
test('DELETE /credentials/:id should delete owned cred for member', async () => { test('DELETE /credentials/:id should delete owned cred for member', async () => {
@ -228,13 +226,13 @@ test('DELETE /credentials/:id should delete owned cred for member', async () =>
expect(response.statusCode).toBe(200); expect(response.statusCode).toBe(200);
expect(response.body).toEqual({ data: true }); expect(response.body).toEqual({ data: true });
const deletedCredential = await Db.collections.Credentials.findOne(savedCredential.id); const deletedCredential = await Db.collections.Credentials.findOneBy({ id: savedCredential.id });
expect(deletedCredential).toBeUndefined(); // deleted expect(deletedCredential).toBeNull(); // deleted
const deletedSharedCredential = await Db.collections.SharedCredentials.findOne(); const deletedSharedCredential = await Db.collections.SharedCredentials.findOneBy({});
expect(deletedSharedCredential).toBeUndefined(); // deleted expect(deletedSharedCredential).toBeNull(); // deleted
}); });
test('DELETE /credentials/:id should not delete non-owned cred for member', async () => { test('DELETE /credentials/:id should not delete non-owned cred for member', async () => {
@ -246,11 +244,11 @@ test('DELETE /credentials/:id should not delete non-owned cred for member', asyn
expect(response.statusCode).toBe(404); expect(response.statusCode).toBe(404);
const shellCredential = await Db.collections.Credentials.findOne(savedCredential.id); const shellCredential = await Db.collections.Credentials.findOneBy({ id: savedCredential.id });
expect(shellCredential).toBeDefined(); // not deleted expect(shellCredential).toBeDefined(); // not deleted
const deletedSharedCredential = await Db.collections.SharedCredentials.findOne(); const deletedSharedCredential = await Db.collections.SharedCredentials.findOneBy({});
expect(deletedSharedCredential).toBeDefined(); // not deleted expect(deletedSharedCredential).toBeDefined(); // not deleted
}); });
@ -285,7 +283,7 @@ test('PATCH /credentials/:id should update owned cred for owner', async () => {
expect(encryptedData).not.toBe(patchPayload.data); expect(encryptedData).not.toBe(patchPayload.data);
const credential = await Db.collections.Credentials.findOneOrFail(id); const credential = await Db.collections.Credentials.findOneByOrFail({ id });
expect(credential.name).toBe(patchPayload.name); expect(credential.name).toBe(patchPayload.name);
expect(credential.type).toBe(patchPayload.type); expect(credential.type).toBe(patchPayload.type);
@ -294,7 +292,7 @@ test('PATCH /credentials/:id should update owned cred for owner', async () => {
const sharedCredential = await Db.collections.SharedCredentials.findOneOrFail({ const sharedCredential = await Db.collections.SharedCredentials.findOneOrFail({
relations: ['credentials'], relations: ['credentials'],
where: { credentials: credential }, where: { credentialsId: credential.id },
}); });
expect(sharedCredential.credentials.name).toBe(patchPayload.name); // updated expect(sharedCredential.credentials.name).toBe(patchPayload.name); // updated
@ -324,7 +322,7 @@ test('PATCH /credentials/:id should update non-owned cred for owner', async () =
expect(encryptedData).not.toBe(patchPayload.data); expect(encryptedData).not.toBe(patchPayload.data);
const credential = await Db.collections.Credentials.findOneOrFail(id); const credential = await Db.collections.Credentials.findOneByOrFail({ id });
expect(credential.name).toBe(patchPayload.name); expect(credential.name).toBe(patchPayload.name);
expect(credential.type).toBe(patchPayload.type); expect(credential.type).toBe(patchPayload.type);
@ -333,7 +331,7 @@ test('PATCH /credentials/:id should update non-owned cred for owner', async () =
const sharedCredential = await Db.collections.SharedCredentials.findOneOrFail({ const sharedCredential = await Db.collections.SharedCredentials.findOneOrFail({
relations: ['credentials'], relations: ['credentials'],
where: { credentials: credential }, where: { credentialsId: credential.id },
}); });
expect(sharedCredential.credentials.name).toBe(patchPayload.name); // updated expect(sharedCredential.credentials.name).toBe(patchPayload.name); // updated
@ -362,7 +360,7 @@ test('PATCH /credentials/:id should update owned cred for member', async () => {
expect(encryptedData).not.toBe(patchPayload.data); expect(encryptedData).not.toBe(patchPayload.data);
const credential = await Db.collections.Credentials.findOneOrFail(id); const credential = await Db.collections.Credentials.findOneByOrFail({ id });
expect(credential.name).toBe(patchPayload.name); expect(credential.name).toBe(patchPayload.name);
expect(credential.type).toBe(patchPayload.type); expect(credential.type).toBe(patchPayload.type);
@ -371,7 +369,7 @@ test('PATCH /credentials/:id should update owned cred for member', async () => {
const sharedCredential = await Db.collections.SharedCredentials.findOneOrFail({ const sharedCredential = await Db.collections.SharedCredentials.findOneOrFail({
relations: ['credentials'], relations: ['credentials'],
where: { credentials: credential }, where: { credentialsId: credential.id },
}); });
expect(sharedCredential.credentials.name).toBe(patchPayload.name); // updated expect(sharedCredential.credentials.name).toBe(patchPayload.name); // updated
@ -389,7 +387,9 @@ test('PATCH /credentials/:id should not update non-owned cred for member', async
expect(response.statusCode).toBe(404); expect(response.statusCode).toBe(404);
const shellCredential = await Db.collections.Credentials.findOneOrFail(savedCredential.id); const shellCredential = await Db.collections.Credentials.findOneByOrFail({
id: savedCredential.id,
});
expect(shellCredential.name).not.toBe(patchPayload.name); // not updated expect(shellCredential.name).not.toBe(patchPayload.name); // not updated
}); });

View file

@ -16,11 +16,11 @@ import {
} from 'n8n-workflow'; } from 'n8n-workflow';
import { eventBus } from '@/eventbus'; import { eventBus } from '@/eventbus';
import { SuperAgentTest } from 'supertest'; import { SuperAgentTest } from 'supertest';
import { EventMessageGeneric } from '../../src/eventbus/EventMessageClasses/EventMessageGeneric'; import { EventMessageGeneric } from '@/eventbus/EventMessageClasses/EventMessageGeneric';
import { MessageEventBusDestinationSyslog } from '../../src/eventbus/MessageEventBusDestination/MessageEventBusDestinationSyslog.ee'; import { MessageEventBusDestinationSyslog } from '@/eventbus/MessageEventBusDestination/MessageEventBusDestinationSyslog.ee';
import { MessageEventBusDestinationWebhook } from '../../src/eventbus/MessageEventBusDestination/MessageEventBusDestinationWebhook.ee'; import { MessageEventBusDestinationWebhook } from '@/eventbus/MessageEventBusDestination/MessageEventBusDestinationWebhook.ee';
import { MessageEventBusDestinationSentry } from '../../src/eventbus/MessageEventBusDestination/MessageEventBusDestinationSentry.ee'; import { MessageEventBusDestinationSentry } from '@/eventbus/MessageEventBusDestination/MessageEventBusDestinationSentry.ee';
import { EventMessageAudit } from '../../src/eventbus/EventMessageClasses/EventMessageAudit'; import { EventMessageAudit } from '@/eventbus/EventMessageClasses/EventMessageAudit';
import { v4 as uuid } from 'uuid'; import { v4 as uuid } from 'uuid';
jest.unmock('@/eventbus/MessageEventBus/MessageEventBus'); jest.unmock('@/eventbus/MessageEventBus/MessageEventBus');
@ -30,7 +30,6 @@ jest.mock('syslog-client');
const mockedSyslog = syslog as jest.Mocked<typeof syslog>; const mockedSyslog = syslog as jest.Mocked<typeof syslog>;
let app: express.Application; let app: express.Application;
let testDbName = '';
let globalOwnerRole: Role; let globalOwnerRole: Role;
let owner: User; let owner: User;
let unAuthOwnerAgent: SuperAgentTest; let unAuthOwnerAgent: SuperAgentTest;
@ -82,8 +81,7 @@ async function confirmIdSent(id: string) {
} }
beforeAll(async () => { beforeAll(async () => {
const initResult = await testDb.init(); await testDb.init();
testDbName = initResult.testDbName;
globalOwnerRole = await testDb.getGlobalOwnerRole(); globalOwnerRole = await testDb.getGlobalOwnerRole();
owner = await testDb.createUser({ globalRole: globalOwnerRole }); owner = await testDb.createUser({ globalRole: globalOwnerRole });
@ -121,7 +119,7 @@ beforeEach(async () => {
afterAll(async () => { afterAll(async () => {
jest.mock('@/eventbus/MessageEventBus/MessageEventBus'); jest.mock('@/eventbus/MessageEventBus/MessageEventBus');
await testDb.terminate(testDbName); await testDb.terminate();
await eventBus.close(); await eventBus.close();
}); });

View file

@ -13,7 +13,6 @@ const MOCK_RENEW_OFFSET = 259200;
const MOCK_INSTANCE_ID = 'instance-id'; const MOCK_INSTANCE_ID = 'instance-id';
let app: express.Application; let app: express.Application;
let testDbName = '';
let globalOwnerRole: Role; let globalOwnerRole: Role;
let globalMemberRole: Role; let globalMemberRole: Role;
let authAgent: AuthAgent; let authAgent: AuthAgent;
@ -21,8 +20,7 @@ let license: License;
beforeAll(async () => { beforeAll(async () => {
app = await utils.initTestServer({ endpointGroups: ['license'], applyAuth: true }); app = await utils.initTestServer({ endpointGroups: ['license'], applyAuth: true });
const initResult = await testDb.init(); await testDb.init();
testDbName = initResult.testDbName;
globalOwnerRole = await testDb.getGlobalOwnerRole(); globalOwnerRole = await testDb.getGlobalOwnerRole();
globalMemberRole = await testDb.getGlobalMemberRole(); globalMemberRole = await testDb.getGlobalMemberRole();
@ -43,11 +41,11 @@ beforeEach(async () => {
}); });
afterEach(async () => { afterEach(async () => {
await testDb.truncate(['Settings'], testDbName); await testDb.truncate(['Settings']);
}); });
afterAll(async () => { afterAll(async () => {
await testDb.terminate(testDbName); await testDb.terminate();
}); });
test('GET /license should return license information to the instance owner', async () => { test('GET /license should return license information to the instance owner', async () => {

View file

@ -18,15 +18,13 @@ import type { AuthAgent } from './shared/types';
import * as utils from './shared/utils'; import * as utils from './shared/utils';
let app: express.Application; let app: express.Application;
let testDbName = '';
let globalOwnerRole: Role; let globalOwnerRole: Role;
let globalMemberRole: Role; let globalMemberRole: Role;
let authAgent: AuthAgent; let authAgent: AuthAgent;
beforeAll(async () => { beforeAll(async () => {
app = await utils.initTestServer({ endpointGroups: ['me'], applyAuth: true }); app = await utils.initTestServer({ endpointGroups: ['me'], applyAuth: true });
const initResult = await testDb.init(); await testDb.init();
testDbName = initResult.testDbName;
globalOwnerRole = await testDb.getGlobalOwnerRole(); globalOwnerRole = await testDb.getGlobalOwnerRole();
globalMemberRole = await testDb.getGlobalMemberRole(); globalMemberRole = await testDb.getGlobalMemberRole();
@ -38,12 +36,12 @@ beforeAll(async () => {
}); });
afterAll(async () => { afterAll(async () => {
await testDb.terminate(testDbName); await testDb.terminate();
}); });
describe('Owner shell', () => { describe('Owner shell', () => {
beforeEach(async () => { beforeEach(async () => {
await testDb.truncate(['User'], testDbName); await testDb.truncate(['User']);
}); });
test('GET /me should return sanitized owner shell', async () => { test('GET /me should return sanitized owner shell', async () => {
@ -113,7 +111,7 @@ describe('Owner shell', () => {
expect(globalRole.scope).toBe('global'); expect(globalRole.scope).toBe('global');
expect(apiKey).toBeUndefined(); expect(apiKey).toBeUndefined();
const storedOwnerShell = await Db.collections.User.findOneOrFail(id); const storedOwnerShell = await Db.collections.User.findOneByOrFail({ id });
expect(storedOwnerShell.email).toBe(validPayload.email.toLowerCase()); expect(storedOwnerShell.email).toBe(validPayload.email.toLowerCase());
expect(storedOwnerShell.firstName).toBe(validPayload.firstName); expect(storedOwnerShell.firstName).toBe(validPayload.firstName);
@ -129,7 +127,7 @@ describe('Owner shell', () => {
const response = await authOwnerShellAgent.patch('/me').send(invalidPayload); const response = await authOwnerShellAgent.patch('/me').send(invalidPayload);
expect(response.statusCode).toBe(400); expect(response.statusCode).toBe(400);
const storedOwnerShell = await Db.collections.User.findOneOrFail(); const storedOwnerShell = await Db.collections.User.findOneByOrFail({});
expect(storedOwnerShell.email).toBeNull(); expect(storedOwnerShell.email).toBeNull();
expect(storedOwnerShell.firstName).toBeNull(); expect(storedOwnerShell.firstName).toBeNull();
expect(storedOwnerShell.lastName).toBeNull(); expect(storedOwnerShell.lastName).toBeNull();
@ -152,7 +150,7 @@ describe('Owner shell', () => {
const response = await authOwnerShellAgent.patch('/me/password').send(payload); const response = await authOwnerShellAgent.patch('/me/password').send(payload);
expect([400, 500].includes(response.statusCode)).toBe(true); expect([400, 500].includes(response.statusCode)).toBe(true);
const storedMember = await Db.collections.User.findOneOrFail(); const storedMember = await Db.collections.User.findOneByOrFail({});
if (payload.newPassword) { if (payload.newPassword) {
expect(storedMember.password).not.toBe(payload.newPassword); expect(storedMember.password).not.toBe(payload.newPassword);
@ -164,7 +162,7 @@ describe('Owner shell', () => {
}), }),
); );
const storedOwnerShell = await Db.collections.User.findOneOrFail(); const storedOwnerShell = await Db.collections.User.findOneByOrFail({});
expect(storedOwnerShell.password).toBeNull(); expect(storedOwnerShell.password).toBeNull();
}); });
@ -241,7 +239,7 @@ describe('Member', () => {
}); });
afterEach(async () => { afterEach(async () => {
await testDb.truncate(['User'], testDbName); await testDb.truncate(['User']);
}); });
test('GET /me should return sanitized member', async () => { test('GET /me should return sanitized member', async () => {
@ -311,7 +309,7 @@ describe('Member', () => {
expect(globalRole.scope).toBe('global'); expect(globalRole.scope).toBe('global');
expect(apiKey).toBeUndefined(); expect(apiKey).toBeUndefined();
const storedMember = await Db.collections.User.findOneOrFail(id); const storedMember = await Db.collections.User.findOneByOrFail({ id });
expect(storedMember.email).toBe(validPayload.email.toLowerCase()); expect(storedMember.email).toBe(validPayload.email.toLowerCase());
expect(storedMember.firstName).toBe(validPayload.firstName); expect(storedMember.firstName).toBe(validPayload.firstName);
@ -327,7 +325,7 @@ describe('Member', () => {
const response = await authMemberAgent.patch('/me').send(invalidPayload); const response = await authMemberAgent.patch('/me').send(invalidPayload);
expect(response.statusCode).toBe(400); expect(response.statusCode).toBe(400);
const storedMember = await Db.collections.User.findOneOrFail(); const storedMember = await Db.collections.User.findOneByOrFail({});
expect(storedMember.email).toBe(member.email); expect(storedMember.email).toBe(member.email);
expect(storedMember.firstName).toBe(member.firstName); expect(storedMember.firstName).toBe(member.firstName);
expect(storedMember.lastName).toBe(member.lastName); expect(storedMember.lastName).toBe(member.lastName);
@ -350,7 +348,7 @@ describe('Member', () => {
expect(response.statusCode).toBe(200); expect(response.statusCode).toBe(200);
expect(response.body).toEqual(SUCCESS_RESPONSE_BODY); expect(response.body).toEqual(SUCCESS_RESPONSE_BODY);
const storedMember = await Db.collections.User.findOneOrFail(); const storedMember = await Db.collections.User.findOneByOrFail({});
expect(storedMember.password).not.toBe(member.password); expect(storedMember.password).not.toBe(member.password);
expect(storedMember.password).not.toBe(validPayload.newPassword); expect(storedMember.password).not.toBe(validPayload.newPassword);
}); });
@ -363,7 +361,7 @@ describe('Member', () => {
const response = await authMemberAgent.patch('/me/password').send(payload); const response = await authMemberAgent.patch('/me/password').send(payload);
expect([400, 500].includes(response.statusCode)).toBe(true); expect([400, 500].includes(response.statusCode)).toBe(true);
const storedMember = await Db.collections.User.findOneOrFail(); const storedMember = await Db.collections.User.findOneByOrFail({});
if (payload.newPassword) { if (payload.newPassword) {
expect(storedMember.password).not.toBe(payload.newPassword); expect(storedMember.password).not.toBe(payload.newPassword);
@ -385,7 +383,9 @@ describe('Member', () => {
expect(response.statusCode).toBe(200); expect(response.statusCode).toBe(200);
expect(response.body).toEqual(SUCCESS_RESPONSE_BODY); expect(response.body).toEqual(SUCCESS_RESPONSE_BODY);
const { personalizationAnswers: storedAnswers } = await Db.collections.User.findOneOrFail(); const { personalizationAnswers: storedAnswers } = await Db.collections.User.findOneByOrFail(
{},
);
expect(storedAnswers).toEqual(validPayload); expect(storedAnswers).toEqual(validPayload);
} }
@ -403,7 +403,7 @@ describe('Member', () => {
expect(response.body.data.apiKey).toBeDefined(); expect(response.body.data.apiKey).toBeDefined();
expect(response.body.data.apiKey).not.toBeNull(); expect(response.body.data.apiKey).not.toBeNull();
const storedMember = await Db.collections.User.findOneOrFail(member.id); const storedMember = await Db.collections.User.findOneByOrFail({ id: member.id });
expect(storedMember.apiKey).toEqual(response.body.data.apiKey); expect(storedMember.apiKey).toEqual(response.body.data.apiKey);
}); });
@ -430,7 +430,7 @@ describe('Member', () => {
expect(response.statusCode).toBe(200); expect(response.statusCode).toBe(200);
const storedMember = await Db.collections.User.findOneOrFail(member.id); const storedMember = await Db.collections.User.findOneByOrFail({ id: member.id });
expect(storedMember.apiKey).toBeNull(); expect(storedMember.apiKey).toBeNull();
}); });
@ -442,7 +442,7 @@ describe('Owner', () => {
}); });
afterEach(async () => { afterEach(async () => {
await testDb.truncate(['User'], testDbName); await testDb.truncate(['User']);
}); });
test('GET /me should return sanitized owner', async () => { test('GET /me should return sanitized owner', async () => {
@ -512,7 +512,7 @@ describe('Owner', () => {
expect(globalRole.scope).toBe('global'); expect(globalRole.scope).toBe('global');
expect(apiKey).toBeUndefined(); expect(apiKey).toBeUndefined();
const storedOwner = await Db.collections.User.findOneOrFail(id); const storedOwner = await Db.collections.User.findOneByOrFail({ id });
expect(storedOwner.email).toBe(validPayload.email.toLowerCase()); expect(storedOwner.email).toBe(validPayload.email.toLowerCase());
expect(storedOwner.firstName).toBe(validPayload.firstName); expect(storedOwner.firstName).toBe(validPayload.firstName);

View file

@ -43,14 +43,12 @@ jest.mock('@/CommunityNodes/packageModel', () => {
const mockedEmptyPackage = mocked(utils.emptyPackage); const mockedEmptyPackage = mocked(utils.emptyPackage);
let app: express.Application; let app: express.Application;
let testDbName = '';
let globalOwnerRole: Role; let globalOwnerRole: Role;
let authAgent: AuthAgent; let authAgent: AuthAgent;
beforeAll(async () => { beforeAll(async () => {
app = await utils.initTestServer({ endpointGroups: ['nodes'], applyAuth: true }); app = await utils.initTestServer({ endpointGroups: ['nodes'], applyAuth: true });
const initResult = await testDb.init(); await testDb.init();
testDbName = initResult.testDbName;
globalOwnerRole = await testDb.getGlobalOwnerRole(); globalOwnerRole = await testDb.getGlobalOwnerRole();
@ -62,14 +60,14 @@ beforeAll(async () => {
}); });
beforeEach(async () => { beforeEach(async () => {
await testDb.truncate(['InstalledNodes', 'InstalledPackages', 'User'], testDbName); await testDb.truncate(['InstalledNodes', 'InstalledPackages', 'User']);
mocked(executeCommand).mockReset(); mocked(executeCommand).mockReset();
mocked(findInstalledPackage).mockReset(); mocked(findInstalledPackage).mockReset();
}); });
afterAll(async () => { afterAll(async () => {
await testDb.terminate(testDbName); await testDb.terminate();
}); });
/** /**

View file

@ -15,14 +15,12 @@ import type { AuthAgent } from './shared/types';
import * as utils from './shared/utils'; import * as utils from './shared/utils';
let app: express.Application; let app: express.Application;
let testDbName = '';
let globalOwnerRole: Role; let globalOwnerRole: Role;
let authAgent: AuthAgent; let authAgent: AuthAgent;
beforeAll(async () => { beforeAll(async () => {
app = await utils.initTestServer({ endpointGroups: ['owner'], applyAuth: true }); app = await utils.initTestServer({ endpointGroups: ['owner'], applyAuth: true });
const initResult = await testDb.init(); await testDb.init();
testDbName = initResult.testDbName;
globalOwnerRole = await testDb.getGlobalOwnerRole(); globalOwnerRole = await testDb.getGlobalOwnerRole();
@ -37,11 +35,11 @@ beforeEach(async () => {
}); });
afterEach(async () => { afterEach(async () => {
await testDb.truncate(['User'], testDbName); await testDb.truncate(['User']);
}); });
afterAll(async () => { afterAll(async () => {
await testDb.terminate(testDbName); await testDb.terminate();
}); });
test('POST /owner should create owner and enable isInstanceOwnerSetUp', async () => { test('POST /owner should create owner and enable isInstanceOwnerSetUp', async () => {
@ -83,7 +81,7 @@ test('POST /owner should create owner and enable isInstanceOwnerSetUp', async ()
expect(globalRole.scope).toBe('global'); expect(globalRole.scope).toBe('global');
expect(apiKey).toBeUndefined(); expect(apiKey).toBeUndefined();
const storedOwner = await Db.collections.User.findOneOrFail(id); const storedOwner = await Db.collections.User.findOneByOrFail({ id });
expect(storedOwner.password).not.toBe(newOwnerData.password); expect(storedOwner.password).not.toBe(newOwnerData.password);
expect(storedOwner.email).toBe(newOwnerData.email); expect(storedOwner.email).toBe(newOwnerData.email);
expect(storedOwner.firstName).toBe(newOwnerData.firstName); expect(storedOwner.firstName).toBe(newOwnerData.firstName);
@ -115,7 +113,7 @@ test('POST /owner should create owner with lowercased email', async () => {
expect(id).toBe(ownerShell.id); expect(id).toBe(ownerShell.id);
expect(email).toBe(newOwnerData.email.toLowerCase()); expect(email).toBe(newOwnerData.email.toLowerCase());
const storedOwner = await Db.collections.User.findOneOrFail(id); const storedOwner = await Db.collections.User.findOneByOrFail({ id });
expect(storedOwner.email).toBe(newOwnerData.email.toLowerCase()); expect(storedOwner.email).toBe(newOwnerData.email.toLowerCase());
}); });
@ -141,7 +139,7 @@ test('POST /owner/skip-setup should persist skipping setup to the DB', async ()
const skipConfig = config.getEnv('userManagement.skipInstanceOwnerSetup'); const skipConfig = config.getEnv('userManagement.skipInstanceOwnerSetup');
expect(skipConfig).toBe(true); expect(skipConfig).toBe(true);
const { value } = await Db.collections.Settings.findOneOrFail({ const { value } = await Db.collections.Settings.findOneByOrFail({
key: 'userManagement.skipInstanceOwnerSetup', key: 'userManagement.skipInstanceOwnerSetup',
}); });
expect(value).toBe('true'); expect(value).toBe('true');

View file

@ -17,14 +17,12 @@ import type { Role } from '@db/entities/Role';
jest.mock('@/UserManagement/email/NodeMailer'); jest.mock('@/UserManagement/email/NodeMailer');
let app: express.Application; let app: express.Application;
let testDbName = '';
let globalOwnerRole: Role; let globalOwnerRole: Role;
let globalMemberRole: Role; let globalMemberRole: Role;
beforeAll(async () => { beforeAll(async () => {
app = await utils.initTestServer({ endpointGroups: ['passwordReset'], applyAuth: true }); app = await utils.initTestServer({ endpointGroups: ['passwordReset'], applyAuth: true });
const initResult = await testDb.init(); await testDb.init();
testDbName = initResult.testDbName;
globalOwnerRole = await testDb.getGlobalOwnerRole(); globalOwnerRole = await testDb.getGlobalOwnerRole();
globalMemberRole = await testDb.getGlobalMemberRole(); globalMemberRole = await testDb.getGlobalMemberRole();
@ -34,7 +32,7 @@ beforeAll(async () => {
}); });
beforeEach(async () => { beforeEach(async () => {
await testDb.truncate(['User'], testDbName); await testDb.truncate(['User']);
jest.mock('@/config'); jest.mock('@/config');
@ -43,7 +41,7 @@ beforeEach(async () => {
}); });
afterAll(async () => { afterAll(async () => {
await testDb.terminate(testDbName); await testDb.terminate();
}); });
test('POST /forgot-password should send password reset email', async () => { test('POST /forgot-password should send password reset email', async () => {
@ -64,7 +62,7 @@ test('POST /forgot-password should send password reset email', async () => {
expect(response.statusCode).toBe(200); expect(response.statusCode).toBe(200);
expect(response.body).toEqual({}); expect(response.body).toEqual({});
const user = await Db.collections.User.findOneOrFail({ email: payload.email }); const user = await Db.collections.User.findOneByOrFail({ email: payload.email });
expect(user.resetPasswordToken).toBeDefined(); expect(user.resetPasswordToken).toBeDefined();
expect(user.resetPasswordTokenExpiration).toBeGreaterThan(Math.ceil(Date.now() / 1000)); expect(user.resetPasswordTokenExpiration).toBeGreaterThan(Math.ceil(Date.now() / 1000));
}), }),
@ -80,7 +78,7 @@ test('POST /forgot-password should fail if emailing is not set up', async () =>
expect(response.statusCode).toBe(500); expect(response.statusCode).toBe(500);
const storedOwner = await Db.collections.User.findOneOrFail({ email: owner.email }); const storedOwner = await Db.collections.User.findOneByOrFail({ email: owner.email });
expect(storedOwner.resetPasswordToken).toBeNull(); expect(storedOwner.resetPasswordToken).toBeNull();
}); });
@ -104,7 +102,7 @@ test('POST /forgot-password should fail with invalid inputs', async () => {
const response = await authlessAgent.post('/forgot-password').send(invalidPayload); const response = await authlessAgent.post('/forgot-password').send(invalidPayload);
expect(response.statusCode).toBe(400); expect(response.statusCode).toBe(400);
const storedOwner = await Db.collections.User.findOneOrFail({ email: owner.email }); const storedOwner = await Db.collections.User.findOneByOrFail({ email: owner.email });
expect(storedOwner.resetPasswordToken).toBeNull(); expect(storedOwner.resetPasswordToken).toBeNull();
}), }),
); );
@ -218,7 +216,7 @@ test('POST /change-password should succeed with valid inputs', async () => {
const authToken = utils.getAuthToken(response); const authToken = utils.getAuthToken(response);
expect(authToken).toBeDefined(); expect(authToken).toBeDefined();
const { password: storedPassword } = await Db.collections.User.findOneOrFail(owner.id); const { password: storedPassword } = await Db.collections.User.findOneByOrFail({ id: owner.id });
const comparisonResult = await compare(passwordToStore, storedPassword); const comparisonResult = await compare(passwordToStore, storedPassword);
expect(comparisonResult).toBe(true); expect(comparisonResult).toBe(true);
@ -262,7 +260,7 @@ test('POST /change-password should fail with invalid inputs', async () => {
const response = await authlessAgent.post('/change-password').query(invalidPayload); const response = await authlessAgent.post('/change-password').query(invalidPayload);
expect(response.statusCode).toBe(400); expect(response.statusCode).toBe(400);
const { password: storedPassword } = await Db.collections.User.findOneOrFail(); const { password: storedPassword } = await Db.collections.User.findOneByOrFail({});
expect(owner.password).toBe(storedPassword); expect(owner.password).toBe(storedPassword);
}), }),
); );

View file

@ -11,7 +11,6 @@ import type { CredentialPayload, SaveCredentialFunction } from '../shared/types'
import * as testDb from '../shared/testDb'; import * as testDb from '../shared/testDb';
let app: express.Application; let app: express.Application;
let testDbName = '';
let globalOwnerRole: Role; let globalOwnerRole: Role;
let globalMemberRole: Role; let globalMemberRole: Role;
let credentialOwnerRole: Role; let credentialOwnerRole: Role;
@ -20,8 +19,7 @@ let saveCredential: SaveCredentialFunction;
beforeAll(async () => { beforeAll(async () => {
app = await utils.initTestServer({ endpointGroups: ['publicApi'], applyAuth: false }); app = await utils.initTestServer({ endpointGroups: ['publicApi'], applyAuth: false });
const initResult = await testDb.init(); await testDb.init();
testDbName = initResult.testDbName;
utils.initConfigFile(); utils.initConfigFile();
@ -40,11 +38,11 @@ beforeAll(async () => {
}); });
beforeEach(async () => { beforeEach(async () => {
await testDb.truncate(['User', 'SharedCredentials', 'Credentials'], testDbName); await testDb.truncate(['User', 'SharedCredentials', 'Credentials']);
}); });
afterAll(async () => { afterAll(async () => {
await testDb.terminate(testDbName); await testDb.terminate();
}); });
test('POST /credentials should create credentials', async () => { test('POST /credentials should create credentials', async () => {
@ -76,15 +74,15 @@ test('POST /credentials should create credentials', async () => {
expect(name).toBe(payload.name); expect(name).toBe(payload.name);
expect(type).toBe(payload.type); expect(type).toBe(payload.type);
const credential = await Db.collections.Credentials!.findOneOrFail(id); const credential = await Db.collections.Credentials.findOneByOrFail({ id });
expect(credential.name).toBe(payload.name); expect(credential.name).toBe(payload.name);
expect(credential.type).toBe(payload.type); expect(credential.type).toBe(payload.type);
expect(credential.data).not.toBe(payload.data); expect(credential.data).not.toBe(payload.data);
const sharedCredential = await Db.collections.SharedCredentials!.findOneOrFail({ const sharedCredential = await Db.collections.SharedCredentials.findOneOrFail({
relations: ['user', 'credentials', 'role'], relations: ['user', 'credentials', 'role'],
where: { credentials: credential, user: ownerShell }, where: { credentialsId: credential.id, userId: ownerShell.id },
}); });
expect(sharedCredential.role).toEqual(credentialOwnerRole); expect(sharedCredential.role).toEqual(credentialOwnerRole);
@ -153,13 +151,13 @@ test('DELETE /credentials/:id should delete owned cred for owner', async () => {
expect(name).toBe(savedCredential.name); expect(name).toBe(savedCredential.name);
expect(type).toBe(savedCredential.type); expect(type).toBe(savedCredential.type);
const deletedCredential = await Db.collections.Credentials!.findOne(savedCredential.id); const deletedCredential = await Db.collections.Credentials.findOneBy({ id: savedCredential.id });
expect(deletedCredential).toBeUndefined(); // deleted expect(deletedCredential).toBeNull(); // deleted
const deletedSharedCredential = await Db.collections.SharedCredentials!.findOne(); const deletedSharedCredential = await Db.collections.SharedCredentials.findOneBy({});
expect(deletedSharedCredential).toBeUndefined(); // deleted expect(deletedSharedCredential).toBeNull(); // deleted
}); });
test('DELETE /credentials/:id should delete non-owned cred for owner', async () => { test('DELETE /credentials/:id should delete non-owned cred for owner', async () => {
@ -181,13 +179,13 @@ test('DELETE /credentials/:id should delete non-owned cred for owner', async ()
expect(response.statusCode).toBe(200); expect(response.statusCode).toBe(200);
const deletedCredential = await Db.collections.Credentials!.findOne(savedCredential.id); const deletedCredential = await Db.collections.Credentials.findOneBy({ id: savedCredential.id });
expect(deletedCredential).toBeUndefined(); // deleted expect(deletedCredential).toBeNull(); // deleted
const deletedSharedCredential = await Db.collections.SharedCredentials!.findOne(); const deletedSharedCredential = await Db.collections.SharedCredentials.findOneBy({});
expect(deletedSharedCredential).toBeUndefined(); // deleted expect(deletedSharedCredential).toBeNull(); // deleted
}); });
test('DELETE /credentials/:id should delete owned cred for member', async () => { test('DELETE /credentials/:id should delete owned cred for member', async () => {
@ -211,13 +209,13 @@ test('DELETE /credentials/:id should delete owned cred for member', async () =>
expect(name).toBe(savedCredential.name); expect(name).toBe(savedCredential.name);
expect(type).toBe(savedCredential.type); expect(type).toBe(savedCredential.type);
const deletedCredential = await Db.collections.Credentials!.findOne(savedCredential.id); const deletedCredential = await Db.collections.Credentials.findOneBy({ id: savedCredential.id });
expect(deletedCredential).toBeUndefined(); // deleted expect(deletedCredential).toBeNull(); // deleted
const deletedSharedCredential = await Db.collections.SharedCredentials!.findOne(); const deletedSharedCredential = await Db.collections.SharedCredentials.findOneBy({});
expect(deletedSharedCredential).toBeUndefined(); // deleted expect(deletedSharedCredential).toBeNull(); // deleted
}); });
test('DELETE /credentials/:id should delete owned cred for member but leave others untouched', async () => { test('DELETE /credentials/:id should delete owned cred for member but leave others untouched', async () => {
@ -244,27 +242,27 @@ test('DELETE /credentials/:id should delete owned cred for member but leave othe
expect(name).toBe(savedCredential.name); expect(name).toBe(savedCredential.name);
expect(type).toBe(savedCredential.type); expect(type).toBe(savedCredential.type);
const deletedCredential = await Db.collections.Credentials!.findOne(savedCredential.id); const deletedCredential = await Db.collections.Credentials.findOneBy({ id: savedCredential.id });
expect(deletedCredential).toBeUndefined(); // deleted expect(deletedCredential).toBeNull(); // deleted
const deletedSharedCredential = await Db.collections.SharedCredentials!.findOne({ const deletedSharedCredential = await Db.collections.SharedCredentials.findOne({
where: { where: {
credentials: savedCredential, credentialsId: savedCredential.id,
}, },
}); });
expect(deletedSharedCredential).toBeUndefined(); // deleted expect(deletedSharedCredential).toBeNull(); // deleted
await Promise.all( await Promise.all(
[notToBeChangedCredential, notToBeChangedCredential2].map(async (credential) => { [notToBeChangedCredential, notToBeChangedCredential2].map(async (credential) => {
const untouchedCredential = await Db.collections.Credentials!.findOne(credential.id); const untouchedCredential = await Db.collections.Credentials.findOneBy({ id: credential.id });
expect(untouchedCredential).toEqual(credential); // not deleted expect(untouchedCredential).toEqual(credential); // not deleted
const untouchedSharedCredential = await Db.collections.SharedCredentials!.findOne({ const untouchedSharedCredential = await Db.collections.SharedCredentials.findOne({
where: { where: {
credentials: credential, credentialsId: credential.id,
}, },
}); });
@ -289,11 +287,11 @@ test('DELETE /credentials/:id should not delete non-owned cred for member', asyn
expect(response.statusCode).toBe(404); expect(response.statusCode).toBe(404);
const shellCredential = await Db.collections.Credentials!.findOne(savedCredential.id); const shellCredential = await Db.collections.Credentials.findOneBy({ id: savedCredential.id });
expect(shellCredential).toBeDefined(); // not deleted expect(shellCredential).toBeDefined(); // not deleted
const deletedSharedCredential = await Db.collections.SharedCredentials!.findOne(); const deletedSharedCredential = await Db.collections.SharedCredentials.findOneBy({});
expect(deletedSharedCredential).toBeDefined(); // not deleted expect(deletedSharedCredential).toBeDefined(); // not deleted
}); });

View file

@ -9,14 +9,12 @@ import * as utils from '../shared/utils';
import * as testDb from '../shared/testDb'; import * as testDb from '../shared/testDb';
let app: express.Application; let app: express.Application;
let testDbName = '';
let globalOwnerRole: Role; let globalOwnerRole: Role;
let workflowRunner: ActiveWorkflowRunner; let workflowRunner: ActiveWorkflowRunner;
beforeAll(async () => { beforeAll(async () => {
app = await utils.initTestServer({ endpointGroups: ['publicApi'], applyAuth: false }); app = await utils.initTestServer({ endpointGroups: ['publicApi'], applyAuth: false });
const initResult = await testDb.init(); await testDb.init();
testDbName = initResult.testDbName;
globalOwnerRole = await testDb.getGlobalOwnerRole(); globalOwnerRole = await testDb.getGlobalOwnerRole();
@ -30,18 +28,15 @@ beforeAll(async () => {
}); });
beforeEach(async () => { beforeEach(async () => {
await testDb.truncate( await testDb.truncate([
[ 'SharedCredentials',
'SharedCredentials', 'SharedWorkflow',
'SharedWorkflow', 'User',
'User', 'Workflow',
'Workflow', 'Credentials',
'Credentials', 'Execution',
'Execution', 'Settings',
'Settings', ]);
],
testDbName,
);
config.set('userManagement.disabled', false); config.set('userManagement.disabled', false);
config.set('userManagement.isInstanceOwnerSetUp', true); config.set('userManagement.isInstanceOwnerSetUp', true);
@ -52,7 +47,7 @@ afterEach(async () => {
}); });
afterAll(async () => { afterAll(async () => {
await testDb.terminate(testDbName); await testDb.terminate();
}); });
test('GET /executions/:id should fail due to missing API Key', async () => { test('GET /executions/:id should fail due to missing API Key', async () => {

View file

@ -11,7 +11,6 @@ import * as utils from '../shared/utils';
import * as testDb from '../shared/testDb'; import * as testDb from '../shared/testDb';
let app: express.Application; let app: express.Application;
let testDbName = '';
let globalOwnerRole: Role; let globalOwnerRole: Role;
let globalMemberRole: Role; let globalMemberRole: Role;
let workflowOwnerRole: Role; let workflowOwnerRole: Role;
@ -19,8 +18,7 @@ let workflowRunner: ActiveWorkflowRunner;
beforeAll(async () => { beforeAll(async () => {
app = await utils.initTestServer({ endpointGroups: ['publicApi'], applyAuth: false }); app = await utils.initTestServer({ endpointGroups: ['publicApi'], applyAuth: false });
const initResult = await testDb.init(); await testDb.init();
testDbName = initResult.testDbName;
const [fetchedGlobalOwnerRole, fetchedGlobalMemberRole, fetchedWorkflowOwnerRole] = const [fetchedGlobalOwnerRole, fetchedGlobalMemberRole, fetchedWorkflowOwnerRole] =
await testDb.getAllRoles(); await testDb.getAllRoles();
@ -37,10 +35,14 @@ beforeAll(async () => {
}); });
beforeEach(async () => { beforeEach(async () => {
await testDb.truncate( await testDb.truncate([
['SharedCredentials', 'SharedWorkflow', 'Tag', 'User', 'Workflow', 'Credentials'], 'SharedCredentials',
testDbName, 'SharedWorkflow',
); 'Tag',
'User',
'Workflow',
'Credentials',
]);
config.set('userManagement.disabled', false); config.set('userManagement.disabled', false);
config.set('userManagement.isInstanceOwnerSetUp', true); config.set('userManagement.isInstanceOwnerSetUp', true);
@ -51,7 +53,7 @@ afterEach(async () => {
}); });
afterAll(async () => { afterAll(async () => {
await testDb.terminate(testDbName); await testDb.terminate();
}); });
test('GET /workflows should fail due to missing API Key', async () => { test('GET /workflows should fail due to missing API Key', async () => {
@ -537,11 +539,11 @@ test('DELETE /workflows/:id should delete the workflow', async () => {
expect(updatedAt).toEqual(workflow.updatedAt.toISOString()); expect(updatedAt).toEqual(workflow.updatedAt.toISOString());
// make sure the workflow actually deleted from the db // make sure the workflow actually deleted from the db
const sharedWorkflow = await Db.collections.SharedWorkflow.findOne({ const sharedWorkflow = await Db.collections.SharedWorkflow.findOneBy({
workflow, workflowId: workflow.id,
}); });
expect(sharedWorkflow).toBeUndefined(); expect(sharedWorkflow).toBeNull();
}); });
test('DELETE /workflows/:id should delete non-owned workflow when owner', async () => { test('DELETE /workflows/:id should delete non-owned workflow when owner', async () => {
@ -576,11 +578,11 @@ test('DELETE /workflows/:id should delete non-owned workflow when owner', async
expect(updatedAt).toEqual(workflow.updatedAt.toISOString()); expect(updatedAt).toEqual(workflow.updatedAt.toISOString());
// make sure the workflow actually deleted from the db // make sure the workflow actually deleted from the db
const sharedWorkflow = await Db.collections.SharedWorkflow.findOne({ const sharedWorkflow = await Db.collections.SharedWorkflow.findOneBy({
workflow, workflowId: workflow.id,
}); });
expect(sharedWorkflow).toBeUndefined(); expect(sharedWorkflow).toBeNull();
}); });
test('POST /workflows/:id/activate should fail due to missing API Key', async () => { test('POST /workflows/:id/activate should fail due to missing API Key', async () => {
@ -679,8 +681,8 @@ test('POST /workflows/:id/activate should set workflow as active', async () => {
// check whether the workflow is on the database // check whether the workflow is on the database
const sharedWorkflow = await Db.collections.SharedWorkflow.findOne({ const sharedWorkflow = await Db.collections.SharedWorkflow.findOne({
where: { where: {
user: member, userId: member.id,
workflow, workflowId: workflow.id,
}, },
relations: ['workflow'], relations: ['workflow'],
}); });
@ -724,17 +726,17 @@ test('POST /workflows/:id/activate should set non-owned workflow as active when
// check whether the workflow is on the database // check whether the workflow is on the database
const sharedOwnerWorkflow = await Db.collections.SharedWorkflow.findOne({ const sharedOwnerWorkflow = await Db.collections.SharedWorkflow.findOne({
where: { where: {
user: owner, userId: owner.id,
workflow, workflowId: workflow.id,
}, },
}); });
expect(sharedOwnerWorkflow).toBeUndefined(); expect(sharedOwnerWorkflow).toBeNull();
const sharedWorkflow = await Db.collections.SharedWorkflow.findOne({ const sharedWorkflow = await Db.collections.SharedWorkflow.findOne({
where: { where: {
user: member, userId: member.id,
workflow, workflowId: workflow.id,
}, },
relations: ['workflow'], relations: ['workflow'],
}); });
@ -824,8 +826,8 @@ test('POST /workflows/:id/deactivate should deactivate workflow', async () => {
// get the workflow after it was deactivated // get the workflow after it was deactivated
const sharedWorkflow = await Db.collections.SharedWorkflow.findOne({ const sharedWorkflow = await Db.collections.SharedWorkflow.findOne({
where: { where: {
user: member, userId: member.id,
workflow, workflowId: workflow.id,
}, },
relations: ['workflow'], relations: ['workflow'],
}); });
@ -869,17 +871,17 @@ test('POST /workflows/:id/deactivate should deactivate non-owned workflow when o
// check whether the workflow is deactivated in the database // check whether the workflow is deactivated in the database
const sharedOwnerWorkflow = await Db.collections.SharedWorkflow.findOne({ const sharedOwnerWorkflow = await Db.collections.SharedWorkflow.findOne({
where: { where: {
user: owner, userId: owner.id,
workflow, workflowId: workflow.id,
}, },
}); });
expect(sharedOwnerWorkflow).toBeUndefined(); expect(sharedOwnerWorkflow).toBeNull();
const sharedWorkflow = await Db.collections.SharedWorkflow.findOne({ const sharedWorkflow = await Db.collections.SharedWorkflow.findOne({
where: { where: {
user: member, userId: member.id,
workflow, workflowId: workflow.id,
}, },
relations: ['workflow'], relations: ['workflow'],
}); });
@ -990,8 +992,8 @@ test('POST /workflows should create workflow', async () => {
// check if created workflow in DB // check if created workflow in DB
const sharedWorkflow = await Db.collections.SharedWorkflow.findOne({ const sharedWorkflow = await Db.collections.SharedWorkflow.findOne({
where: { where: {
user: member, userId: member.id,
workflow: response.body, workflowId: response.body.id,
}, },
relations: ['workflow', 'role'], relations: ['workflow', 'role'],
}); });
@ -1170,8 +1172,8 @@ test('PUT /workflows/:id should update workflow', async () => {
// check updated workflow in DB // check updated workflow in DB
const sharedWorkflow = await Db.collections.SharedWorkflow.findOne({ const sharedWorkflow = await Db.collections.SharedWorkflow.findOne({
where: { where: {
user: member, userId: member.id,
workflow: response.body, workflowId: response.body.id,
}, },
relations: ['workflow'], relations: ['workflow'],
}); });
@ -1247,17 +1249,17 @@ test('PUT /workflows/:id should update non-owned workflow if owner', async () =>
// check updated workflow in DB // check updated workflow in DB
const sharedOwnerWorkflow = await Db.collections.SharedWorkflow.findOne({ const sharedOwnerWorkflow = await Db.collections.SharedWorkflow.findOne({
where: { where: {
user: owner, userId: owner.id,
workflow: response.body, workflowId: response.body.id,
}, },
}); });
expect(sharedOwnerWorkflow).toBeUndefined(); expect(sharedOwnerWorkflow).toBeNull();
const sharedWorkflow = await Db.collections.SharedWorkflow.findOne({ const sharedWorkflow = await Db.collections.SharedWorkflow.findOne({
where: { where: {
user: member, userId: member.id,
workflow: response.body, workflowId: response.body.id,
}, },
relations: ['workflow', 'role'], relations: ['workflow', 'role'],
}); });

View file

@ -1,5 +1,5 @@
import superagent = require('superagent'); import superagent = require('superagent');
import { ObjectLiteral } from 'typeorm'; import type { ObjectLiteral } from 'typeorm';
/** /**
* Make `SuperTest<T>` string-indexable. * Make `SuperTest<T>` string-indexable.

View file

@ -1,5 +1,5 @@
import { UserSettings } from 'n8n-core'; import { UserSettings } from 'n8n-core';
import { Connection, ConnectionOptions, createConnection, getConnection } from 'typeorm'; import { DataSource as Connection, DataSourceOptions as ConnectionOptions } from 'typeorm';
import config from '@/config'; import config from '@/config';
import * as Db from '@/Db'; import * as Db from '@/Db';
@ -50,7 +50,7 @@ export async function init() {
// no bootstrap connection required // no bootstrap connection required
const testDbName = `n8n_test_sqlite_${randomString(6, 10)}_${Date.now()}`; const testDbName = `n8n_test_sqlite_${randomString(6, 10)}_${Date.now()}`;
await Db.init(getSqliteOptions({ name: testDbName })); await Db.init(getSqliteOptions({ name: testDbName }));
await getConnection(testDbName).runMigrations({ transaction: 'none' }); await Db.getConnection().runMigrations({ transaction: 'none' });
return { testDbName }; return { testDbName };
} }
@ -60,7 +60,7 @@ export async function init() {
const pgOptions = getBootstrapDBOptions('postgres'); const pgOptions = getBootstrapDBOptions('postgres');
try { try {
bootstrapPostgres = await createConnection(pgOptions); bootstrapPostgres = await new Connection(pgOptions).initialize();
} catch (error) { } catch (error) {
const pgConfig = getPostgresSchemaSection(); const pgConfig = getPostgresSchemaSection();
@ -82,15 +82,15 @@ export async function init() {
const testDbName = `postgres_${randomString(6, 10)}_${Date.now()}_n8n_test`; const testDbName = `postgres_${randomString(6, 10)}_${Date.now()}_n8n_test`;
await bootstrapPostgres.query(`CREATE DATABASE ${testDbName}`); await bootstrapPostgres.query(`CREATE DATABASE ${testDbName}`);
await bootstrapPostgres.close(); await bootstrapPostgres.destroy();
const dbOptions = getDBOptions('postgres', testDbName); const dbOptions = getDBOptions('postgres', testDbName);
if (dbOptions.schema !== 'public') { if (dbOptions.schema !== 'public') {
const { schema, migrations, ...options } = dbOptions; const { schema, migrations, ...options } = dbOptions;
const connection = await createConnection(options); const connection = await new Connection(options).initialize();
await connection.query(`CREATE SCHEMA IF NOT EXISTS "${schema}"`); await connection.query(`CREATE SCHEMA IF NOT EXISTS "${schema}"`);
await connection.close(); await connection.destroy();
} }
await Db.init(dbOptions); await Db.init(dbOptions);
@ -99,11 +99,11 @@ export async function init() {
} }
if (dbType === 'mysqldb') { if (dbType === 'mysqldb') {
const bootstrapMysql = await createConnection(getBootstrapDBOptions('mysql')); const bootstrapMysql = await new Connection(getBootstrapDBOptions('mysql')).initialize();
const testDbName = `mysql_${randomString(6, 10)}_${Date.now()}_n8n_test`; const testDbName = `mysql_${randomString(6, 10)}_${Date.now()}_n8n_test`;
await bootstrapMysql.query(`CREATE DATABASE ${testDbName}`); await bootstrapMysql.query(`CREATE DATABASE ${testDbName}`);
await bootstrapMysql.close(); await bootstrapMysql.destroy();
await Db.init(getDBOptions('mysql', testDbName)); await Db.init(getDBOptions('mysql', testDbName));
@ -116,8 +116,8 @@ export async function init() {
/** /**
* Drop test DB, closing bootstrap connection if existing. * Drop test DB, closing bootstrap connection if existing.
*/ */
export async function terminate(testDbName: string) { export async function terminate() {
await getConnection(testDbName).close(); await Db.getConnection().destroy();
} }
async function truncateMappingTables( async function truncateMappingTables(
@ -171,9 +171,9 @@ async function truncateMappingTables(
* @param collections Array of entity names whose tables to truncate. * @param collections Array of entity names whose tables to truncate.
* @param testDbName Name of the test DB to truncate tables in. * @param testDbName Name of the test DB to truncate tables in.
*/ */
export async function truncate(collections: Array<CollectionName>, testDbName: string) { export async function truncate(collections: Array<CollectionName>) {
const dbType = config.getEnv('database.type'); const dbType = config.getEnv('database.type');
const testDb = getConnection(testDbName); const testDb = Db.getConnection();
if (dbType === 'sqlite') { if (dbType === 'sqlite') {
await testDb.query('PRAGMA foreign_keys=OFF'); await testDb.query('PRAGMA foreign_keys=OFF');
@ -287,12 +287,12 @@ export async function saveCredential(
} }
export async function shareCredentialWithUsers(credential: CredentialsEntity, users: User[]) { export async function shareCredentialWithUsers(credential: CredentialsEntity, users: User[]) {
const role = await Db.collections.Role.findOne({ scope: 'credential', name: 'user' }); const role = await Db.collections.Role.findOneBy({ scope: 'credential', name: 'user' });
const newSharedCredentials = users.map((user) => const newSharedCredentials = users.map((user) =>
Db.collections.SharedCredentials.create({ Db.collections.SharedCredentials.create({
user, userId: user.id,
credentials: credential, credentialsId: credential.id,
role, roleId: role?.id,
}), }),
); );
return Db.collections.SharedCredentials.save(newSharedCredentials); return Db.collections.SharedCredentials.save(newSharedCredentials);
@ -333,7 +333,7 @@ export function createUserShell(globalRole: Role): Promise<User> {
throw new Error(`Invalid role received: ${JSON.stringify(globalRole)}`); throw new Error(`Invalid role received: ${JSON.stringify(globalRole)}`);
} }
const shell: Partial<User> = { globalRole }; const shell: Partial<User> = { globalRoleId: globalRole.id };
if (globalRole.name !== 'owner') { if (globalRole.name !== 'owner') {
shell.email = randomEmail(); shell.email = randomEmail();
@ -405,35 +405,35 @@ export function addApiKey(user: User): Promise<User> {
// ---------------------------------- // ----------------------------------
export function getGlobalOwnerRole() { export function getGlobalOwnerRole() {
return Db.collections.Role.findOneOrFail({ return Db.collections.Role.findOneByOrFail({
name: 'owner', name: 'owner',
scope: 'global', scope: 'global',
}); });
} }
export function getGlobalMemberRole() { export function getGlobalMemberRole() {
return Db.collections.Role.findOneOrFail({ return Db.collections.Role.findOneByOrFail({
name: 'member', name: 'member',
scope: 'global', scope: 'global',
}); });
} }
export function getWorkflowOwnerRole() { export function getWorkflowOwnerRole() {
return Db.collections.Role.findOneOrFail({ return Db.collections.Role.findOneByOrFail({
name: 'owner', name: 'owner',
scope: 'workflow', scope: 'workflow',
}); });
} }
export function getWorkflowEditorRole() { export function getWorkflowEditorRole() {
return Db.collections.Role.findOneOrFail({ return Db.collections.Role.findOneByOrFail({
name: 'editor', name: 'editor',
scope: 'workflow', scope: 'workflow',
}); });
} }
export function getCredentialOwnerRole() { export function getCredentialOwnerRole() {
return Db.collections.Role.findOneOrFail({ return Db.collections.Role.findOneByOrFail({
name: 'owner', name: 'owner',
scope: 'credential', scope: 'credential',
}); });
@ -641,10 +641,8 @@ export async function createWorkflowWithTrigger(
// ---------------------------------- // ----------------------------------
export async function getWorkflowSharing(workflow: WorkflowEntity) { export async function getWorkflowSharing(workflow: WorkflowEntity) {
return Db.collections.SharedWorkflow.find({ return Db.collections.SharedWorkflow.findBy({
where: { workflowId: workflow.id,
workflow,
},
}); });
} }

View file

@ -646,7 +646,7 @@ export function getAuthToken(response: request.Response, authCookieName = AUTH_C
// ---------------------------------- // ----------------------------------
export async function isInstanceOwnerSetUp() { export async function isInstanceOwnerSetUp() {
const { value } = await Db.collections.Settings.findOneOrFail({ const { value } = await Db.collections.Settings.findOneByOrFail({
key: 'userManagement.isInstanceOwnerSetUp', key: 'userManagement.isInstanceOwnerSetUp',
}); });

View file

@ -25,7 +25,6 @@ import { NodeMailer } from '@/UserManagement/email/NodeMailer';
jest.mock('@/UserManagement/email/NodeMailer'); jest.mock('@/UserManagement/email/NodeMailer');
let app: express.Application; let app: express.Application;
let testDbName = '';
let globalMemberRole: Role; let globalMemberRole: Role;
let globalOwnerRole: Role; let globalOwnerRole: Role;
let workflowOwnerRole: Role; let workflowOwnerRole: Role;
@ -34,8 +33,7 @@ let authAgent: AuthAgent;
beforeAll(async () => { beforeAll(async () => {
app = await utils.initTestServer({ endpointGroups: ['users'], applyAuth: true }); app = await utils.initTestServer({ endpointGroups: ['users'], applyAuth: true });
const initResult = await testDb.init(); await testDb.init();
testDbName = initResult.testDbName;
const [ const [
fetchedGlobalOwnerRole, fetchedGlobalOwnerRole,
@ -56,10 +54,7 @@ beforeAll(async () => {
}); });
beforeEach(async () => { beforeEach(async () => {
await testDb.truncate( await testDb.truncate(['User', 'SharedCredentials', 'SharedWorkflow', 'Workflow', 'Credentials']);
['User', 'SharedCredentials', 'SharedWorkflow', 'Workflow', 'Credentials'],
testDbName,
);
jest.mock('@/config'); jest.mock('@/config');
@ -70,7 +65,7 @@ beforeEach(async () => {
}); });
afterAll(async () => { afterAll(async () => {
await testDb.terminate(testDbName); await testDb.terminate();
}); });
test('GET /users should return all users', async () => { test('GET /users should return all users', async () => {
@ -156,28 +151,28 @@ test('DELETE /users/:id should delete the user', async () => {
expect(response.statusCode).toBe(200); expect(response.statusCode).toBe(200);
expect(response.body).toEqual(SUCCESS_RESPONSE_BODY); expect(response.body).toEqual(SUCCESS_RESPONSE_BODY);
const user = await Db.collections.User.findOne(userToDelete.id); const user = await Db.collections.User.findOneBy({ id: userToDelete.id });
expect(user).toBeUndefined(); // deleted expect(user).toBeNull(); // deleted
const sharedWorkflow = await Db.collections.SharedWorkflow.findOne({ const sharedWorkflow = await Db.collections.SharedWorkflow.findOne({
relations: ['user'], relations: ['user'],
where: { user: userToDelete, role: workflowOwnerRole }, where: { userId: userToDelete.id, roleId: workflowOwnerRole.id },
}); });
expect(sharedWorkflow).toBeUndefined(); // deleted expect(sharedWorkflow).toBeNull(); // deleted
const sharedCredential = await Db.collections.SharedCredentials.findOne({ const sharedCredential = await Db.collections.SharedCredentials.findOne({
relations: ['user'], relations: ['user'],
where: { user: userToDelete, role: credentialOwnerRole }, where: { userId: userToDelete.id, roleId: credentialOwnerRole.id },
}); });
expect(sharedCredential).toBeUndefined(); // deleted expect(sharedCredential).toBeNull(); // deleted
const workflow = await Db.collections.Workflow.findOne(savedWorkflow.id); const workflow = await Db.collections.Workflow.findOneBy({ id: savedWorkflow.id });
expect(workflow).toBeUndefined(); // deleted expect(workflow).toBeNull(); // deleted
// TODO: Include active workflow and check whether webhook has been removed // TODO: Include active workflow and check whether webhook has been removed
const credential = await Db.collections.Credentials.findOne(savedCredential.id); const credential = await Db.collections.Credentials.findOneBy({ id: savedCredential.id });
expect(credential).toBeUndefined(); // deleted expect(credential).toBeNull(); // deleted
}); });
test('DELETE /users/:id should fail to delete self', async () => { test('DELETE /users/:id should fail to delete self', async () => {
@ -187,7 +182,7 @@ test('DELETE /users/:id should fail to delete self', async () => {
expect(response.statusCode).toBe(400); expect(response.statusCode).toBe(400);
const user = await Db.collections.User.findOne(owner.id); const user = await Db.collections.User.findOneBy({ id: owner.id });
expect(user).toBeDefined(); expect(user).toBeDefined();
}); });
@ -202,7 +197,7 @@ test('DELETE /users/:id should fail if user to delete is transferee', async () =
expect(response.statusCode).toBe(400); expect(response.statusCode).toBe(400);
const user = await Db.collections.User.findOne(idToDelete); const user = await Db.collections.User.findOneBy({ id: idToDelete });
expect(user).toBeDefined(); expect(user).toBeDefined();
}); });
@ -226,7 +221,7 @@ test('DELETE /users/:id with transferId should perform transfer', async () => {
const sharedWorkflow = await Db.collections.SharedWorkflow.findOneOrFail({ const sharedWorkflow = await Db.collections.SharedWorkflow.findOneOrFail({
relations: ['workflow'], relations: ['workflow'],
where: { user: owner }, where: { userId: owner.id },
}); });
expect(sharedWorkflow.workflow).toBeDefined(); expect(sharedWorkflow.workflow).toBeDefined();
@ -234,15 +229,15 @@ test('DELETE /users/:id with transferId should perform transfer', async () => {
const sharedCredential = await Db.collections.SharedCredentials.findOneOrFail({ const sharedCredential = await Db.collections.SharedCredentials.findOneOrFail({
relations: ['credentials'], relations: ['credentials'],
where: { user: owner }, where: { userId: owner.id },
}); });
expect(sharedCredential.credentials).toBeDefined(); expect(sharedCredential.credentials).toBeDefined();
expect(sharedCredential.credentials.id).toBe(savedCredential.id); expect(sharedCredential.credentials.id).toBe(savedCredential.id);
const deletedUser = await Db.collections.User.findOne(userToDelete); const deletedUser = await Db.collections.User.findOneBy({ id: userToDelete.id });
expect(deletedUser).toBeUndefined(); expect(deletedUser).toBeNull();
}); });
test('GET /resolve-signup-token should validate invite token', async () => { test('GET /resolve-signup-token should validate invite token', async () => {
@ -342,7 +337,7 @@ test('POST /users/:id should fill out a user shell', async () => {
const authToken = utils.getAuthToken(response); const authToken = utils.getAuthToken(response);
expect(authToken).toBeDefined(); expect(authToken).toBeDefined();
const member = await Db.collections.User.findOneOrFail(memberShell.id); const member = await Db.collections.User.findOneByOrFail({ id: memberShell.id });
expect(member.firstName).toBe(memberData.firstName); expect(member.firstName).toBe(memberData.firstName);
expect(member.lastName).toBe(memberData.lastName); expect(member.lastName).toBe(memberData.lastName);
expect(member.password).not.toBe(memberData.password); expect(member.password).not.toBe(memberData.password);
@ -487,7 +482,7 @@ test('POST /users should email invites and create user shells but ignore existin
expect(error).toBe('Email could not be sent'); expect(error).toBe('Email could not be sent');
} }
const storedUser = await Db.collections.User.findOneOrFail(id); const storedUser = await Db.collections.User.findOneByOrFail({ id });
const { firstName, lastName, personalizationAnswers, password, resetPasswordToken } = const { firstName, lastName, personalizationAnswers, password, resetPasswordToken } =
storedUser; storedUser;

View file

@ -14,7 +14,6 @@ import { randomCredentialPayload } from './shared/random';
import { ActiveWorkflowRunner } from '@/ActiveWorkflowRunner'; import { ActiveWorkflowRunner } from '@/ActiveWorkflowRunner';
let app: express.Application; let app: express.Application;
let testDbName = '';
let globalOwnerRole: Role; let globalOwnerRole: Role;
let globalMemberRole: Role; let globalMemberRole: Role;
let credentialOwnerRole: Role; let credentialOwnerRole: Role;
@ -29,8 +28,7 @@ beforeAll(async () => {
endpointGroups: ['workflows'], endpointGroups: ['workflows'],
applyAuth: true, applyAuth: true,
}); });
const initResult = await testDb.init(); await testDb.init();
testDbName = initResult.testDbName;
globalOwnerRole = await testDb.getGlobalOwnerRole(); globalOwnerRole = await testDb.getGlobalOwnerRole();
globalMemberRole = await testDb.getGlobalMemberRole(); globalMemberRole = await testDb.getGlobalMemberRole();
@ -53,11 +51,11 @@ beforeAll(async () => {
}); });
beforeEach(async () => { beforeEach(async () => {
await testDb.truncate(['User', 'Workflow', 'SharedWorkflow'], testDbName); await testDb.truncate(['User', 'Workflow', 'SharedWorkflow']);
}); });
afterAll(async () => { afterAll(async () => {
await testDb.terminate(testDbName); await testDb.terminate();
}); });
test('Router should switch dynamically', async () => { test('Router should switch dynamically', async () => {

View file

@ -9,7 +9,6 @@ import type { IPinData } from 'n8n-workflow';
import { makeWorkflow, MOCK_PINDATA } from './shared/utils'; import { makeWorkflow, MOCK_PINDATA } from './shared/utils';
let app: express.Application; let app: express.Application;
let testDbName = '';
let globalOwnerRole: Role; let globalOwnerRole: Role;
// mock whether sharing is enabled or not // mock whether sharing is enabled or not
@ -20,8 +19,7 @@ beforeAll(async () => {
endpointGroups: ['workflows'], endpointGroups: ['workflows'],
applyAuth: true, applyAuth: true,
}); });
const initResult = await testDb.init(); await testDb.init();
testDbName = initResult.testDbName;
globalOwnerRole = await testDb.getGlobalOwnerRole(); globalOwnerRole = await testDb.getGlobalOwnerRole();
@ -30,11 +28,11 @@ beforeAll(async () => {
}); });
beforeEach(async () => { beforeEach(async () => {
await testDb.truncate(['User', 'Workflow', 'SharedWorkflow'], testDbName); await testDb.truncate(['User', 'Workflow', 'SharedWorkflow']);
}); });
afterAll(async () => { afterAll(async () => {
await testDb.terminate(testDbName); await testDb.terminate();
}); });
test('POST /workflows should store pin data for node in workflow', async () => { test('POST /workflows should store pin data for node in workflow', async () => {

View file

@ -1,5 +1,5 @@
import 'tsconfig-paths/register'; import 'tsconfig-paths/register';
import { createConnection } from 'typeorm'; import { DataSource as Connection } from 'typeorm';
import config from '@/config'; import config from '@/config';
import { getBootstrapDBOptions } from './integration/shared/testDb'; import { getBootstrapDBOptions } from './integration/shared/testDb';
@ -7,7 +7,8 @@ export default async () => {
const dbType = config.getEnv('database.type').replace(/db$/, ''); const dbType = config.getEnv('database.type').replace(/db$/, '');
if (dbType !== 'postgres' && dbType !== 'mysql') return; if (dbType !== 'postgres' && dbType !== 'mysql') return;
const connection = await createConnection(getBootstrapDBOptions(dbType)); const connection = new Connection(getBootstrapDBOptions(dbType));
await connection.initialize();
const query = const query =
dbType === 'postgres' ? 'SELECT datname as "Database" FROM pg_database' : 'SHOW DATABASES'; dbType === 'postgres' ? 'SELECT datname as "Database" FROM pg_database' : 'SHOW DATABASES';
@ -20,5 +21,5 @@ export default async () => {
const promises = databases.map((dbName) => connection.query(`DROP DATABASE ${dbName};`)); const promises = databases.map((dbName) => connection.query(`DROP DATABASE ${dbName};`));
await Promise.all(promises); await Promise.all(promises);
await connection.close(); await connection.destroy();
}; };

View file

@ -25,15 +25,13 @@ import type { SaveCredentialFunction } from '../integration/shared/types';
import { User } from '@/databases/entities/User'; import { User } from '@/databases/entities/User';
import { SharedWorkflow } from '@/databases/entities/SharedWorkflow'; import { SharedWorkflow } from '@/databases/entities/SharedWorkflow';
let testDbName = '';
let mockNodeTypes: INodeTypes; let mockNodeTypes: INodeTypes;
let credentialOwnerRole: Role; let credentialOwnerRole: Role;
let workflowOwnerRole: Role; let workflowOwnerRole: Role;
let saveCredential: SaveCredentialFunction; let saveCredential: SaveCredentialFunction;
beforeAll(async () => { beforeAll(async () => {
const initResult = await testDb.init(); await testDb.init();
testDbName = initResult.testDbName;
mockNodeTypes = MockNodeTypes({ mockNodeTypes = MockNodeTypes({
loaded: { loaded: {
@ -51,12 +49,12 @@ beforeAll(async () => {
}); });
beforeEach(async () => { beforeEach(async () => {
await testDb.truncate(['SharedWorkflow', 'SharedCredentials'], testDbName); await testDb.truncate(['SharedWorkflow', 'SharedCredentials']);
await testDb.truncate(['User', 'Workflow', 'Credentials'], testDbName); await testDb.truncate(['User', 'Workflow', 'Credentials']);
}); });
afterAll(async () => { afterAll(async () => {
await testDb.terminate(testDbName); await testDb.terminate();
}); });
describe('PermissionChecker.check()', () => { describe('PermissionChecker.check()', () => {

View file

@ -39,7 +39,7 @@ describe('Telemetry', () => {
beforeEach(() => { beforeEach(() => {
spyTrack.mockClear(); spyTrack.mockClear();
telemetry = new Telemetry(instanceId, n8nVersion); telemetry = new Telemetry(instanceId);
(telemetry as any).rudderStack = { (telemetry as any).rudderStack = {
flush: () => {}, flush: () => {},
identify: () => {}, identify: () => {},

View file

@ -3,7 +3,7 @@ import type { INode, IWorkflowCredentials } from 'n8n-workflow';
import * as Db from '@/Db'; import * as Db from '@/Db';
import { WorkflowCredentials } from '@/WorkflowCredentials'; import { WorkflowCredentials } from '@/WorkflowCredentials';
// Define a function used to mock the findOne function // Define a function used to mock the findOneBy function
async function mockFind({ async function mockFind({
id, id,
type, type,
@ -33,7 +33,7 @@ jest.mock('@/Db', () => {
return { return {
collections: { collections: {
Credentials: { Credentials: {
findOne: jest.fn(mockFind), findOneBy: jest.fn(mockFind),
}, },
}, },
}; };
@ -54,7 +54,7 @@ describe('WorkflowCredentials', () => {
`Credentials with name "${credentials.name}" for type "test" miss an ID.`, `Credentials with name "${credentials.name}" for type "test" miss an ID.`,
); );
expect(WorkflowCredentials([noIdNode])).rejects.toEqual(expectedError); expect(WorkflowCredentials([noIdNode])).rejects.toEqual(expectedError);
expect(mocked(Db.collections.Credentials.findOne)).toHaveBeenCalledTimes(0); expect(mocked(Db.collections.Credentials.findOneBy)).toHaveBeenCalledTimes(0);
}); });
test('Should return an error if credentials cannot be found in the DB', () => { test('Should return an error if credentials cannot be found in the DB', () => {
@ -63,7 +63,7 @@ describe('WorkflowCredentials', () => {
`Could not find credentials for type "test" with ID "${credentials.id}".`, `Could not find credentials for type "test" with ID "${credentials.id}".`,
); );
expect(WorkflowCredentials([notFoundNode])).rejects.toEqual(expectedError); expect(WorkflowCredentials([notFoundNode])).rejects.toEqual(expectedError);
expect(mocked(Db.collections.Credentials.findOne)).toHaveBeenCalledTimes(1); expect(mocked(Db.collections.Credentials.findOneBy)).toHaveBeenCalledTimes(1);
}); });
test('Should ignore duplicates', async () => { test('Should ignore duplicates', async () => {

View file

@ -184,7 +184,7 @@ importers:
lodash.uniqby: ^4.7.0 lodash.uniqby: ^4.7.0
lodash.unset: ^4.5.2 lodash.unset: ^4.5.2
luxon: ^3.1.0 luxon: ^3.1.0
mysql2: ~2.3.0 mysql2: ~2.3.3
n8n-core: ~0.151.0 n8n-core: ~0.151.0
n8n-editor-ui: ~0.177.0 n8n-editor-ui: ~0.177.0
n8n-nodes-base: ~0.209.0 n8n-nodes-base: ~0.209.0
@ -199,7 +199,7 @@ importers:
passport: ^0.6.0 passport: ^0.6.0
passport-cookie: ^1.0.9 passport-cookie: ^1.0.9
passport-jwt: ^4.0.0 passport-jwt: ^4.0.0
pg: ^8.3.0 pg: ^8.8.0
picocolors: ^1.0.0 picocolors: ^1.0.0
posthog-node: ^2.2.2 posthog-node: ^2.2.2
prom-client: ^13.1.0 prom-client: ^13.1.0
@ -209,7 +209,7 @@ importers:
semver: ^7.3.8 semver: ^7.3.8
shelljs: ^0.8.5 shelljs: ^0.8.5
source-map-support: ^0.5.21 source-map-support: ^0.5.21
sqlite3: ^5.1.2 sqlite3: ^5.1.4
sse-channel: ^4.0.0 sse-channel: ^4.0.0
supertest: ^6.2.2 supertest: ^6.2.2
swagger-ui-express: ^4.3.0 swagger-ui-express: ^4.3.0
@ -218,7 +218,7 @@ importers:
tsc-alias: ^1.7.0 tsc-alias: ^1.7.0
tsconfig-paths: ^3.14.1 tsconfig-paths: ^3.14.1
tslib: 1.14.1 tslib: 1.14.1
typeorm: 0.2.45 typeorm: 0.3.11
uuid: ^8.3.2 uuid: ^8.3.2
validator: 13.7.0 validator: 13.7.0
winston: ^3.3.3 winston: ^3.3.3
@ -299,12 +299,12 @@ importers:
semver: 7.3.8 semver: 7.3.8
shelljs: 0.8.5 shelljs: 0.8.5
source-map-support: 0.5.21 source-map-support: 0.5.21
sqlite3: 5.1.2 sqlite3: 5.1.4
sse-channel: 4.0.0 sse-channel: 4.0.0
swagger-ui-express: 4.5.0_express@4.18.2 swagger-ui-express: 4.5.0_express@4.18.2
syslog-client: 1.1.1 syslog-client: 1.1.1
tslib: 1.14.1 tslib: 1.14.1
typeorm: 0.2.45_b2izk5tn6tm5xb65gvog337urq typeorm: 0.3.11_a77gzgdqnod3rkvxniiwirlqsi
uuid: 8.3.2 uuid: 8.3.2
validator: 13.7.0 validator: 13.7.0
winston: 3.8.2 winston: 3.8.2
@ -3481,6 +3481,7 @@ packages:
/@npmcli/move-file/1.1.2: /@npmcli/move-file/1.1.2:
resolution: {integrity: sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==} resolution: {integrity: sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==}
engines: {node: '>=10'} engines: {node: '>=10'}
deprecated: This functionality has been moved to @npmcli/fs
dependencies: dependencies:
mkdirp: 1.0.4 mkdirp: 1.0.4
rimraf: 3.0.2 rimraf: 3.0.2
@ -6407,10 +6408,6 @@ packages:
dev: true dev: true
optional: true optional: true
/@types/zen-observable/0.8.3:
resolution: {integrity: sha512-fbF6oTd4sGGy0xjHPKAt+eS2CrxJ3+6gQ3FGcBoIJR2TLAyCkCyI8JqZNy+FeON0AhVgNJoUumVoZQjBFUqHkw==}
dev: false
/@typescript-eslint/eslint-plugin/5.45.0_psz44bhp76u27vmulntnlx26h4: /@typescript-eslint/eslint-plugin/5.45.0_psz44bhp76u27vmulntnlx26h4:
resolution: {integrity: sha512-CXXHNlf0oL+Yg021cxgOdMHNTXD17rHkq7iW6RFHoybdFgQBjU3yIXhhcPpGwr1CjZlo6ET8C6tzX5juQoXeGA==} resolution: {integrity: sha512-CXXHNlf0oL+Yg021cxgOdMHNTXD17rHkq7iW6RFHoybdFgQBjU3yIXhhcPpGwr1CjZlo6ET8C6tzX5juQoXeGA==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@ -7378,7 +7375,6 @@ packages:
/arg/4.1.3: /arg/4.1.3:
resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==}
dev: true
/argparse/1.0.10: /argparse/1.0.10:
resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==}
@ -9707,7 +9703,6 @@ packages:
/create-require/1.1.1: /create-require/1.1.1:
resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==}
dev: true
/crelt/1.0.5: /crelt/1.0.5:
resolution: {integrity: sha512-+BO9wPPi+DWTDcNYhr/W90myha8ptzftZT+LwcmUbbok0rcP/fequmFYCw8NMoH7pkAZQzU78b3kYrlua5a9eA==} resolution: {integrity: sha512-+BO9wPPi+DWTDcNYhr/W90myha8ptzftZT+LwcmUbbok0rcP/fequmFYCw8NMoH7pkAZQzU78b3kYrlua5a9eA==}
@ -9998,7 +9993,6 @@ packages:
/date-fns/2.29.3: /date-fns/2.29.3:
resolution: {integrity: sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA==} resolution: {integrity: sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA==}
engines: {node: '>=0.11'} engines: {node: '>=0.11'}
dev: true
/date-now/0.1.4: /date-now/0.1.4:
resolution: {integrity: sha512-AsElvov3LoNB7tf5k37H2jYSB+ZZPMT5sG2QjJCcdlV5chIv6htBUBUui2IKRjgtKAKtCBN7Zbwa+MtwLjSeNw==} resolution: {integrity: sha512-AsElvov3LoNB7tf5k37H2jYSB+ZZPMT5sG2QjJCcdlV5chIv6htBUBUui2IKRjgtKAKtCBN7Zbwa+MtwLjSeNw==}
@ -10307,7 +10301,6 @@ packages:
/diff/4.0.2: /diff/4.0.2:
resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==}
engines: {node: '>=0.3.1'} engines: {node: '>=0.3.1'}
dev: true
/diffie-hellman/5.0.3: /diffie-hellman/5.0.3:
resolution: {integrity: sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==} resolution: {integrity: sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==}
@ -10460,7 +10453,6 @@ packages:
/dotenv/16.0.3: /dotenv/16.0.3:
resolution: {integrity: sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==} resolution: {integrity: sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==}
engines: {node: '>=12'} engines: {node: '>=12'}
dev: true
/dotenv/8.6.0: /dotenv/8.6.0:
resolution: {integrity: sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==} resolution: {integrity: sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==}
@ -19609,8 +19601,8 @@ packages:
resolution: {integrity: sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==} resolution: {integrity: sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==}
dev: false dev: false
/sqlite3/5.1.2: /sqlite3/5.1.4:
resolution: {integrity: sha512-D0Reg6pRWAFXFUnZKsszCI67tthFD8fGPewRddDCX6w4cYwz3MbvuwRICbL+YQjBAh9zbw+lJ/V9oC8nG5j6eg==} resolution: {integrity: sha512-i0UlWAzPlzX3B5XP2cYuhWQJsTtlMD6obOa1PgeEQ4DHEXUuyJkgv50I3isqZAP5oFc2T8OFvakmDh2W6I+YpA==}
requiresBuild: true requiresBuild: true
peerDependenciesMeta: peerDependenciesMeta:
node-gyp: node-gyp:
@ -20732,7 +20724,6 @@ packages:
source-map-support: 0.5.21 source-map-support: 0.5.21
typescript: 4.8.4 typescript: 4.8.4
yn: 3.1.1 yn: 3.1.1
dev: true
/ts-pnp/1.2.0_typescript@4.8.4: /ts-pnp/1.2.0_typescript@4.8.4:
resolution: {integrity: sha512-csd+vJOb/gkzvcCHgTGSChYpy5f1/XKNsmvBGO4JXS+z1v2HobugDz4s1IeFXM3wZB44uczs+eazB5Q/ccdhQw==} resolution: {integrity: sha512-csd+vJOb/gkzvcCHgTGSChYpy5f1/XKNsmvBGO4JXS+z1v2HobugDz4s1IeFXM3wZB44uczs+eazB5Q/ccdhQw==}
@ -20935,26 +20926,31 @@ packages:
/typedarray/0.0.6: /typedarray/0.0.6:
resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==}
/typeorm/0.2.45_b2izk5tn6tm5xb65gvog337urq: /typeorm/0.3.11_a77gzgdqnod3rkvxniiwirlqsi:
resolution: {integrity: sha512-c0rCO8VMJ3ER7JQ73xfk0zDnVv0WDjpsP6Q1m6CVKul7DB9iVdWLRjPzc8v2eaeBuomsbZ2+gTaYr8k1gm3bYA==} resolution: {integrity: sha512-pzdOyWbVuz/z8Ww6gqvBW4nylsM0KLdUCDExr2gR20/x1khGSVxQkjNV/3YqliG90jrWzrknYbYscpk8yxFJVg==}
engines: {node: '>= 12.9.0'}
hasBin: true hasBin: true
peerDependencies: peerDependencies:
'@sap/hana-client': ^2.11.14 '@google-cloud/spanner': ^5.18.0
better-sqlite3: ^7.1.2 '@sap/hana-client': ^2.12.25
better-sqlite3: ^7.1.2 || ^8.0.0
hdb-pool: ^0.1.6 hdb-pool: ^0.1.6
ioredis: ^4.28.3 ioredis: ^5.0.4
mongodb: ^3.6.0 mongodb: ^3.6.0
mssql: ^6.3.1 mssql: ^7.3.0
mysql2: ^2.2.5 mysql2: ^2.2.5
oracledb: ^5.1.0 oracledb: ^5.1.0
pg: ^8.5.1 pg: ^8.5.1
pg-native: ^3.0.0 pg-native: ^3.0.0
pg-query-stream: ^4.0.0 pg-query-stream: ^4.0.0
redis: ^3.1.1 redis: ^3.1.1 || ^4.0.0
sql.js: ^1.4.0 sql.js: ^1.4.0
sqlite3: ^5.0.2 sqlite3: ^5.0.3
ts-node: ^10.7.0
typeorm-aurora-data-api-driver: ^2.0.0 typeorm-aurora-data-api-driver: ^2.0.0
peerDependenciesMeta: peerDependenciesMeta:
'@google-cloud/spanner':
optional: true
'@sap/hana-client': '@sap/hana-client':
optional: true optional: true
better-sqlite3: better-sqlite3:
@ -20983,6 +20979,8 @@ packages:
optional: true optional: true
sqlite3: sqlite3:
optional: true optional: true
ts-node:
optional: true
typeorm-aurora-data-api-driver: typeorm-aurora-data-api-driver:
optional: true optional: true
dependencies: dependencies:
@ -20991,8 +20989,9 @@ packages:
buffer: 6.0.3 buffer: 6.0.3
chalk: 4.1.2 chalk: 4.1.2
cli-highlight: 2.1.11 cli-highlight: 2.1.11
date-fns: 2.29.3
debug: 4.3.4 debug: 4.3.4
dotenv: 8.6.0 dotenv: 16.0.3
glob: 7.2.3 glob: 7.2.3
ioredis: 5.2.4 ioredis: 5.2.4
js-yaml: 4.1.0 js-yaml: 4.1.0
@ -21001,12 +21000,12 @@ packages:
pg: 8.8.0 pg: 8.8.0
reflect-metadata: 0.1.13 reflect-metadata: 0.1.13
sha.js: 2.4.11 sha.js: 2.4.11
sqlite3: 5.1.2 sqlite3: 5.1.4
ts-node: 9.1.1_typescript@4.8.4
tslib: 2.4.0 tslib: 2.4.0
uuid: 8.3.2 uuid: 8.3.2
xml2js: 0.4.23 xml2js: 0.4.23
yargs: 17.6.0 yargs: 17.6.0
zen-observable-ts: 1.1.0
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
dev: false dev: false
@ -22715,7 +22714,6 @@ packages:
/yn/3.1.1: /yn/3.1.1:
resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==}
engines: {node: '>=6'} engines: {node: '>=6'}
dev: true
/yocto-queue/0.1.0: /yocto-queue/0.1.0:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
@ -22747,17 +22745,6 @@ packages:
commander: 2.20.3 commander: 2.20.3
dev: true dev: true
/zen-observable-ts/1.1.0:
resolution: {integrity: sha512-1h4zlLSqI2cRLPJUHJFL8bCWHhkpuXkF+dbGkRaWjgDIG26DmzyshUMrdV/rL3UnR+mhaX4fRq8LPouq0MYYIA==}
dependencies:
'@types/zen-observable': 0.8.3
zen-observable: 0.8.15
dev: false
/zen-observable/0.8.15:
resolution: {integrity: sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ==}
dev: false
/zwitch/1.0.5: /zwitch/1.0.5:
resolution: {integrity: sha512-V50KMwwzqJV0NpZIZFwfOD5/lyny3WlSzRiXgA0G7VUnRlqttta1L6UQIHzd6EuBY/cHGfwTIck7w1yH6Q5zUw==} resolution: {integrity: sha512-V50KMwwzqJV0NpZIZFwfOD5/lyny3WlSzRiXgA0G7VUnRlqttta1L6UQIHzd6EuBY/cHGfwTIck7w1yH6Q5zUw==}
dev: true dev: true