Add support for PostgresDB and save date as Date

This commit is contained in:
Jan Oberhauser 2019-07-22 20:29:06 +02:00
parent d027545986
commit a9453806b8
27 changed files with 310 additions and 73 deletions

View file

@ -68,11 +68,19 @@ n8n start --tunnel
```
### Start with MongoDB as Database
### Start with other Database
By default n8n uses SQLite to save credentials, past executions and workflows.
To use MongoDB instead you can provide the environment varialbles `DB_TYPE` and
`DB_MONGODB_CONNECTION_URL` like in the example bellow.
n8n however also supports MongoDB and PostgresDB. To use them simply a few
environment variables have to be set.
#### Start with MongoDB as Database
To use MongoDB as database you can provide the following environment variables like
in the example bellow:
- `DB_TYPE=mongodb`
- `DB_MONGODB_CONNECTION_URL=<CONNECTION_URL>`
Replace the following placeholders with the actual data:
- MONGO_DATABASE
@ -88,6 +96,30 @@ n8n start
```
#### Start with PostgresDB as Database
To use PostgresDB as database you can provide the following environment variables
- `DB_TYPE=postgresdb`
- `DB_POSTGRESDB_DATABASE` (default: 'n8n')
- `DB_POSTGRESDB_HOST` (default: 'localhost')
- `DB_POSTGRESDB_PORT` (default: 5432)
- `DB_POSTGRESDB_USER` (default: 'root')
- `DB_POSTGRESDB_PASSWORD` (default: empty)
```bash
export DB_TYPE=postgresdb
export DB_POSTGRESDB_DATABASE=n8n
export DB_POSTGRESDB_HOST=postgresdb
export DB_POSTGRESDB_PORT=5432
export DB_POSTGRESDB_USER=n8n
export DB_POSTGRESDB_PASSWORD=n8n
n8n start
```
## Execute Workflow from CLI
Workflows can not just be started by triggers, webhooks or manually via the

View file

@ -8,7 +8,7 @@ const config = convict({
database: {
type: {
doc: 'Type of database to use',
format: ['sqlite', 'mongodb'],
format: ['sqlite', 'mongodb', 'postgresdb'],
default: 'sqlite',
env: 'DB_TYPE'
},
@ -20,6 +20,38 @@ const config = convict({
env: 'DB_MONGODB_CONNECTION_URL'
}
},
postgresdb: {
database: {
doc: 'PostgresDB Database',
format: String,
default: 'n8n',
env: 'DB_POSTGRESDB_DATABASE'
},
host: {
doc: 'PostgresDB Host',
format: String,
default: 'localhost',
env: 'DB_POSTGRESDB_HOST'
},
password: {
doc: 'PostgresDB Password',
format: String,
default: '',
env: 'DB_POSTGRESDB_PASSWORD'
},
port: {
doc: 'PostgresDB Port',
format: Number,
default: 5432,
env: 'DB_POSTGRESDB_PORT'
},
user: {
doc: 'PostgresDB User',
format: String,
default: 'root',
env: 'DB_POSTGRESDB_USER'
},
},
},
executions: {

View file

@ -73,6 +73,7 @@
"n8n-nodes-base": "^0.7.0",
"n8n-workflow": "^0.6.0",
"open": "^6.1.0",
"pg": "^7.11.0",
"request-promise-native": "^1.0.7",
"sqlite3": "^4.0.6",
"sse-channel": "^3.1.1",

View file

@ -18,6 +18,7 @@ import * as config from './../config';
import {
MongoDb,
PostgresDb,
SQLite,
} from './databases';
@ -43,6 +44,16 @@ export async function init(): Promise<IDatabaseCollections> {
url: config.get('database.mongodb.connectionUrl') as string,
useNewUrlParser: true,
};
} else if (dbType === 'postgresdb') {
entities = PostgresDb;
connectionOptions = {
type: 'postgres',
database: config.get('database.postgresdb.database'),
host: config.get('database.postgresdb.host'),
password: config.get('database.postgresdb.password'),
port: config.get('database.postgresdb.port'),
username: config.get('database.postgresdb.user'),
};
} else if (dbType === 'sqlite') {
entities = SQLite;
connectionOptions = {

View file

@ -41,8 +41,8 @@ export interface IWorkflowBase {
id?: number | string | ObjectID;
name: string;
active: boolean;
createdAt: number | string;
updatedAt: number | string;
createdAt: Date;
updatedAt: Date;
nodes: INode[];
connections: IConnections;
settings?: IWorkflowSettings;
@ -63,13 +63,13 @@ export interface IWorkflowShortResponse {
id: string;
name: string;
active: boolean;
createdAt: number | string;
updatedAt: number | string;
createdAt: Date;
updatedAt: Date;
}
export interface ICredentialsBase {
createdAt: number | string;
updatedAt: number | string;
createdAt: Date;
updatedAt: Date;
}
export interface ICredentialsDb extends ICredentialsBase, ICredentialsEncrypted{
@ -88,14 +88,14 @@ export interface ICredentialsDecryptedResponse extends ICredentialsDecryptedDb {
id: string;
}
export type DatabaseType = 'mongodb' | 'sqlite';
export type DatabaseType = 'mongodb' | 'postgresdb' | 'sqlite';
export type SaveExecutionDataType = 'all' | 'none';
export interface IExecutionBase {
id?: number | string | ObjectID;
mode: WorkflowExecuteMode;
startedAt: number;
stoppedAt: number;
startedAt: Date;
stoppedAt: Date;
workflowId?: string; // To be able to filter executions easily //
finished: boolean;
retryOf?: number | string | ObjectID; // If it is a retry, the id of the execution it is a retry of.
@ -148,8 +148,8 @@ export interface IExecutionsListResponse {
export interface IExecutionsStopData {
finished?: boolean;
mode: WorkflowExecuteMode;
startedAt: number | string;
stoppedAt: number | string;
startedAt: Date;
stoppedAt: Date;
}
export interface IExecutionsSummary {
@ -158,14 +158,14 @@ export interface IExecutionsSummary {
finished?: boolean;
retryOf?: string;
retrySuccessId?: string;
startedAt: number | string;
stoppedAt?: number | string;
startedAt: Date;
stoppedAt?: Date;
workflowId: string;
workflowName?: string;
}
export interface IExecutionDeleteFilter {
deleteBefore?: number;
deleteBefore?: Date;
filters?: IDataObject;
ids?: string[];
}
@ -186,6 +186,12 @@ export interface IN8nConfigDatabase {
mongodb: {
connectionUrl: string;
};
postgresdb: {
host: string;
password: string;
port: number;
user: string;
};
}
export interface IN8nConfigEndpoints {

View file

@ -105,8 +105,8 @@ class App {
* @returns {number}
* @memberof App
*/
getCurrentDate(): number {
return Math.floor(new Date().getTime());
getCurrentDate(): Date {
return new Date();
}
@ -851,7 +851,7 @@ class App {
id: data.id.toString(),
workflowId: data.workflowId,
mode:data.mode,
startedAt: data.startedAt,
startedAt: new Date(data.startedAt),
}
);
}
@ -873,8 +873,8 @@ class App {
const returnData: IExecutionsStopData = {
mode: result.mode,
startedAt: result.startedAt,
stoppedAt: result.stoppedAt,
startedAt: new Date(result.startedAt),
stoppedAt: new Date(result.stoppedAt),
finished: result.finished,
};

View file

@ -1,7 +1,9 @@
import * as MongoDb from './mongodb';
import * as PostgresDb from './postgresdb';
import * as SQLite from './sqlite';
export {
MongoDb,
PostgresDb,
SQLite,
};

View file

@ -33,9 +33,9 @@ export class CredentialsEntity implements ICredentialsDb {
@Column('json')
nodesAccess: ICredentialNodeAccess[];
@Column()
createdAt: number;
@Column('Date')
createdAt: Date;
@Column()
updatedAt: number;
@Column('Date')
updatedAt: Date;
}

View file

@ -36,11 +36,11 @@ export class ExecutionEntity implements IExecutionFlattedDb {
@Column()
retrySuccessId: string;
@Column()
startedAt: number;
@Column('Date')
startedAt: Date;
@Column()
stoppedAt: number;
@Column('Date')
stoppedAt: Date;
@Column('json')
workflowData: IWorkflowDb;

View file

@ -34,11 +34,11 @@ export class WorkflowEntity implements IWorkflowDb {
@Column('json')
connections: IConnections;
@Column()
createdAt: number;
@Column('Date')
createdAt: Date;
@Column()
updatedAt: number;
@Column('Date')
updatedAt: Date;
@Column('json')
settings?: IWorkflowSettings;

View file

@ -0,0 +1,44 @@
import {
ICredentialNodeAccess,
} from 'n8n-workflow';
import {
ICredentialsDb,
} from '../../';
import {
Column,
Entity,
Index,
PrimaryGeneratedColumn,
} from "typeorm";
@Entity()
export class CredentialsEntity implements ICredentialsDb {
@PrimaryGeneratedColumn()
id: number;
@Column({
length: 128
})
name: string;
@Column('text')
data: string;
@Index()
@Column({
length: 32
})
type: string;
@Column('json')
nodesAccess: ICredentialNodeAccess[];
@Column('timestamp')
createdAt: Date;
@Column('timestamp')
updatedAt: Date;
}

View file

@ -0,0 +1,51 @@
import {
WorkflowExecuteMode,
} from 'n8n-workflow';
import {
IExecutionFlattedDb,
IWorkflowDb,
} from '../../';
import {
Column,
Entity,
Index,
PrimaryGeneratedColumn,
} from "typeorm";
@Entity()
export class ExecutionEntity implements IExecutionFlattedDb {
@PrimaryGeneratedColumn()
id: number;
@Column('text')
data: string;
@Column()
finished: boolean;
@Column()
mode: WorkflowExecuteMode;
@Column({ nullable: true })
retryOf: string;
@Column({ nullable: true })
retrySuccessId: string;
@Column('timestamp')
startedAt: Date;
@Column('timestamp')
stoppedAt: Date;
@Column('json')
workflowData: IWorkflowDb;
@Index()
@Column({ nullable: true })
workflowId: string;
}

View file

@ -0,0 +1,55 @@
import {
IConnections,
IDataObject,
INode,
IWorkflowSettings,
} from 'n8n-workflow';
import {
IWorkflowDb,
} from '../../';
import {
Column,
Entity,
PrimaryGeneratedColumn,
} from "typeorm";
@Entity()
export class WorkflowEntity implements IWorkflowDb {
@PrimaryGeneratedColumn()
id: number;
@Column({
length: 128
})
name: string;
@Column()
active: boolean;
@Column('json')
nodes: INode[];
@Column('json')
connections: IConnections;
@Column('timestamp')
createdAt: Date;
@Column('timestamp')
updatedAt: Date;
@Column({
type: 'json',
nullable: true,
})
settings?: IWorkflowSettings;
@Column({
type: 'json',
nullable: true,
})
staticData?: IDataObject;
}

View file

@ -0,0 +1,3 @@
export * from './CredentialsEntity';
export * from './ExecutionEntity';
export * from './WorkflowEntity';

View file

@ -37,8 +37,8 @@ export class CredentialsEntity implements ICredentialsDb {
nodesAccess: ICredentialNodeAccess[];
@Column()
createdAt: number;
createdAt: Date;
@Column()
updatedAt: number;
updatedAt: Date;
}

View file

@ -39,10 +39,10 @@ export class ExecutionEntity implements IExecutionFlattedDb {
retrySuccessId: string;
@Column()
startedAt: number;
startedAt: Date;
@Column()
stoppedAt: number;
stoppedAt: Date;
@Column('simple-json')
workflowData: IWorkflowDb;

View file

@ -36,10 +36,10 @@ export class WorkflowEntity implements IWorkflowDb {
connections: IConnections;
@Column()
createdAt: number;
createdAt: Date;
@Column()
updatedAt: number;
updatedAt: Date;
@Column({
type: 'simple-json',

View file

@ -35,7 +35,7 @@ export class ActiveExecutions {
this.activeExecutions[executionId] = {
runExecutionData,
startedAt: new Date().getTime(),
startedAt: new Date(),
mode,
workflow,
postExecutePromises: [],

View file

@ -47,7 +47,7 @@ export interface IExecuteSingleFunctions extends IExecuteSingleFunctionsBase {
export interface IExecutingWorkflowData {
runExecutionData: IRunExecutionData;
startedAt: number;
startedAt: Date;
mode: WorkflowExecuteMode;
workflow: Workflow;
postExecutePromises: Array<IDeferredPromise<IRun>>;
@ -55,7 +55,7 @@ export interface IExecutingWorkflowData {
export interface IExecutionsCurrentSummary {
id: string;
startedAt: number;
startedAt: Date;
mode: WorkflowExecuteMode;
workflowId: string;
}

View file

@ -66,8 +66,8 @@ export class LoadNodeParameterOptions {
active: false,
connections: {},
nodes: Object.values(this.workflow.nodes),
createdAt: 0,
updatedAt: 0,
createdAt: new Date(),
updatedAt: new Date(),
};
}

View file

@ -623,8 +623,8 @@ export class WorkflowExecute {
const fullRunData: IRun = {
data: runExecutionData,
mode: this.mode,
startedAt,
stoppedAt: new Date().getTime(),
startedAt: new Date(startedAt),
stoppedAt: new Date(),
};
if (executionError !== undefined) {
@ -643,8 +643,8 @@ export class WorkflowExecute {
const fullRunData: IRun = {
data: runExecutionData,
mode: this.mode,
startedAt,
stoppedAt: new Date().getTime(),
startedAt: new Date(startedAt),
stoppedAt: new Date(),
};
fullRunData.data.resultData.error = {

View file

@ -50,7 +50,7 @@ describe('Credentials', () => {
{
nodeType: 'base.noOp',
user: 'userName',
date: 1234,
date: new Date(),
}
];

View file

@ -116,7 +116,7 @@ export interface IRestApi {
getActiveWorkflows(): Promise<string[]>;
getActivationError(id: string): Promise<IActivationError | undefined >;
getCurrentExecutions(filter: object): Promise<IExecutionsCurrentSummaryExtended[]>;
getPastExecutions(filter: object, limit: number, lastStartedAt?: number): Promise<IExecutionsListResponse>;
getPastExecutions(filter: object, limit: number, lastStartedAt?: Date): Promise<IExecutionsListResponse>;
stopCurrentExecution(executionId: string): Promise<IExecutionsStopData>;
makeRestApiRequest(method: string, endpoint: string, data?: any): Promise<any>; // tslint:disable-line:no-any
getSettings(): Promise<IN8nUISettings>;
@ -250,8 +250,8 @@ export interface IExecutionBase {
mode: WorkflowExecuteMode;
retryOf?: string;
retrySuccessId?: string;
startedAt: number;
stoppedAt: number;
startedAt: Date;
stoppedAt?: Date;
workflowId?: string; // To be able to filter executions easily //
}
@ -283,8 +283,8 @@ export interface IExecutionShortResponse {
};
mode: WorkflowExecuteMode;
finished: boolean;
startedAt: number | string;
stoppedAt: number | string;
startedAt: Date;
stoppedAt: Date;
executionTime?: number;
}
@ -297,8 +297,8 @@ export interface IExecutionsCurrentSummaryExtended {
id: string;
finished?: boolean;
mode: WorkflowExecuteMode;
startedAt: number | string;
stoppedAt?: number | string;
startedAt: Date;
stoppedAt?: Date;
workflowId: string;
workflowName?: string;
}
@ -306,8 +306,8 @@ export interface IExecutionsCurrentSummaryExtended {
export interface IExecutionsStopData {
finished?: boolean;
mode: WorkflowExecuteMode;
startedAt: number | string;
stoppedAt: number | string;
startedAt: Date;
stoppedAt: Date;
}
export interface IExecutionsSummary {
@ -316,14 +316,14 @@ export interface IExecutionsSummary {
finished?: boolean;
retryOf?: string;
retrySuccessId?: string;
startedAt: number | string;
stoppedAt?: number | string;
startedAt: Date;
stoppedAt?: Date;
workflowId: string;
workflowName?: string;
}
export interface IExecutionDeleteFilter {
deleteBefore?: number;
deleteBefore?: Date;
filters?: IDataObject;
ids?: string[];
}

View file

@ -110,7 +110,7 @@
{{(new Date().getTime() - new Date(scope.row.startedAt).getTime())/1000}} sec.
</span>
<span v-else>
{{(scope.row.stoppedAt - scope.row.startedAt) / 1000}} sec.
{{(new Date(scope.row.stoppedAt).getTime() - new Date(scope.row.startedAt).getTime()) / 1000}} sec.
</span>
</template>
</el-table-column>
@ -323,7 +323,7 @@ export default mixins(
const sendData: IExecutionDeleteFilter = {};
if (this.checkAll === true) {
sendData.deleteBefore = this.finishedExecutions[0].startedAt as number;
sendData.deleteBefore = this.finishedExecutions[0].startedAt as Date;
} else {
sendData.ids = Object.keys(this.selectedItems);
}
@ -387,11 +387,11 @@ export default mixins(
this.isDataLoading = true;
const filter = this.workflowFilter;
let lastStartedAt: number | undefined;
let lastStartedAt: Date | undefined;
if (this.finishedExecutions.length !== 0) {
const lastItem = this.finishedExecutions.slice(-1)[0];
lastStartedAt = lastItem.startedAt as number;
lastStartedAt = lastItem.startedAt as Date;
}
let data: IExecutionsListResponse;

View file

@ -269,7 +269,7 @@ export const restApi = Vue.extend({
// Returns all saved executions
// TODO: For sure needs some kind of default filter like last day, with max 10 results, ...
getPastExecutions: (filter: object, limit: number, lastStartedAt?: number): Promise<IExecutionsListResponse> => {
getPastExecutions: (filter: object, limit: number, lastStartedAt?: Date): Promise<IExecutionsListResponse> => {
let sendData = {};
if (filter) {
sendData = {

View file

@ -144,8 +144,8 @@ export const workflowRun = mixins(
id: '__IN_PROGRESS__',
finished: false,
mode: 'manual',
startedAt: new Date().getTime(),
stoppedAt: 0,
startedAt: new Date(),
stoppedAt: undefined,
workflowId: workflow.id,
data: {
resultData: {

View file

@ -37,7 +37,7 @@ export interface IGetCredentials {
export interface ICredentialNodeAccess {
nodeType: string;
user?: string;
date?: number;
date?: Date;
}
export interface ICredentialsDecrypted {
@ -466,8 +466,8 @@ export interface IRun {
data: IRunExecutionData;
finished?: boolean;
mode: WorkflowExecuteMode;
startedAt: number;
stoppedAt: number;
startedAt: Date;
stoppedAt: Date;
}