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. 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 n8n however also supports MongoDB and PostgresDB. To use them simply a few
`DB_MONGODB_CONNECTION_URL` like in the example bellow. 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: Replace the following placeholders with the actual data:
- MONGO_DATABASE - 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 ## Execute Workflow from CLI
Workflows can not just be started by triggers, webhooks or manually via the Workflows can not just be started by triggers, webhooks or manually via the

View file

@ -8,7 +8,7 @@ const config = convict({
database: { database: {
type: { type: {
doc: 'Type of database to use', doc: 'Type of database to use',
format: ['sqlite', 'mongodb'], format: ['sqlite', 'mongodb', 'postgresdb'],
default: 'sqlite', default: 'sqlite',
env: 'DB_TYPE' env: 'DB_TYPE'
}, },
@ -20,6 +20,38 @@ const config = convict({
env: 'DB_MONGODB_CONNECTION_URL' 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: { executions: {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -34,11 +34,11 @@ export class WorkflowEntity implements IWorkflowDb {
@Column('json') @Column('json')
connections: IConnections; connections: IConnections;
@Column() @Column('Date')
createdAt: number; createdAt: Date;
@Column() @Column('Date')
updatedAt: number; updatedAt: Date;
@Column('json') @Column('json')
settings?: IWorkflowSettings; 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[]; nodesAccess: ICredentialNodeAccess[];
@Column() @Column()
createdAt: number; createdAt: Date;
@Column() @Column()
updatedAt: number; updatedAt: Date;
} }

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -269,7 +269,7 @@ export const restApi = Vue.extend({
// Returns all saved executions // Returns all saved executions
// TODO: For sure needs some kind of default filter like last day, with max 10 results, ... // 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 = {}; let sendData = {};
if (filter) { if (filter) {
sendData = { sendData = {

View file

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

View file

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