refactor(core): Move backend config to a separate package (no-changelog) (#9325)
This commit is contained in:
committed by
GitHub
parent
1d5b9836ca
commit
c7d4b471c4
25
packages/@n8n/config/src/configs/credentials.ts
Normal file
25
packages/@n8n/config/src/configs/credentials.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { Config, Env, Nested } from '../decorators';
|
||||
|
||||
@Config
|
||||
class CredentialsOverwrite {
|
||||
/**
|
||||
* Prefilled data ("overwrite") in credential types. End users cannot view or change this data.
|
||||
* Format: { CREDENTIAL_NAME: { PARAMETER: VALUE }}
|
||||
*/
|
||||
@Env('CREDENTIALS_OVERWRITE_DATA')
|
||||
readonly data: string = '{}';
|
||||
|
||||
/** Internal API endpoint to fetch overwritten credential types from. */
|
||||
@Env('CREDENTIALS_OVERWRITE_ENDPOINT')
|
||||
readonly endpoint: string = '';
|
||||
}
|
||||
|
||||
@Config
|
||||
export class CredentialsConfig {
|
||||
/** Default name for credentials */
|
||||
@Env('CREDENTIALS_DEFAULT_NAME')
|
||||
readonly defaultName: string = 'My credentials';
|
||||
|
||||
@Nested
|
||||
readonly overwrite: CredentialsOverwrite;
|
||||
}
|
||||
151
packages/@n8n/config/src/configs/database.ts
Normal file
151
packages/@n8n/config/src/configs/database.ts
Normal file
@@ -0,0 +1,151 @@
|
||||
import { Config, Env, Nested } from '../decorators';
|
||||
|
||||
@Config
|
||||
class LoggingConfig {
|
||||
/** Whether database logging is enabled. */
|
||||
@Env('DB_LOGGING_ENABLED')
|
||||
readonly enabled: boolean = false;
|
||||
|
||||
/**
|
||||
* Database logging level. Requires `DB_LOGGING_MAX_EXECUTION_TIME` to be higher than `0`.
|
||||
*/
|
||||
@Env('DB_LOGGING_OPTIONS')
|
||||
readonly options: 'query' | 'error' | 'schema' | 'warn' | 'info' | 'log' | 'all' = 'error';
|
||||
|
||||
/**
|
||||
* Only queries that exceed this time (ms) will be logged. Set `0` to disable.
|
||||
*/
|
||||
@Env('DB_LOGGING_MAX_EXECUTION_TIME')
|
||||
readonly maxQueryExecutionTime: number = 0;
|
||||
}
|
||||
|
||||
@Config
|
||||
class PostgresSSLConfig {
|
||||
/**
|
||||
* Whether to enable SSL.
|
||||
* If `DB_POSTGRESDB_SSL_CA`, `DB_POSTGRESDB_SSL_CERT`, or `DB_POSTGRESDB_SSL_KEY` are defined, `DB_POSTGRESDB_SSL_ENABLED` defaults to `true`.
|
||||
*/
|
||||
@Env('DB_POSTGRESDB_SSL_ENABLED')
|
||||
readonly enabled: boolean = false;
|
||||
|
||||
/** SSL certificate authority */
|
||||
@Env('DB_POSTGRESDB_SSL_CA')
|
||||
readonly ca: string = '';
|
||||
|
||||
/** SSL certificate */
|
||||
@Env('DB_POSTGRESDB_SSL_CERT')
|
||||
readonly cert: string = '';
|
||||
|
||||
/** SSL key */
|
||||
@Env('DB_POSTGRESDB_SSL_KEY')
|
||||
readonly key: string = '';
|
||||
|
||||
/** If unauthorized SSL connections should be rejected */
|
||||
@Env('DB_POSTGRESDB_SSL_REJECT_UNAUTHORIZED')
|
||||
readonly rejectUnauthorized: boolean = true;
|
||||
}
|
||||
|
||||
@Config
|
||||
class PostgresConfig {
|
||||
/** Postgres database name */
|
||||
@Env('DB_POSTGRESDB_DATABASE')
|
||||
database: string = 'n8n';
|
||||
|
||||
/** Postgres database host */
|
||||
@Env('DB_POSTGRESDB_HOST')
|
||||
readonly host: string = 'localhost';
|
||||
|
||||
/** Postgres database password */
|
||||
@Env('DB_POSTGRESDB_PASSWORD')
|
||||
readonly password: string = '';
|
||||
|
||||
/** Postgres database port */
|
||||
@Env('DB_POSTGRESDB_PORT')
|
||||
readonly port: number = 5432;
|
||||
|
||||
/** Postgres database user */
|
||||
@Env('DB_POSTGRESDB_USER')
|
||||
readonly user: string = 'postgres';
|
||||
|
||||
/** Postgres database schema */
|
||||
@Env('DB_POSTGRESDB_SCHEMA')
|
||||
readonly schema: string = 'public';
|
||||
|
||||
/** Postgres database pool size */
|
||||
@Env('DB_POSTGRESDB_POOL_SIZE')
|
||||
readonly poolSize = 2;
|
||||
|
||||
@Nested
|
||||
readonly ssl: PostgresSSLConfig;
|
||||
}
|
||||
|
||||
@Config
|
||||
class MysqlConfig {
|
||||
/** @deprecated MySQL database name */
|
||||
@Env('DB_MYSQLDB_DATABASE')
|
||||
database: string = 'n8n';
|
||||
|
||||
/** MySQL database host */
|
||||
@Env('DB_MYSQLDB_HOST')
|
||||
readonly host: string = 'localhost';
|
||||
|
||||
/** MySQL database password */
|
||||
@Env('DB_MYSQLDB_PASSWORD')
|
||||
readonly password: string = '';
|
||||
|
||||
/** MySQL database port */
|
||||
@Env('DB_MYSQLDB_PORT')
|
||||
readonly port: number = 3306;
|
||||
|
||||
/** MySQL database user */
|
||||
@Env('DB_MYSQLDB_USER')
|
||||
readonly user: string = 'root';
|
||||
}
|
||||
|
||||
@Config
|
||||
class SqliteConfig {
|
||||
/** SQLite database file name */
|
||||
@Env('DB_SQLITE_DATABASE')
|
||||
readonly database: string = 'database.sqlite';
|
||||
|
||||
/** SQLite database pool size. Set to `0` to disable pooling. */
|
||||
@Env('DB_SQLITE_POOL_SIZE')
|
||||
readonly poolSize: number = 0;
|
||||
|
||||
/**
|
||||
* Enable SQLite WAL mode.
|
||||
*/
|
||||
@Env('DB_SQLITE_ENABLE_WAL')
|
||||
readonly enableWAL: boolean = this.poolSize > 1;
|
||||
|
||||
/**
|
||||
* Run `VACUUM` on startup to rebuild the database, reducing file size and optimizing indexes.
|
||||
*
|
||||
* @warning Long-running blocking operation that will increase startup time.
|
||||
*/
|
||||
@Env('DB_SQLITE_VACUUM_ON_STARTUP')
|
||||
readonly executeVacuumOnStartup: boolean = false;
|
||||
}
|
||||
|
||||
@Config
|
||||
export class DatabaseConfig {
|
||||
/** Type of database to use */
|
||||
@Env('DB_TYPE')
|
||||
type: 'sqlite' | 'mariadb' | 'mysqldb' | 'postgresdb' = 'sqlite';
|
||||
|
||||
/** Prefix for table names */
|
||||
@Env('DB_TABLE_PREFIX')
|
||||
readonly tablePrefix: string = '';
|
||||
|
||||
@Nested
|
||||
readonly logging: LoggingConfig;
|
||||
|
||||
@Nested
|
||||
readonly postgresdb: PostgresConfig;
|
||||
|
||||
@Nested
|
||||
readonly mysqldb: MysqlConfig;
|
||||
|
||||
@Nested
|
||||
readonly sqlite: SqliteConfig;
|
||||
}
|
||||
78
packages/@n8n/config/src/configs/email.ts
Normal file
78
packages/@n8n/config/src/configs/email.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
import { Config, Env, Nested } from '../decorators';
|
||||
|
||||
@Config
|
||||
export class SmtpAuth {
|
||||
/** SMTP login username */
|
||||
@Env('N8N_SMTP_USER')
|
||||
readonly user: string = '';
|
||||
|
||||
/** SMTP login password */
|
||||
@Env('N8N_SMTP_PASS')
|
||||
readonly pass: string = '';
|
||||
|
||||
/** SMTP OAuth Service Client */
|
||||
@Env('N8N_SMTP_OAUTH_SERVICE_CLIENT')
|
||||
readonly serviceClient: string = '';
|
||||
|
||||
/** SMTP OAuth Private Key */
|
||||
@Env('N8N_SMTP_OAUTH_PRIVATE_KEY')
|
||||
readonly privateKey: string = '';
|
||||
}
|
||||
|
||||
@Config
|
||||
export class SmtpConfig {
|
||||
/** SMTP server host */
|
||||
@Env('N8N_SMTP_HOST')
|
||||
readonly host: string = '';
|
||||
|
||||
/** SMTP server port */
|
||||
@Env('N8N_SMTP_PORT')
|
||||
readonly port: number = 465;
|
||||
|
||||
/** Whether to use SSL for SMTP */
|
||||
@Env('N8N_SMTP_SSL')
|
||||
readonly secure: boolean = true;
|
||||
|
||||
/** Whether to use STARTTLS for SMTP when SSL is disabled */
|
||||
@Env('N8N_SMTP_STARTTLS')
|
||||
readonly startTLS: boolean = true;
|
||||
|
||||
/** How to display sender name */
|
||||
@Env('N8N_SMTP_SENDER')
|
||||
readonly sender: string = '';
|
||||
|
||||
@Nested
|
||||
readonly auth: SmtpAuth;
|
||||
}
|
||||
|
||||
@Config
|
||||
export class TemplateConfig {
|
||||
/** Overrides default HTML template for inviting new people (use full path) */
|
||||
@Env('N8N_UM_EMAIL_TEMPLATES_INVITE')
|
||||
readonly invite: string = '';
|
||||
|
||||
/** Overrides default HTML template for resetting password (use full path) */
|
||||
@Env('N8N_UM_EMAIL_TEMPLATES_PWRESET')
|
||||
readonly passwordReset: string = '';
|
||||
|
||||
/** Overrides default HTML template for notifying that a workflow was shared (use full path) */
|
||||
@Env('N8N_UM_EMAIL_TEMPLATES_WORKFLOW_SHARED')
|
||||
readonly workflowShared: string = '';
|
||||
|
||||
/** Overrides default HTML template for notifying that credentials were shared (use full path) */
|
||||
@Env('N8N_UM_EMAIL_TEMPLATES_CREDENTIALS_SHARED')
|
||||
readonly credentialsShared: string = '';
|
||||
}
|
||||
|
||||
@Config
|
||||
export class EmailConfig {
|
||||
/** How to send emails */
|
||||
@Env('N8N_EMAIL_MODE')
|
||||
readonly mode: '' | 'smtp' = 'smtp';
|
||||
|
||||
@Nested
|
||||
readonly smtp: SmtpConfig;
|
||||
|
||||
@Nested
|
||||
readonly template: TemplateConfig;
|
||||
}
|
||||
79
packages/@n8n/config/src/decorators.ts
Normal file
79
packages/@n8n/config/src/decorators.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
import 'reflect-metadata';
|
||||
import { readFileSync } from 'fs';
|
||||
import { Container, Service } from 'typedi';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
type Class = Function;
|
||||
type PropertyKey = string | symbol;
|
||||
interface PropertyMetadata {
|
||||
type: unknown;
|
||||
envName?: string;
|
||||
}
|
||||
|
||||
const globalMetadata = new Map<Class, Map<PropertyKey, PropertyMetadata>>();
|
||||
|
||||
export const Config: ClassDecorator = (ConfigClass: Class) => {
|
||||
const factory = function () {
|
||||
const config = new (ConfigClass as new () => Record<PropertyKey, unknown>)();
|
||||
const classMetadata = globalMetadata.get(ConfigClass);
|
||||
if (!classMetadata) {
|
||||
// eslint-disable-next-line n8n-local-rules/no-plain-errors
|
||||
throw new Error('Invalid config class: ' + ConfigClass.name);
|
||||
}
|
||||
|
||||
for (const [key, { type, envName }] of classMetadata) {
|
||||
if (typeof type === 'function' && globalMetadata.has(type)) {
|
||||
config[key] = Container.get(type);
|
||||
} else if (envName) {
|
||||
let value: unknown = process.env[envName];
|
||||
|
||||
// Read the value from a file, if "_FILE" environment variable is defined
|
||||
const filePath = process.env[`${envName}_FILE`];
|
||||
if (filePath) {
|
||||
value = readFileSync(filePath, 'utf8');
|
||||
}
|
||||
|
||||
if (type === Number) {
|
||||
value = Number(value);
|
||||
if (isNaN(value as number)) {
|
||||
// TODO: add a warning
|
||||
value = undefined;
|
||||
}
|
||||
} else if (type === Boolean) {
|
||||
if (value !== 'true' && value !== 'false') {
|
||||
// TODO: add a warning
|
||||
value = undefined;
|
||||
} else {
|
||||
value = value === 'true';
|
||||
}
|
||||
}
|
||||
|
||||
if (value !== undefined) {
|
||||
config[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return config;
|
||||
};
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
||||
return Service({ factory })(ConfigClass);
|
||||
};
|
||||
|
||||
export const Nested: PropertyDecorator = (target: object, key: PropertyKey) => {
|
||||
const ConfigClass = target.constructor;
|
||||
const classMetadata = globalMetadata.get(ConfigClass) ?? new Map<PropertyKey, PropertyMetadata>();
|
||||
const type = Reflect.getMetadata('design:type', target, key) as unknown;
|
||||
classMetadata.set(key, { type });
|
||||
globalMetadata.set(ConfigClass, classMetadata);
|
||||
};
|
||||
|
||||
export const Env =
|
||||
(envName: string): PropertyDecorator =>
|
||||
(target: object, key: PropertyKey) => {
|
||||
const ConfigClass = target.constructor;
|
||||
const classMetadata =
|
||||
globalMetadata.get(ConfigClass) ?? new Map<PropertyKey, PropertyMetadata>();
|
||||
const type = Reflect.getMetadata('design:type', target, key) as unknown;
|
||||
classMetadata.set(key, { type, envName });
|
||||
globalMetadata.set(ConfigClass, classMetadata);
|
||||
};
|
||||
22
packages/@n8n/config/src/index.ts
Normal file
22
packages/@n8n/config/src/index.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { Config, Nested } from './decorators';
|
||||
import { CredentialsConfig } from './configs/credentials';
|
||||
import { DatabaseConfig } from './configs/database';
|
||||
import { EmailConfig } from './configs/email';
|
||||
|
||||
@Config
|
||||
class UserManagementConfig {
|
||||
@Nested
|
||||
emails: EmailConfig;
|
||||
}
|
||||
|
||||
@Config
|
||||
export class GlobalConfig {
|
||||
@Nested
|
||||
database: DatabaseConfig;
|
||||
|
||||
@Nested
|
||||
credentials: CredentialsConfig;
|
||||
|
||||
@Nested
|
||||
userManagement: UserManagementConfig;
|
||||
}
|
||||
Reference in New Issue
Block a user