mirror of
https://gitlab.com/openstapps/openstapps.git
synced 2026-01-10 19:52:53 +00:00
18
README.md
18
README.md
@@ -8,28 +8,30 @@ This is a simple logger for TypeScript projects with colors for console output.
|
|||||||
Logs are only printed if their log level is equal or higher than the defined log level.
|
Logs are only printed if their log level is equal or higher than the defined log level.
|
||||||
|
|
||||||
## Log Levels
|
## Log Levels
|
||||||
Available Log levels are:
|
Available log levels are:
|
||||||
- 1 - INFO
|
- 1 - INFO
|
||||||
- 2 - LOG
|
- 2 - LOG
|
||||||
- 4 - WARN
|
- 4 - WARN
|
||||||
- 8 - ERROR
|
- 8 - ERROR
|
||||||
- 16 - OK
|
- 16 - OK
|
||||||
|
|
||||||
You can set your Log Level with the environment variable
|
You can set your log level with the environment variable `STAPPS_LOG_LEVEL`.
|
||||||
`STAPPS_LOG_LEVEL` in a binary way.
|
|
||||||
|
|
||||||
For example `STAPPS_LOG_LEVEL=17` is 16 + 1 and would log everything
|
To select your desired log levels add the corresponding numbers and set the value of `STAPPS_LOG_LEVEL` to the sum.
|
||||||
that is `OK` or `ERROR`.
|
|
||||||
|
|
||||||
If you want to use Logger in production (`NODE_ENV=production`) and allow all transports to fail set `ALLOW_NO_TRANSPORT` to `true`.
|
For example `STAPPS_LOG_LEVEL=17` is 16 + 1 and would log everything that is `OK` or `INFO`.
|
||||||
|
|
||||||
|
If you want to use logger in production (`NODE_ENV=production`) and allow all transports to fail set
|
||||||
|
`ALLOW_NO_TRANSPORT` to `true`.
|
||||||
|
|
||||||
## SMTP
|
## SMTP
|
||||||
This class also provides a simple implementation of an smtp transport which can be used as a
|
This class also provides a simple implementation of an SMTP transport which can be used as a
|
||||||
`TransportWithVerification` for the logger. You can use this to transport errors of the logger or to transport mails
|
`TransportWithVerification` for the logger. You can use this to transport errors of the logger or to transport mails
|
||||||
of your own monitoring solution.
|
of your own monitoring solution.
|
||||||
|
|
||||||
### Usage
|
### Usage
|
||||||
You can instatiate it with a config or it will check for a config in the environment variables.
|
You can instatiate it with a config or it will check for a config in the environment variables. Environment variables
|
||||||
|
can overwrite the actual config values.
|
||||||
|
|
||||||
Environment variables are:
|
Environment variables are:
|
||||||
* SMTP_AUTH_USER: SMTP username
|
* SMTP_AUTH_USER: SMTP username
|
||||||
|
|||||||
146
src/SMTP.ts
146
src/SMTP.ts
@@ -14,6 +14,7 @@
|
|||||||
*/
|
*/
|
||||||
import * as nodemailer from 'nodemailer';
|
import * as nodemailer from 'nodemailer';
|
||||||
import { MailOptions } from 'nodemailer/lib/sendmail-transport';
|
import { MailOptions } from 'nodemailer/lib/sendmail-transport';
|
||||||
|
import { deleteUndefinedProperties, RecursivePartial } from './common';
|
||||||
import { Logger } from './Logger';
|
import { Logger } from './Logger';
|
||||||
import { TransportWithVerification } from './Transport';
|
import { TransportWithVerification } from './Transport';
|
||||||
|
|
||||||
@@ -93,55 +94,6 @@ export class SMTP extends TransportWithVerification {
|
|||||||
return this._instance;
|
return this._instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if no config is given, we assume it is set via environment variables
|
|
||||||
if (typeof config === 'undefined') {
|
|
||||||
if (typeof process.env.SMTP_AUTH_USER !== 'string') {
|
|
||||||
throw new Error('User of SMTP configuration is empty. Please provide a user.');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof process.env.SMTP_AUTH_PASSWORD !== 'string') {
|
|
||||||
throw new Error('Password of SMTP configuration is empty. Please provide a password.');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof process.env.SMTP_HOST !== 'string') {
|
|
||||||
throw new Error('Host of SMTP configuration is empty. Please provide a host.');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof process.env.SMTP_PORT !== 'string') {
|
|
||||||
throw new Error('Port of SMTP configuration is empty. Please provide a port.');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof process.env.SMTP_RECIPIENTS !== 'string') {
|
|
||||||
throw new Error('Recipients of SMTP configuration is empty. Please provide at least one recipient.');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof process.env.SMTP_SENDER_MAIL !== 'string') {
|
|
||||||
throw new Error('Sender of SMTP configuration is empty. Please provide a sender.');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof process.env.SMTP_SENDER_NAME !== 'string') {
|
|
||||||
throw new Error('Name of sender of SMTP configuration is empty. Please provide a name for the sender.');
|
|
||||||
}
|
|
||||||
|
|
||||||
config = {
|
|
||||||
auth: {
|
|
||||||
password: process.env.SMTP_AUTH_PASSWORD,
|
|
||||||
user: process.env.SMTP_AUTH_USER,
|
|
||||||
},
|
|
||||||
cc: ((typeof process.env.SMTP_CC === 'string') ? (process.env.SMTP_CC as string).split(',') : []),
|
|
||||||
host: process.env.SMTP_HOST,
|
|
||||||
port: parseInt(process.env.SMTP_PORT, 10),
|
|
||||||
recipients: (typeof process.env.SMTP_RECIPIENTS === 'string') ?
|
|
||||||
(process.env.SMTP_RECIPIENTS).split(',') :
|
|
||||||
[],
|
|
||||||
secure: (typeof process.env.SMTP_SECURE === 'string') ? (process.env.SMTP_SECURE === 'true') : false,
|
|
||||||
sender: {
|
|
||||||
mail: process.env.SMTP_SENDER_MAIL,
|
|
||||||
name: process.env.SMTP_SENDER_NAME,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// monitoring is not required -> SMTP init can fail
|
// monitoring is not required -> SMTP init can fail
|
||||||
if (!Logger.isProductiveEnvironment() || process.env.ALLOW_NO_TRANSPORT === 'true') {
|
if (!Logger.isProductiveEnvironment() || process.env.ALLOW_NO_TRANSPORT === 'true') {
|
||||||
try {
|
try {
|
||||||
@@ -185,7 +137,7 @@ export class SMTP extends TransportWithVerification {
|
|||||||
* @param {string[]} recipients
|
* @param {string[]} recipients
|
||||||
* @return {string[]}
|
* @return {string[]}
|
||||||
*/
|
*/
|
||||||
public static isValidRecipientsList(recipients: string[]): boolean {
|
public static isValidRecipientsList(recipients: string[] | undefined): boolean {
|
||||||
return Array.isArray(recipients) && recipients.length > 0 && recipients.every(this.isValidEmailAddress);
|
return Array.isArray(recipients) && recipients.length > 0 && recipients.every(this.isValidEmailAddress);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,46 +146,88 @@ export class SMTP extends TransportWithVerification {
|
|||||||
* call `new SMTP()`
|
* call `new SMTP()`
|
||||||
* @param {SMTPConfig} config
|
* @param {SMTPConfig} config
|
||||||
*/
|
*/
|
||||||
private constructor(config: SMTPConfig) {
|
private constructor(config?: SMTPConfig) {
|
||||||
if (typeof config.host !== 'string') {
|
// create a partial config from environment variables that can overwrite the given config
|
||||||
throw new Error('SMTP configuration needs a host.');
|
const envConfig: RecursivePartial<SMTPConfig> = {
|
||||||
|
auth: {
|
||||||
|
password: process.env.SMTP_AUTH_PASSWORD,
|
||||||
|
user: process.env.SMTP_AUTH_USER,
|
||||||
|
},
|
||||||
|
cc: ((typeof process.env.SMTP_CC === 'string') ? (process.env.SMTP_CC as string).split(',') : []),
|
||||||
|
host: process.env.SMTP_HOST,
|
||||||
|
port: (typeof process.env.SMTP_PORT === 'string') ? parseInt(process.env.SMTP_PORT, 10) : undefined,
|
||||||
|
recipients: (typeof process.env.SMTP_RECIPIENTS === 'string') ?
|
||||||
|
(process.env.SMTP_RECIPIENTS).split(',') :
|
||||||
|
[],
|
||||||
|
secure: (typeof process.env.SMTP_SECURE === 'string') ? (process.env.SMTP_SECURE === 'true') : false,
|
||||||
|
sender: {
|
||||||
|
mail: process.env.SMTP_SENDER_MAIL,
|
||||||
|
name: process.env.SMTP_SENDER_NAME,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
config = {
|
||||||
|
...config,
|
||||||
|
// deleting undefined properties so the actual config doesn't get overwritten by undefined values
|
||||||
|
...deleteUndefinedProperties(envConfig),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (typeof config!.host !== 'string') {
|
||||||
|
throw new Error(
|
||||||
|
'SMTP configuration needs a host. Add it to the config or use environment variables (SMTP_HOST).',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof config.port !== 'number') {
|
if (typeof config!.port !== 'number' || isNaN(config!.port)) {
|
||||||
throw new Error('SMTP configuration needs a port.');
|
throw new Error(
|
||||||
|
'SMTP configuration needs a port. Add it to the config or use environment variables (SMTP_PORT).',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof config.auth !== 'object') {
|
if (typeof config!.auth !== 'object') {
|
||||||
throw new Error('SMTP configuration needs an auth object.');
|
throw new Error(
|
||||||
|
'SMTP configuration needs an auth object.' +
|
||||||
|
'Add it to the config or use environment variables (SMTP_AUTH_USER, SMTP_AUTH_PASSWORD).',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof config.auth.user !== 'string') {
|
if (typeof config!.auth.user !== 'string') {
|
||||||
throw new Error('SMTP auth configuration needs a user');
|
throw new Error(
|
||||||
|
'SMTP auth configuration needs a user. Add it to the config or use environment variables (SMTP_AUTH_USER).',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof config.auth.password !== 'string') {
|
if (typeof config!.auth.password !== 'string') {
|
||||||
throw new Error('SMTP auth configuration needs a password');
|
throw new Error(
|
||||||
|
'SMTP auth configuration needs a password.' +
|
||||||
|
'Add it to the config or use environment variables (SMTP_AUTH_PASSWORD).',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Array.isArray(config.recipients) && config.recipients.length < 1) {
|
if (Array.isArray(config!.recipients) && config!.recipients.length < 1) {
|
||||||
throw new Error('SMTP configuration needs recipients.');
|
throw new Error(
|
||||||
|
'SMTP configuration needs recipients. Add it to the config or use environment variables (SMTP_RECIPIENTS).',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof config.sender.mail !== 'string') {
|
if (typeof config!.sender.mail !== 'string') {
|
||||||
throw new Error('SMTP configuration needs a sender');
|
throw new Error(
|
||||||
|
'SMTP configuration needs a sender. Add it to the config or use environment variables (SMTP_SENDER_MAIL).',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
super();
|
super();
|
||||||
|
|
||||||
if (SMTP.isValidRecipientsList(config.recipients)) {
|
if (SMTP.isValidRecipientsList(config!.recipients)) {
|
||||||
this.recipients = config.recipients;
|
this.recipients = config!.recipients;
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Invalid recipients found');
|
throw new Error('Invalid recipients found');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof config.cc !== 'undefined') {
|
if (typeof config!.cc !== 'undefined') {
|
||||||
if (SMTP.isValidRecipientsList(config.cc) || (Array.isArray(config.cc) && config.cc.length === 0)) {
|
if (SMTP.isValidRecipientsList(config!.cc)) {
|
||||||
this.cc = config.cc;
|
this.cc = config!.cc!;
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Invalid cc recipients found');
|
throw new Error('Invalid cc recipients found');
|
||||||
}
|
}
|
||||||
@@ -241,19 +235,19 @@ export class SMTP extends TransportWithVerification {
|
|||||||
this.cc = [];
|
this.cc = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
this.from = config.sender;
|
this.from = config!.sender;
|
||||||
|
|
||||||
this.verified = false;
|
this.verified = false;
|
||||||
|
|
||||||
// creating transport with configuration
|
// creating transport with configuration
|
||||||
this.transportAgent = nodemailer.createTransport({
|
this.transportAgent = nodemailer.createTransport({
|
||||||
auth: {
|
auth: {
|
||||||
pass: config.auth.password,
|
pass: config!.auth.password,
|
||||||
user: config.auth.user,
|
user: config!.auth.user,
|
||||||
},
|
},
|
||||||
host: config.host,
|
host: config!.host,
|
||||||
port: config.port,
|
port: config!.port,
|
||||||
secure: typeof config.secure === 'boolean' ? config.secure : false,
|
secure: typeof config!.secure === 'boolean' ? config!.secure : false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
49
src/common.ts
Normal file
49
src/common.ts
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2019 StApps
|
||||||
|
* This program is free software: you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License as published by the Free
|
||||||
|
* Software Foundation, version 3.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along with
|
||||||
|
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A recursive partial object
|
||||||
|
*
|
||||||
|
* Copied from https://stackoverflow.com/a/51365037
|
||||||
|
*/
|
||||||
|
export type RecursivePartial<T> = {
|
||||||
|
[P in keyof T]?: T[P] extends Array<infer U> ?
|
||||||
|
Array<RecursivePartial<U>> :
|
||||||
|
T[P] extends object ? RecursivePartial<T[P]> : T[P];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes all properties that are undefined from an object
|
||||||
|
* @param obj
|
||||||
|
*/
|
||||||
|
export function deleteUndefinedProperties(obj: any) {
|
||||||
|
// return atomic data types and arrays (recursion anchor)
|
||||||
|
if (typeof obj !== 'object' || Array.isArray(obj)) {
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check each key
|
||||||
|
Object.keys(obj).forEach((key) => {
|
||||||
|
if (typeof obj[key] === 'undefined') {
|
||||||
|
// delete undefined keys
|
||||||
|
delete obj[key];
|
||||||
|
} else {
|
||||||
|
// check recursive
|
||||||
|
obj[key] = deleteUndefinedProperties(obj[key]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
70
test/Common.spec.ts
Normal file
70
test/Common.spec.ts
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2019 StApps
|
||||||
|
* This program is free software: you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License as published by the Free
|
||||||
|
* Software Foundation, version 3.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along with
|
||||||
|
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
import { expect } from 'chai';
|
||||||
|
import { slow, suite, test, timeout } from 'mocha-typescript';
|
||||||
|
import { deleteUndefinedProperties } from '../src/common';
|
||||||
|
|
||||||
|
@suite(timeout(2000), slow(1000))
|
||||||
|
export class CommonSpec {
|
||||||
|
/* tslint:disable:member-ordering */
|
||||||
|
@test
|
||||||
|
deleteUndefinedProperties1() {
|
||||||
|
expect(deleteUndefinedProperties(
|
||||||
|
{
|
||||||
|
a: 2,
|
||||||
|
b: {
|
||||||
|
c: 3,
|
||||||
|
d: undefined,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)).to.deep.equal(
|
||||||
|
{
|
||||||
|
a: 2,
|
||||||
|
b: {
|
||||||
|
c: 3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@test
|
||||||
|
deleteUndefinedProperties2() {
|
||||||
|
expect(deleteUndefinedProperties(
|
||||||
|
{
|
||||||
|
a: undefined,
|
||||||
|
b: undefined,
|
||||||
|
},
|
||||||
|
)).to.deep.equal(
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@test
|
||||||
|
deleteUndefinedProperties3() {
|
||||||
|
expect(deleteUndefinedProperties(
|
||||||
|
{
|
||||||
|
a: 2,
|
||||||
|
b: 'foo',
|
||||||
|
c: 'bar',
|
||||||
|
},
|
||||||
|
)).to.deep.equal(
|
||||||
|
{
|
||||||
|
a: 2,
|
||||||
|
b: 'foo',
|
||||||
|
c: 'bar',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user