diff --git a/README.md b/README.md index 42c5d23d..a6cb9777 100644 --- a/README.md +++ b/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. ## Log Levels -Available Log levels are: +Available log levels are: - 1 - INFO - 2 - LOG - 4 - WARN - 8 - ERROR - 16 - OK -You can set your Log Level with the environment variable -`STAPPS_LOG_LEVEL` in a binary way. +You can set your log level with the environment variable `STAPPS_LOG_LEVEL`. -For example `STAPPS_LOG_LEVEL=17` is 16 + 1 and would log everything -that is `OK` or `ERROR`. +To select your desired log levels add the corresponding numbers and set the value of `STAPPS_LOG_LEVEL` to the sum. -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 -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 of your own monitoring solution. ### 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: * SMTP_AUTH_USER: SMTP username diff --git a/src/SMTP.ts b/src/SMTP.ts index 4c74127c..02a294d3 100644 --- a/src/SMTP.ts +++ b/src/SMTP.ts @@ -14,6 +14,7 @@ */ import * as nodemailer from 'nodemailer'; import { MailOptions } from 'nodemailer/lib/sendmail-transport'; +import { deleteUndefinedProperties, RecursivePartial } from './common'; import { Logger } from './Logger'; import { TransportWithVerification } from './Transport'; @@ -93,55 +94,6 @@ export class SMTP extends TransportWithVerification { 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 if (!Logger.isProductiveEnvironment() || process.env.ALLOW_NO_TRANSPORT === 'true') { try { @@ -185,7 +137,7 @@ export class SMTP extends TransportWithVerification { * @param {string[]} recipients * @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); } @@ -194,46 +146,88 @@ export class SMTP extends TransportWithVerification { * call `new SMTP()` * @param {SMTPConfig} config */ - private constructor(config: SMTPConfig) { - if (typeof config.host !== 'string') { - throw new Error('SMTP configuration needs a host.'); + private constructor(config?: SMTPConfig) { + // create a partial config from environment variables that can overwrite the given config + const envConfig: RecursivePartial = { + 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') { - throw new Error('SMTP configuration needs a port.'); + if (typeof config!.port !== 'number' || isNaN(config!.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') { - throw new Error('SMTP configuration needs an auth object.'); + if (typeof config!.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') { - throw new Error('SMTP auth configuration needs a user'); + if (typeof config!.auth.user !== 'string') { + 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') { - throw new Error('SMTP auth configuration needs a password'); + if (typeof config!.auth.password !== 'string') { + 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) { - throw new Error('SMTP configuration needs recipients.'); + if (Array.isArray(config!.recipients) && config!.recipients.length < 1) { + throw new Error( + 'SMTP configuration needs recipients. Add it to the config or use environment variables (SMTP_RECIPIENTS).', + ); } - if (typeof config.sender.mail !== 'string') { - throw new Error('SMTP configuration needs a sender'); + if (typeof config!.sender.mail !== 'string') { + throw new Error( + 'SMTP configuration needs a sender. Add it to the config or use environment variables (SMTP_SENDER_MAIL).', + ); } super(); - if (SMTP.isValidRecipientsList(config.recipients)) { - this.recipients = config.recipients; + if (SMTP.isValidRecipientsList(config!.recipients)) { + this.recipients = config!.recipients; } else { throw new Error('Invalid recipients found'); } - if (typeof config.cc !== 'undefined') { - if (SMTP.isValidRecipientsList(config.cc) || (Array.isArray(config.cc) && config.cc.length === 0)) { - this.cc = config.cc; + if (typeof config!.cc !== 'undefined') { + if (SMTP.isValidRecipientsList(config!.cc)) { + this.cc = config!.cc!; } else { throw new Error('Invalid cc recipients found'); } @@ -241,19 +235,19 @@ export class SMTP extends TransportWithVerification { this.cc = []; } - this.from = config.sender; + this.from = config!.sender; this.verified = false; // creating transport with configuration this.transportAgent = nodemailer.createTransport({ auth: { - pass: config.auth.password, - user: config.auth.user, + pass: config!.auth.password, + user: config!.auth.user, }, - host: config.host, - port: config.port, - secure: typeof config.secure === 'boolean' ? config.secure : false, + host: config!.host, + port: config!.port, + secure: typeof config!.secure === 'boolean' ? config!.secure : false, }); } diff --git a/src/common.ts b/src/common.ts new file mode 100644 index 00000000..2bfcc48f --- /dev/null +++ b/src/common.ts @@ -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 . + */ + + /** + * A recursive partial object + * + * Copied from https://stackoverflow.com/a/51365037 + */ +export type RecursivePartial = { + [P in keyof T]?: T[P] extends Array ? + Array> : + T[P] extends object ? RecursivePartial : 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; +} diff --git a/test/Common.spec.ts b/test/Common.spec.ts new file mode 100644 index 00000000..36700ab6 --- /dev/null +++ b/test/Common.spec.ts @@ -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 . + */ +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', + }, + ); + } +}