⚡ Change credentials structure (#2139)
* ✨ change FE to handle new object type * 🚸 improve UX of handling invalid credentials * 🚧 WIP * 🎨 fix typescript issues * 🐘 add migrations for all supported dbs * ✏️ add description to migrations * ⚡ add credential update on import * ⚡ resolve after merge issues * 👕 fix lint issues * ⚡ check credentials on workflow create/update * update interface * 👕 fix ts issues * ⚡ adaption to new credentials UI * 🐛 intialize cache on BE for credentials check * 🐛 fix undefined oldCredentials * 🐛 fix deleting credential * 🐛 fix check for undefined keys * 🐛 fix disabling edit in execution * 🎨 just show credential name on execution view * ✏️ remove TODO * ⚡ implement review suggestions * ⚡ add cache to getCredentialsByType * ⏪ use getter instead of cache * ✏️ fix variable name typo * 🐘 include waiting nodes to migrations * 🐛 fix reverting migrations command * ⚡ update typeorm command * ✨ create db:revert command * 👕 fix lint error Co-authored-by: Mutasem <mutdmour@gmail.com>
This commit is contained in:
@@ -11,9 +11,40 @@ import {
|
||||
PrimaryGeneratedColumn,
|
||||
UpdateDateColumn,
|
||||
} from 'typeorm';
|
||||
import { getTimestampSyntax, resolveDataType } from '../utils';
|
||||
|
||||
import { ICredentialsDb } from '../..';
|
||||
import config = require('../../../config');
|
||||
import { DatabaseType, ICredentialsDb } from '../..';
|
||||
|
||||
function resolveDataType(dataType: string) {
|
||||
const dbType = config.get('database.type') as DatabaseType;
|
||||
|
||||
const typeMap: { [key in DatabaseType]: { [key: string]: string } } = {
|
||||
sqlite: {
|
||||
json: 'simple-json',
|
||||
},
|
||||
postgresdb: {
|
||||
datetime: 'timestamptz',
|
||||
},
|
||||
mysqldb: {},
|
||||
mariadb: {},
|
||||
};
|
||||
|
||||
return typeMap[dbType][dataType] ?? dataType;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
function getTimestampSyntax() {
|
||||
const dbType = config.get('database.type') as DatabaseType;
|
||||
|
||||
const map: { [key in DatabaseType]: string } = {
|
||||
sqlite: "STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')",
|
||||
postgresdb: 'CURRENT_TIMESTAMP(3)',
|
||||
mysqldb: 'CURRENT_TIMESTAMP(3)',
|
||||
mariadb: 'CURRENT_TIMESTAMP(3)',
|
||||
};
|
||||
|
||||
return map[dbType];
|
||||
}
|
||||
|
||||
@Entity()
|
||||
export class CredentialsEntity implements ICredentialsDb {
|
||||
|
||||
@@ -2,9 +2,25 @@
|
||||
import { WorkflowExecuteMode } from 'n8n-workflow';
|
||||
|
||||
import { Column, ColumnOptions, Entity, Index, PrimaryGeneratedColumn } from 'typeorm';
|
||||
import { IExecutionFlattedDb, IWorkflowDb } from '../..';
|
||||
import config = require('../../../config');
|
||||
import { DatabaseType, IExecutionFlattedDb, IWorkflowDb } from '../..';
|
||||
|
||||
import { resolveDataType } from '../utils';
|
||||
function resolveDataType(dataType: string) {
|
||||
const dbType = config.get('database.type') as DatabaseType;
|
||||
|
||||
const typeMap: { [key in DatabaseType]: { [key: string]: string } } = {
|
||||
sqlite: {
|
||||
json: 'simple-json',
|
||||
},
|
||||
postgresdb: {
|
||||
datetime: 'timestamptz',
|
||||
},
|
||||
mysqldb: {},
|
||||
mariadb: {},
|
||||
};
|
||||
|
||||
return typeMap[dbType][dataType] ?? dataType;
|
||||
}
|
||||
|
||||
@Entity()
|
||||
export class ExecutionEntity implements IExecutionFlattedDb {
|
||||
|
||||
@@ -12,9 +12,24 @@ import {
|
||||
} from 'typeorm';
|
||||
import { IsDate, IsOptional, IsString, Length } from 'class-validator';
|
||||
|
||||
import config = require('../../../config');
|
||||
import { DatabaseType } from '../../index';
|
||||
import { ITagDb } from '../../Interfaces';
|
||||
import { WorkflowEntity } from './WorkflowEntity';
|
||||
import { getTimestampSyntax } from '../utils';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
function getTimestampSyntax() {
|
||||
const dbType = config.get('database.type') as DatabaseType;
|
||||
|
||||
const map: { [key in DatabaseType]: string } = {
|
||||
sqlite: "STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')",
|
||||
postgresdb: 'CURRENT_TIMESTAMP(3)',
|
||||
mysqldb: 'CURRENT_TIMESTAMP(3)',
|
||||
mariadb: 'CURRENT_TIMESTAMP(3)',
|
||||
};
|
||||
|
||||
return map[dbType];
|
||||
}
|
||||
|
||||
@Entity()
|
||||
export class TagEntity implements ITagDb {
|
||||
|
||||
@@ -17,12 +17,41 @@ import {
|
||||
UpdateDateColumn,
|
||||
} from 'typeorm';
|
||||
|
||||
import { IWorkflowDb } from '../..';
|
||||
|
||||
import { getTimestampSyntax, resolveDataType } from '../utils';
|
||||
|
||||
import config = require('../../../config');
|
||||
import { DatabaseType, IWorkflowDb } from '../..';
|
||||
import { TagEntity } from './TagEntity';
|
||||
|
||||
function resolveDataType(dataType: string) {
|
||||
const dbType = config.get('database.type') as DatabaseType;
|
||||
|
||||
const typeMap: { [key in DatabaseType]: { [key: string]: string } } = {
|
||||
sqlite: {
|
||||
json: 'simple-json',
|
||||
},
|
||||
postgresdb: {
|
||||
datetime: 'timestamptz',
|
||||
},
|
||||
mysqldb: {},
|
||||
mariadb: {},
|
||||
};
|
||||
|
||||
return typeMap[dbType][dataType] ?? dataType;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
function getTimestampSyntax() {
|
||||
const dbType = config.get('database.type') as DatabaseType;
|
||||
|
||||
const map: { [key in DatabaseType]: string } = {
|
||||
sqlite: "STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')",
|
||||
postgresdb: 'CURRENT_TIMESTAMP(3)',
|
||||
mysqldb: 'CURRENT_TIMESTAMP(3)',
|
||||
mariadb: 'CURRENT_TIMESTAMP(3)',
|
||||
};
|
||||
|
||||
return map[dbType];
|
||||
}
|
||||
|
||||
@Entity()
|
||||
export class WorkflowEntity implements IWorkflowDb {
|
||||
@PrimaryGeneratedColumn()
|
||||
|
||||
@@ -0,0 +1,215 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
import config = require('../../../../config');
|
||||
|
||||
// replacing the credentials in workflows and execution
|
||||
// `nodeType: name` changes to `nodeType: { id, name }`
|
||||
|
||||
export class UpdateWorkflowCredentials1630451444017 implements MigrationInterface {
|
||||
name = 'UpdateWorkflowCredentials1630451444017';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
const tablePrefix = config.get('database.tablePrefix');
|
||||
|
||||
const credentialsEntities = await queryRunner.query(`
|
||||
SELECT id, name, type
|
||||
FROM ${tablePrefix}credentials_entity
|
||||
`);
|
||||
|
||||
const workflows = await queryRunner.query(`
|
||||
SELECT id, nodes
|
||||
FROM ${tablePrefix}workflow_entity
|
||||
`);
|
||||
// @ts-ignore
|
||||
workflows.forEach(async (workflow) => {
|
||||
const nodes = workflow.nodes;
|
||||
let credentialsUpdated = false;
|
||||
// @ts-ignore
|
||||
nodes.forEach((node) => {
|
||||
if (node.credentials) {
|
||||
const allNodeCredentials = Object.entries(node.credentials);
|
||||
for (const [type, name] of allNodeCredentials) {
|
||||
if (typeof name === 'string') {
|
||||
// @ts-ignore
|
||||
const matchingCredentials = credentialsEntities.find(
|
||||
// @ts-ignore
|
||||
(credentials) => credentials.name === name && credentials.type === type,
|
||||
);
|
||||
node.credentials[type] = { id: matchingCredentials?.id.toString() || null, name };
|
||||
credentialsUpdated = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
if (credentialsUpdated) {
|
||||
const [updateQuery, updateParams] = queryRunner.connection.driver.escapeQueryWithParameters(
|
||||
`
|
||||
UPDATE ${tablePrefix}workflow_entity
|
||||
SET nodes = :nodes
|
||||
WHERE id = '${workflow.id}'
|
||||
`,
|
||||
{ nodes: JSON.stringify(nodes) },
|
||||
{},
|
||||
);
|
||||
|
||||
await queryRunner.query(updateQuery, updateParams);
|
||||
}
|
||||
});
|
||||
|
||||
const waitingExecutions = await queryRunner.query(`
|
||||
SELECT id, workflowData
|
||||
FROM ${tablePrefix}execution_entity
|
||||
WHERE waitTill IS NOT NULL AND finished = 0
|
||||
`);
|
||||
|
||||
const retryableExecutions = await queryRunner.query(`
|
||||
SELECT id, workflowData
|
||||
FROM ${tablePrefix}execution_entity
|
||||
WHERE waitTill IS NULL AND finished = 0 AND mode != 'retry'
|
||||
ORDER BY startedAt DESC
|
||||
LIMIT 200
|
||||
`);
|
||||
|
||||
[...waitingExecutions, ...retryableExecutions].forEach(async (execution) => {
|
||||
const data = execution.workflowData;
|
||||
let credentialsUpdated = false;
|
||||
// @ts-ignore
|
||||
data.nodes.forEach((node) => {
|
||||
if (node.credentials) {
|
||||
const allNodeCredentials = Object.entries(node.credentials);
|
||||
for (const [type, name] of allNodeCredentials) {
|
||||
if (typeof name === 'string') {
|
||||
// @ts-ignore
|
||||
const matchingCredentials = credentialsEntities.find(
|
||||
// @ts-ignore
|
||||
(credentials) => credentials.name === name && credentials.type === type,
|
||||
);
|
||||
node.credentials[type] = { id: matchingCredentials?.id.toString() || null, name };
|
||||
credentialsUpdated = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
if (credentialsUpdated) {
|
||||
const [updateQuery, updateParams] = queryRunner.connection.driver.escapeQueryWithParameters(
|
||||
`
|
||||
UPDATE ${tablePrefix}execution_entity
|
||||
SET workflowData = :data
|
||||
WHERE id = '${execution.id}'
|
||||
`,
|
||||
{ data: JSON.stringify(data) },
|
||||
{},
|
||||
);
|
||||
|
||||
await queryRunner.query(updateQuery, updateParams);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
const tablePrefix = config.get('database.tablePrefix');
|
||||
|
||||
const credentialsEntities = await queryRunner.query(`
|
||||
SELECT id, name, type
|
||||
FROM ${tablePrefix}credentials_entity
|
||||
`);
|
||||
|
||||
const workflows = await queryRunner.query(`
|
||||
SELECT id, nodes
|
||||
FROM ${tablePrefix}workflow_entity
|
||||
`);
|
||||
// @ts-ignore
|
||||
workflows.forEach(async (workflow) => {
|
||||
const nodes = workflow.nodes;
|
||||
let credentialsUpdated = false;
|
||||
// @ts-ignore
|
||||
nodes.forEach((node) => {
|
||||
if (node.credentials) {
|
||||
const allNodeCredentials = Object.entries(node.credentials);
|
||||
for (const [type, creds] of allNodeCredentials) {
|
||||
if (typeof creds === 'object') {
|
||||
// @ts-ignore
|
||||
const matchingCredentials = credentialsEntities.find(
|
||||
// @ts-ignore
|
||||
(credentials) => credentials.id === creds.id && credentials.type === type,
|
||||
);
|
||||
if (matchingCredentials) {
|
||||
node.credentials[type] = matchingCredentials.name;
|
||||
} else {
|
||||
// @ts-ignore
|
||||
node.credentials[type] = creds.name;
|
||||
}
|
||||
credentialsUpdated = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
if (credentialsUpdated) {
|
||||
const [updateQuery, updateParams] = queryRunner.connection.driver.escapeQueryWithParameters(
|
||||
`
|
||||
UPDATE ${tablePrefix}workflow_entity
|
||||
SET nodes = :nodes
|
||||
WHERE id = '${workflow.id}'
|
||||
`,
|
||||
{ nodes: JSON.stringify(nodes) },
|
||||
{},
|
||||
);
|
||||
|
||||
await queryRunner.query(updateQuery, updateParams);
|
||||
}
|
||||
});
|
||||
|
||||
const waitingExecutions = await queryRunner.query(`
|
||||
SELECT id, workflowData
|
||||
FROM ${tablePrefix}execution_entity
|
||||
WHERE waitTill IS NOT NULL AND finished = 0
|
||||
`);
|
||||
|
||||
const retryableExecutions = await queryRunner.query(`
|
||||
SELECT id, workflowData
|
||||
FROM ${tablePrefix}execution_entity
|
||||
WHERE waitTill IS NULL AND finished = 0 AND mode != 'retry'
|
||||
ORDER BY startedAt DESC
|
||||
LIMIT 200
|
||||
`);
|
||||
|
||||
[...waitingExecutions, ...retryableExecutions].forEach(async (execution) => {
|
||||
const data = execution.workflowData;
|
||||
let credentialsUpdated = false;
|
||||
// @ts-ignore
|
||||
data.nodes.forEach((node) => {
|
||||
if (node.credentials) {
|
||||
const allNodeCredentials = Object.entries(node.credentials);
|
||||
for (const [type, creds] of allNodeCredentials) {
|
||||
if (typeof creds === 'object') {
|
||||
// @ts-ignore
|
||||
const matchingCredentials = credentialsEntities.find(
|
||||
// @ts-ignore
|
||||
(credentials) => credentials.id === creds.id && credentials.type === type,
|
||||
);
|
||||
if (matchingCredentials) {
|
||||
node.credentials[type] = matchingCredentials.name;
|
||||
} else {
|
||||
// @ts-ignore
|
||||
node.credentials[type] = creds.name;
|
||||
}
|
||||
credentialsUpdated = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
if (credentialsUpdated) {
|
||||
const [updateQuery, updateParams] = queryRunner.connection.driver.escapeQueryWithParameters(
|
||||
`
|
||||
UPDATE ${tablePrefix}execution_entity
|
||||
SET workflowData = :data
|
||||
WHERE id = '${execution.id}'
|
||||
`,
|
||||
{ data: JSON.stringify(data) },
|
||||
{},
|
||||
);
|
||||
|
||||
await queryRunner.query(updateQuery, updateParams);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import { CreateTagEntity1617268711084 } from './1617268711084-CreateTagEntity';
|
||||
import { UniqueWorkflowNames1620826335440 } from './1620826335440-UniqueWorkflowNames';
|
||||
import { CertifyCorrectCollation1623936588000 } from './1623936588000-CertifyCorrectCollation';
|
||||
import { AddWaitColumnId1626183952959 } from './1626183952959-AddWaitColumn';
|
||||
import { UpdateWorkflowCredentials1630451444017 } from './1630451444017-UpdateWorkflowCredentials';
|
||||
|
||||
export const mysqlMigrations = [
|
||||
InitialMigration1588157391238,
|
||||
@@ -22,4 +23,5 @@ export const mysqlMigrations = [
|
||||
UniqueWorkflowNames1620826335440,
|
||||
CertifyCorrectCollation1623936588000,
|
||||
AddWaitColumnId1626183952959,
|
||||
UpdateWorkflowCredentials1630451444017,
|
||||
];
|
||||
|
||||
@@ -0,0 +1,223 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
import config = require('../../../../config');
|
||||
|
||||
// replacing the credentials in workflows and execution
|
||||
// `nodeType: name` changes to `nodeType: { id, name }`
|
||||
|
||||
export class UpdateWorkflowCredentials1630419189837 implements MigrationInterface {
|
||||
name = 'UpdateWorkflowCredentials1630419189837';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
let tablePrefix = config.get('database.tablePrefix');
|
||||
const schema = config.get('database.postgresdb.schema');
|
||||
if (schema) {
|
||||
tablePrefix = schema + '.' + tablePrefix;
|
||||
}
|
||||
|
||||
const credentialsEntities = await queryRunner.query(`
|
||||
SELECT id, name, type
|
||||
FROM ${tablePrefix}credentials_entity
|
||||
`);
|
||||
|
||||
const workflows = await queryRunner.query(`
|
||||
SELECT id, nodes
|
||||
FROM ${tablePrefix}workflow_entity
|
||||
`);
|
||||
// @ts-ignore
|
||||
workflows.forEach(async (workflow) => {
|
||||
const nodes = workflow.nodes;
|
||||
let credentialsUpdated = false;
|
||||
// @ts-ignore
|
||||
nodes.forEach((node) => {
|
||||
if (node.credentials) {
|
||||
const allNodeCredentials = Object.entries(node.credentials);
|
||||
for (const [type, name] of allNodeCredentials) {
|
||||
if (typeof name === 'string') {
|
||||
// @ts-ignore
|
||||
const matchingCredentials = credentialsEntities.find(
|
||||
// @ts-ignore
|
||||
(credentials) => credentials.name === name && credentials.type === type,
|
||||
);
|
||||
node.credentials[type] = { id: matchingCredentials?.id.toString() || null, name };
|
||||
credentialsUpdated = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
if (credentialsUpdated) {
|
||||
const [updateQuery, updateParams] = queryRunner.connection.driver.escapeQueryWithParameters(
|
||||
`
|
||||
UPDATE ${tablePrefix}workflow_entity
|
||||
SET nodes = :nodes
|
||||
WHERE id = '${workflow.id}'
|
||||
`,
|
||||
{ nodes: JSON.stringify(nodes) },
|
||||
{},
|
||||
);
|
||||
|
||||
await queryRunner.query(updateQuery, updateParams);
|
||||
}
|
||||
});
|
||||
|
||||
const waitingExecutions = await queryRunner.query(`
|
||||
SELECT id, "workflowData"
|
||||
FROM ${tablePrefix}execution_entity
|
||||
WHERE "waitTill" IS NOT NULL AND finished = FALSE
|
||||
`);
|
||||
|
||||
const retryableExecutions = await queryRunner.query(`
|
||||
SELECT id, "workflowData"
|
||||
FROM ${tablePrefix}execution_entity
|
||||
WHERE "waitTill" IS NULL AND finished = FALSE AND mode != 'retry'
|
||||
ORDER BY "startedAt" DESC
|
||||
LIMIT 200
|
||||
`);
|
||||
|
||||
[...waitingExecutions, ...retryableExecutions].forEach(async (execution) => {
|
||||
const data = execution.workflowData;
|
||||
let credentialsUpdated = false;
|
||||
// @ts-ignore
|
||||
data.nodes.forEach((node) => {
|
||||
if (node.credentials) {
|
||||
const allNodeCredentials = Object.entries(node.credentials);
|
||||
for (const [type, name] of allNodeCredentials) {
|
||||
if (typeof name === 'string') {
|
||||
// @ts-ignore
|
||||
const matchingCredentials = credentialsEntities.find(
|
||||
// @ts-ignore
|
||||
(credentials) => credentials.name === name && credentials.type === type,
|
||||
);
|
||||
node.credentials[type] = { id: matchingCredentials?.id.toString() || null, name };
|
||||
credentialsUpdated = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
if (credentialsUpdated) {
|
||||
const [updateQuery, updateParams] = queryRunner.connection.driver.escapeQueryWithParameters(
|
||||
`
|
||||
UPDATE ${tablePrefix}execution_entity
|
||||
SET "workflowData" = :data
|
||||
WHERE id = '${execution.id}'
|
||||
`,
|
||||
{ data: JSON.stringify(data) },
|
||||
{},
|
||||
);
|
||||
|
||||
await queryRunner.query(updateQuery, updateParams);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
let tablePrefix = config.get('database.tablePrefix');
|
||||
const schema = config.get('database.postgresdb.schema');
|
||||
if (schema) {
|
||||
tablePrefix = schema + '.' + tablePrefix;
|
||||
}
|
||||
|
||||
const credentialsEntities = await queryRunner.query(`
|
||||
SELECT id, name, type
|
||||
FROM ${tablePrefix}credentials_entity
|
||||
`);
|
||||
|
||||
const workflows = await queryRunner.query(`
|
||||
SELECT id, nodes
|
||||
FROM ${tablePrefix}workflow_entity
|
||||
`);
|
||||
// @ts-ignore
|
||||
workflows.forEach(async (workflow) => {
|
||||
const nodes = workflow.nodes;
|
||||
let credentialsUpdated = false;
|
||||
// @ts-ignore
|
||||
nodes.forEach((node) => {
|
||||
if (node.credentials) {
|
||||
const allNodeCredentials = Object.entries(node.credentials);
|
||||
for (const [type, creds] of allNodeCredentials) {
|
||||
if (typeof creds === 'object') {
|
||||
// @ts-ignore
|
||||
const matchingCredentials = credentialsEntities.find(
|
||||
// @ts-ignore
|
||||
(credentials) => credentials.id === creds.id && credentials.type === type,
|
||||
);
|
||||
if (matchingCredentials) {
|
||||
node.credentials[type] = matchingCredentials.name;
|
||||
} else {
|
||||
// @ts-ignore
|
||||
node.credentials[type] = creds.name;
|
||||
}
|
||||
credentialsUpdated = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
if (credentialsUpdated) {
|
||||
const [updateQuery, updateParams] = queryRunner.connection.driver.escapeQueryWithParameters(
|
||||
`
|
||||
UPDATE ${tablePrefix}workflow_entity
|
||||
SET nodes = :nodes
|
||||
WHERE id = '${workflow.id}'
|
||||
`,
|
||||
{ nodes: JSON.stringify(nodes) },
|
||||
{},
|
||||
);
|
||||
|
||||
await queryRunner.query(updateQuery, updateParams);
|
||||
}
|
||||
});
|
||||
|
||||
const waitingExecutions = await queryRunner.query(`
|
||||
SELECT id, "workflowData"
|
||||
FROM ${tablePrefix}execution_entity
|
||||
WHERE "waitTill" IS NOT NULL AND finished = FALSE
|
||||
`);
|
||||
|
||||
const retryableExecutions = await queryRunner.query(`
|
||||
SELECT id, "workflowData"
|
||||
FROM ${tablePrefix}execution_entity
|
||||
WHERE "waitTill" IS NULL AND finished = FALSE AND mode != 'retry'
|
||||
ORDER BY "startedAt" DESC
|
||||
LIMIT 200
|
||||
`);
|
||||
|
||||
[...waitingExecutions, ...retryableExecutions].forEach(async (execution) => {
|
||||
const data = execution.workflowData;
|
||||
let credentialsUpdated = false;
|
||||
// @ts-ignore
|
||||
data.nodes.forEach((node) => {
|
||||
if (node.credentials) {
|
||||
const allNodeCredentials = Object.entries(node.credentials);
|
||||
for (const [type, creds] of allNodeCredentials) {
|
||||
if (typeof creds === 'object') {
|
||||
// @ts-ignore
|
||||
const matchingCredentials = credentialsEntities.find(
|
||||
// @ts-ignore
|
||||
(credentials) => credentials.id === creds.id && credentials.type === type,
|
||||
);
|
||||
if (matchingCredentials) {
|
||||
node.credentials[type] = matchingCredentials.name;
|
||||
} else {
|
||||
// @ts-ignore
|
||||
node.credentials[type] = creds.name;
|
||||
}
|
||||
credentialsUpdated = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
if (credentialsUpdated) {
|
||||
const [updateQuery, updateParams] = queryRunner.connection.driver.escapeQueryWithParameters(
|
||||
`
|
||||
UPDATE ${tablePrefix}execution_entity
|
||||
SET "workflowData" = :data
|
||||
WHERE id = '${execution.id}'
|
||||
`,
|
||||
{ data: JSON.stringify(data) },
|
||||
{},
|
||||
);
|
||||
|
||||
await queryRunner.query(updateQuery, updateParams);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import { MakeStoppedAtNullable1607431743768 } from './1607431743768-MakeStoppedA
|
||||
import { CreateTagEntity1617270242566 } from './1617270242566-CreateTagEntity';
|
||||
import { UniqueWorkflowNames1620824779533 } from './1620824779533-UniqueWorkflowNames';
|
||||
import { AddwaitTill1626176912946 } from './1626176912946-AddwaitTill';
|
||||
import { UpdateWorkflowCredentials1630419189837 } from './1630419189837-UpdateWorkflowCredentials';
|
||||
|
||||
export const postgresMigrations = [
|
||||
InitialMigration1587669153312,
|
||||
@@ -16,4 +17,5 @@ export const postgresMigrations = [
|
||||
CreateTagEntity1617270242566,
|
||||
UniqueWorkflowNames1620824779533,
|
||||
AddwaitTill1626176912946,
|
||||
UpdateWorkflowCredentials1630419189837,
|
||||
];
|
||||
|
||||
@@ -0,0 +1,215 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
import config = require('../../../../config');
|
||||
|
||||
// replacing the credentials in workflows and execution
|
||||
// `nodeType: name` changes to `nodeType: { id, name }`
|
||||
|
||||
export class UpdateWorkflowCredentials1630330987096 implements MigrationInterface {
|
||||
name = 'UpdateWorkflowCredentials1630330987096';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
const tablePrefix = config.get('database.tablePrefix');
|
||||
|
||||
const credentialsEntities = await queryRunner.query(`
|
||||
SELECT id, name, type
|
||||
FROM "${tablePrefix}credentials_entity"
|
||||
`);
|
||||
|
||||
const workflows = await queryRunner.query(`
|
||||
SELECT id, nodes
|
||||
FROM "${tablePrefix}workflow_entity"
|
||||
`);
|
||||
// @ts-ignore
|
||||
workflows.forEach(async (workflow) => {
|
||||
const nodes = JSON.parse(workflow.nodes);
|
||||
let credentialsUpdated = false;
|
||||
// @ts-ignore
|
||||
nodes.forEach((node) => {
|
||||
if (node.credentials) {
|
||||
const allNodeCredentials = Object.entries(node.credentials);
|
||||
for (const [type, name] of allNodeCredentials) {
|
||||
if (typeof name === 'string') {
|
||||
// @ts-ignore
|
||||
const matchingCredentials = credentialsEntities.find(
|
||||
// @ts-ignore
|
||||
(credentials) => credentials.name === name && credentials.type === type,
|
||||
);
|
||||
node.credentials[type] = { id: matchingCredentials?.id || null, name };
|
||||
credentialsUpdated = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
if (credentialsUpdated) {
|
||||
const [updateQuery, updateParams] = queryRunner.connection.driver.escapeQueryWithParameters(
|
||||
`
|
||||
UPDATE "${tablePrefix}workflow_entity"
|
||||
SET nodes = :nodes
|
||||
WHERE id = '${workflow.id}'
|
||||
`,
|
||||
{ nodes: JSON.stringify(nodes) },
|
||||
{},
|
||||
);
|
||||
|
||||
await queryRunner.query(updateQuery, updateParams);
|
||||
}
|
||||
});
|
||||
|
||||
const waitingExecutions = await queryRunner.query(`
|
||||
SELECT id, "workflowData"
|
||||
FROM "${tablePrefix}execution_entity"
|
||||
WHERE "waitTill" IS NOT NULL AND finished = 0
|
||||
`);
|
||||
|
||||
const retryableExecutions = await queryRunner.query(`
|
||||
SELECT id, "workflowData"
|
||||
FROM "${tablePrefix}execution_entity"
|
||||
WHERE "waitTill" IS NULL AND finished = 0 AND mode != 'retry'
|
||||
ORDER BY "startedAt" DESC
|
||||
LIMIT 200
|
||||
`);
|
||||
|
||||
[...waitingExecutions, ...retryableExecutions].forEach(async (execution) => {
|
||||
const data = JSON.parse(execution.workflowData);
|
||||
let credentialsUpdated = false;
|
||||
// @ts-ignore
|
||||
data.nodes.forEach((node) => {
|
||||
if (node.credentials) {
|
||||
const allNodeCredentials = Object.entries(node.credentials);
|
||||
for (const [type, name] of allNodeCredentials) {
|
||||
if (typeof name === 'string') {
|
||||
// @ts-ignore
|
||||
const matchingCredentials = credentialsEntities.find(
|
||||
// @ts-ignore
|
||||
(credentials) => credentials.name === name && credentials.type === type,
|
||||
);
|
||||
node.credentials[type] = { id: matchingCredentials?.id || null, name };
|
||||
credentialsUpdated = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
if (credentialsUpdated) {
|
||||
const [updateQuery, updateParams] = queryRunner.connection.driver.escapeQueryWithParameters(
|
||||
`
|
||||
UPDATE "${tablePrefix}execution_entity"
|
||||
SET "workflowData" = :data
|
||||
WHERE id = '${execution.id}'
|
||||
`,
|
||||
{ data: JSON.stringify(data) },
|
||||
{},
|
||||
);
|
||||
|
||||
await queryRunner.query(updateQuery, updateParams);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
const tablePrefix = config.get('database.tablePrefix');
|
||||
|
||||
const credentialsEntities = await queryRunner.query(`
|
||||
SELECT id, name, type
|
||||
FROM "${tablePrefix}credentials_entity"
|
||||
`);
|
||||
|
||||
const workflows = await queryRunner.query(`
|
||||
SELECT id, nodes
|
||||
FROM "${tablePrefix}workflow_entity"
|
||||
`);
|
||||
// @ts-ignore
|
||||
workflows.forEach(async (workflow) => {
|
||||
const nodes = JSON.parse(workflow.nodes);
|
||||
let credentialsUpdated = false;
|
||||
// @ts-ignore
|
||||
nodes.forEach((node) => {
|
||||
if (node.credentials) {
|
||||
const allNodeCredentials = Object.entries(node.credentials);
|
||||
for (const [type, creds] of allNodeCredentials) {
|
||||
if (typeof creds === 'object') {
|
||||
// @ts-ignore
|
||||
const matchingCredentials = credentialsEntities.find(
|
||||
// @ts-ignore
|
||||
(credentials) => credentials.id === creds.id && credentials.type === type,
|
||||
);
|
||||
if (matchingCredentials) {
|
||||
node.credentials[type] = matchingCredentials.name;
|
||||
} else {
|
||||
// @ts-ignore
|
||||
node.credentials[type] = creds.name;
|
||||
}
|
||||
credentialsUpdated = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
if (credentialsUpdated) {
|
||||
const [updateQuery, updateParams] = queryRunner.connection.driver.escapeQueryWithParameters(
|
||||
`
|
||||
UPDATE "${tablePrefix}workflow_entity"
|
||||
SET nodes = :nodes
|
||||
WHERE id = '${workflow.id}'
|
||||
`,
|
||||
{ nodes: JSON.stringify(nodes) },
|
||||
{},
|
||||
);
|
||||
|
||||
await queryRunner.query(updateQuery, updateParams);
|
||||
}
|
||||
});
|
||||
|
||||
const waitingExecutions = await queryRunner.query(`
|
||||
SELECT id, "workflowData"
|
||||
FROM "${tablePrefix}execution_entity"
|
||||
WHERE "waitTill" IS NOT NULL AND finished = 0
|
||||
`);
|
||||
|
||||
const retryableExecutions = await queryRunner.query(`
|
||||
SELECT id, "workflowData"
|
||||
FROM "${tablePrefix}execution_entity"
|
||||
WHERE "waitTill" IS NULL AND finished = 0 AND mode != 'retry'
|
||||
ORDER BY "startedAt" DESC
|
||||
LIMIT 200
|
||||
`);
|
||||
|
||||
[...waitingExecutions, ...retryableExecutions].forEach(async (execution) => {
|
||||
const data = JSON.parse(execution.workflowData);
|
||||
let credentialsUpdated = false;
|
||||
// @ts-ignore
|
||||
data.nodes.forEach((node) => {
|
||||
if (node.credentials) {
|
||||
const allNodeCredentials = Object.entries(node.credentials);
|
||||
for (const [type, creds] of allNodeCredentials) {
|
||||
if (typeof creds === 'object') {
|
||||
// @ts-ignore
|
||||
const matchingCredentials = credentialsEntities.find(
|
||||
// @ts-ignore
|
||||
(credentials) => credentials.id === creds.id && credentials.type === type,
|
||||
);
|
||||
if (matchingCredentials) {
|
||||
node.credentials[type] = matchingCredentials.name;
|
||||
} else {
|
||||
// @ts-ignore
|
||||
node.credentials[type] = creds.name;
|
||||
}
|
||||
credentialsUpdated = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
if (credentialsUpdated) {
|
||||
const [updateQuery, updateParams] = queryRunner.connection.driver.escapeQueryWithParameters(
|
||||
`
|
||||
UPDATE "${tablePrefix}execution_entity"
|
||||
SET "workflowData" = :data
|
||||
WHERE id = '${execution.id}'
|
||||
`,
|
||||
{ data: JSON.stringify(data) },
|
||||
{},
|
||||
);
|
||||
|
||||
await queryRunner.query(updateQuery, updateParams);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import { MakeStoppedAtNullable1607431743769 } from './1607431743769-MakeStoppedA
|
||||
import { CreateTagEntity1617213344594 } from './1617213344594-CreateTagEntity';
|
||||
import { UniqueWorkflowNames1620821879465 } from './1620821879465-UniqueWorkflowNames';
|
||||
import { AddWaitColumn1621707690587 } from './1621707690587-AddWaitColumn';
|
||||
import { UpdateWorkflowCredentials1630330987096 } from './1630330987096-UpdateWorkflowCredentials';
|
||||
|
||||
export const sqliteMigrations = [
|
||||
InitialMigration1588102412422,
|
||||
@@ -16,4 +17,5 @@ export const sqliteMigrations = [
|
||||
CreateTagEntity1617213344594,
|
||||
UniqueWorkflowNames1620821879465,
|
||||
AddWaitColumn1621707690587,
|
||||
UpdateWorkflowCredentials1630330987096,
|
||||
];
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
/* eslint-disable import/no-cycle */
|
||||
import { DatabaseType } from '../index';
|
||||
import { getConfigValueSync } from '../GenericHelpers';
|
||||
|
||||
/**
|
||||
* Resolves the data type for the used database type
|
||||
*
|
||||
* @export
|
||||
* @param {string} dataType
|
||||
* @returns {string}
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
export function resolveDataType(dataType: string) {
|
||||
const dbType = getConfigValueSync('database.type') as DatabaseType;
|
||||
|
||||
const typeMap: { [key in DatabaseType]: { [key: string]: string } } = {
|
||||
sqlite: {
|
||||
json: 'simple-json',
|
||||
},
|
||||
postgresdb: {
|
||||
datetime: 'timestamptz',
|
||||
},
|
||||
mysqldb: {},
|
||||
mariadb: {},
|
||||
};
|
||||
|
||||
return typeMap[dbType][dataType] ?? dataType;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
export function getTimestampSyntax() {
|
||||
const dbType = getConfigValueSync('database.type') as DatabaseType;
|
||||
|
||||
const map: { [key in DatabaseType]: string } = {
|
||||
sqlite: "STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')",
|
||||
postgresdb: 'CURRENT_TIMESTAMP(3)',
|
||||
mysqldb: 'CURRENT_TIMESTAMP(3)',
|
||||
mariadb: 'CURRENT_TIMESTAMP(3)',
|
||||
};
|
||||
|
||||
return map[dbType];
|
||||
}
|
||||
Reference in New Issue
Block a user