Added more explicit type for SMTP config and refactored transport initialization.

This commit is contained in:
baldo 2022-07-28 14:45:28 +02:00
parent d2ca8ed55b
commit 13e4895b81
6 changed files with 84 additions and 43 deletions

View file

@ -146,7 +146,7 @@ Dann die `config.json` anpassen nach belieben. Es gibt die folgenden Konfigurati
* **`server.email.from`** Absender für versendete E-Mails, z. B.: `"Freifunk Knotenformular <no-reply@musterstadt.freifunk.net>"` * **`server.email.from`** Absender für versendete E-Mails, z. B.: `"Freifunk Knotenformular <no-reply@musterstadt.freifunk.net>"`
* **`server.email.smtp`** Konfiguration des SMTP-Servers für den E-Mail-Versand entsprechend der Dokumentation unter * **`server.email.smtp`** Konfiguration des SMTP-Servers für den E-Mail-Versand entsprechend der Dokumentation unter
[https://nodemailer.com/2-0-0-beta/setup-smtp/](https://nodemailer.com/2-0-0-beta/setup-smtp/), z. B.: [https://nodemailer.com/smtp/](https://nodemailer.com/smtp/), z. B.:
``` ```
{ {

29
server/mail/index.ts Normal file
View file

@ -0,0 +1,29 @@
import {createTransport, Transporter} from "nodemailer";
import {config} from "../config";
import * as MailTemplateService from "../services/mailTemplateService";
import Mail from "nodemailer/lib/mailer";
import SMTPTransport from "nodemailer/lib/smtp-transport";
let transporterSingleton: Transporter | null = null;
function transporter() {
if (!transporterSingleton) {
const options = {
...config.server.email.smtp,
pool: true,
};
transporterSingleton = createTransport(new SMTPTransport(options));
MailTemplateService.configureTransporter(transporterSingleton);
}
return transporterSingleton;
}
export function init(): void {
transporter();
}
export async function send(options: Mail.Options): Promise<void> {
await transporter().sendMail(options);
}

View file

@ -5,6 +5,7 @@ import * as db from "./db/database"
import * as scheduler from "./jobs/scheduler" import * as scheduler from "./jobs/scheduler"
import * as router from "./router" import * as router from "./router"
import * as app from "./app" import * as app from "./app"
import * as mail from "./mail";
app.init(); app.init();
Logger.init(config.server.logging.enabled); Logger.init(config.server.logging.enabled);
@ -14,8 +15,8 @@ async function main() {
Logger.tag('main').info('Initializing...'); Logger.tag('main').info('Initializing...');
await db.init(); await db.init();
mail.init();
scheduler.init(); scheduler.init();
router.init(); router.init();
app.app.listen(config.server.port, '::'); app.app.listen(config.server.port, '::');

View file

@ -1,17 +1,15 @@
import _ from "lodash"; import _ from "lodash";
import deepExtend from "deep-extend";
import moment, {Moment} from "moment"; import moment, {Moment} from "moment";
import {createTransport, Transporter} from "nodemailer";
import {config} from "../config";
import {db} from "../db/database"; import {db} from "../db/database";
import Logger from "../logger"; import Logger from "../logger";
import * as MailTemplateService from "./mailTemplateService"; import * as MailTemplateService from "./mailTemplateService";
import * as Resources from "../utils/resources"; import * as Resources from "../utils/resources";
import {RestParams} from "../utils/resources"; import {RestParams} from "../utils/resources";
import { import {
EmailAddress, isJSONObject, EmailAddress,
isMailSortField, isMailType, JSONObject, isJSONObject,
isMailSortField,
isMailType,
Mail, Mail,
MailData, MailData,
MailId, MailId,
@ -21,6 +19,7 @@ import {
UnixTimestampSeconds UnixTimestampSeconds
} from "../types"; } from "../types";
import ErrorTypes from "../utils/errorTypes"; import ErrorTypes from "../utils/errorTypes";
import {send} from "../mail";
type EmaiQueueRow = { type EmaiQueueRow = {
id: MailId, id: MailId,
@ -35,26 +34,6 @@ type EmaiQueueRow = {
const MAIL_QUEUE_DB_BATCH_SIZE = 50; const MAIL_QUEUE_DB_BATCH_SIZE = 50;
// TODO: Extract transporter into own module and initialize during main().
let transporterSingleton: Transporter | null = null;
function transporter() {
if (!transporterSingleton) {
transporterSingleton = createTransport(deepExtend(
{},
config.server.email.smtp,
{
transport: 'smtp',
pool: true
} as JSONObject
));
MailTemplateService.configureTransporter(transporterSingleton);
}
return transporterSingleton;
}
async function sendMail(options: Mail): Promise<void> { async function sendMail(options: Mail): Promise<void> {
Logger Logger
.tag('mail', 'queue') .tag('mail', 'queue')
@ -73,7 +52,7 @@ async function sendMail(options: Mail): Promise<void> {
html: renderedTemplate.body html: renderedTemplate.body
}; };
await transporter().sendMail(mailOptions); await send(mailOptions);
Logger.tag('mail', 'queue').info('Mail[%d] has been send.', options.id); Logger.tag('mail', 'queue').info('Mail[%d] has been send.', options.id);
} }

View file

@ -1,5 +1,5 @@
import {ArrayField, Field, RawJsonField} from "sparkson" import {ArrayField, Field, RawJsonField} from "sparkson"
import {ClientConfig, isSite, isString, JSONObject, toIsNewtype, Url} from "./shared"; import {ClientConfig, DurationMilliseconds, isString, toIsNewtype, Url} from "./shared";
export type Username = string & { readonly __tag: unique symbol }; export type Username = string & { readonly __tag: unique symbol };
export const isUsername = toIsNewtype(isString, "" as Username); export const isUsername = toIsNewtype(isString, "" as Username);
@ -14,7 +14,8 @@ export class UsersConfig {
constructor( constructor(
@Field("user") public username: Username, @Field("user") public username: Username,
@Field("passwordHash") public passwordHash: PasswordHash, @Field("passwordHash") public passwordHash: PasswordHash,
) {} ) {
}
} }
export class LoggingConfig { export class LoggingConfig {
@ -22,51 +23,79 @@ export class LoggingConfig {
@Field("enabled") public enabled: boolean, @Field("enabled") public enabled: boolean,
@Field("debug") public debug: boolean, @Field("debug") public debug: boolean,
@Field("profile") public profile: boolean, @Field("profile") public profile: boolean,
) {} ) {
}
} }
export class InternalConfig { export class InternalConfig {
constructor( constructor(
@Field("active") public active: boolean, @Field("active") public active: boolean,
@ArrayField("users", UsersConfig) public users: UsersConfig[], @ArrayField("users", UsersConfig) public users: UsersConfig[],
) {} ) {
}
}
export class SMTPAuthConfig {
constructor(
@Field("user") public user: Username,
@Field("pass") public pass: CleartextPassword,
) {
}
}
// For details see: https://nodemailer.com/smtp/
export class SMTPConfig {
constructor(
@Field("host") public host?: string,
@Field("port") public port?: number,
@Field("auth") public auth?: SMTPAuthConfig,
@Field("secure") public secure?: boolean,
@Field("ignoreTLS") public ignoreTLS?: boolean,
@Field("requireTLS") public requireTLS?: boolean,
@Field("opportunisticTLS") public opportunisticTLS?: boolean,
@Field("name") public name?: string,
@Field("localAddress") public localAddress?: string,
@Field("connectionTimeout") public connectionTimeout?: DurationMilliseconds,
@Field("greetingTimeout") public greetingTimeout?: DurationMilliseconds,
@Field("socketTimeout") public socketTimeout?: DurationMilliseconds,
) {
}
} }
export class EmailConfig { export class EmailConfig {
constructor( constructor(
@Field("from") public from: string, @Field("from") public from: string,
@RawJsonField("smtp") public smtp: SMTPConfig,
// For details see: https://nodemailer.com/2-0-0-beta/setup-smtp/ ) {
@RawJsonField("smtp") public smtp: JSONObject, }
) {}
} }
export class ServerMapConfig { export class ServerMapConfig {
constructor( constructor(
@ArrayField("nodesJsonUrl", String) public nodesJsonUrl: Url[], @ArrayField("nodesJsonUrl", String) public nodesJsonUrl: Url[],
) {} ) {
}
} }
export class ServerConfig { export class ServerConfig {
constructor( constructor(
@Field("baseUrl") public baseUrl: Url, @Field("baseUrl") public baseUrl: Url,
@Field("port") public port: number, @Field("port") public port: number,
@Field("databaseFile") public databaseFile: string, @Field("databaseFile") public databaseFile: string,
@Field("peersPath") public peersPath: string, @Field("peersPath") public peersPath: string,
@Field("logging") public logging: LoggingConfig, @Field("logging") public logging: LoggingConfig,
@Field("internal") public internal: InternalConfig, @Field("internal") public internal: InternalConfig,
@Field("email") public email: EmailConfig, @Field("email") public email: EmailConfig,
@Field("map") public map: ServerMapConfig, @Field("map") public map: ServerMapConfig,
@Field("rootPath", true, undefined, "/") public rootPath: string, @Field("rootPath", true, undefined, "/") public rootPath: string,
) {} ) {
}
} }
export class Config { export class Config {
constructor( constructor(
@Field("server") public server: ServerConfig, @Field("server") public server: ServerConfig,
@Field("client") public client: ClientConfig, @Field("client") public client: ClientConfig,
) {} ) {
}
} }

View file

@ -344,6 +344,9 @@ export const isMAC = toIsNewtype(isString, "" as MAC);
export type DurationSeconds = number & { readonly __tag: unique symbol }; export type DurationSeconds = number & { readonly __tag: unique symbol };
export const isDurationSeconds = toIsNewtype(isNumber, NaN as DurationSeconds); export const isDurationSeconds = toIsNewtype(isNumber, NaN as DurationSeconds);
export type DurationMilliseconds = number & { readonly __tag: unique symbol };
export const isDurationMilliseconds = toIsNewtype(isNumber, NaN as DurationMilliseconds);
export type UnixTimestampSeconds = number & { readonly __tag: unique symbol }; export type UnixTimestampSeconds = number & { readonly __tag: unique symbol };
export const isUnixTimestampSeconds = toIsNewtype(isNumber, NaN as UnixTimestampSeconds); export const isUnixTimestampSeconds = toIsNewtype(isNumber, NaN as UnixTimestampSeconds);