mirror of
https://github.com/IT4Change/gradido.git
synced 2026-02-06 09:56:05 +00:00
add email builder class for replacing sendEmailVariants later
This commit is contained in:
parent
99a98f6e84
commit
bc064bdecf
220
backend/src/emails/Email.builder.ts
Normal file
220
backend/src/emails/Email.builder.ts
Normal file
@ -0,0 +1,220 @@
|
||||
import { Contribution } from '@entity/Contribution'
|
||||
import { Transaction } from '@entity/Transaction'
|
||||
import { User } from '@entity/User'
|
||||
|
||||
import { CONFIG } from '@/config'
|
||||
import { LogError } from '@/server/LogError'
|
||||
import { decimalSeparatorByLanguage, resetInterface } from '@/util/utilities'
|
||||
|
||||
import { sendEmailTranslated } from './sendEmailTranslated'
|
||||
|
||||
export interface EmailLocals {
|
||||
firstName: string
|
||||
lastName: string
|
||||
locale: string
|
||||
supportEmail: string
|
||||
communityURL: string
|
||||
senderFirstName?: string
|
||||
senderLastName?: string
|
||||
senderEmail?: string
|
||||
contributionMemo?: string
|
||||
contributionAmount?: string
|
||||
overviewURL?: string
|
||||
activationLink?: string
|
||||
timeDurationObject?: Date
|
||||
resendLink?: string
|
||||
resetLink?: string
|
||||
transactionMemo?: string
|
||||
transactionAmount?: string
|
||||
[key: string]: string | Date | undefined
|
||||
}
|
||||
|
||||
export enum EmailType {
|
||||
NONE = 'none',
|
||||
ACCOUNT_ACTIVATION = 'accountActivation',
|
||||
ACCOUNT_MULTI_REGISTRATION = 'accountMultiRegistration',
|
||||
ADDED_CONTRIBUTION_MESSAGE = 'addedContributionMessage',
|
||||
CONTRIBUTION_CONFIRMED = 'contributionConfirmed',
|
||||
CONTRIBUTION_DELETED = 'contributionDeleted',
|
||||
CONTRIBUTION_DENIED = 'contributionDenied',
|
||||
CONTRIBUTION_CHANGED_BY_MODERATOR = 'contributionChangedByModerator',
|
||||
RESET_PASSWORD = 'resetPassword',
|
||||
TRANSACTION_LINK_REDEEMED = 'transactionLinkRedeemed',
|
||||
TRANSACTION_RECEIVED = 'transactionReceived',
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-extraneous-class
|
||||
export class EmailBuilder {
|
||||
private receiver: { to: string }
|
||||
private type: EmailType
|
||||
private locals: EmailLocals
|
||||
|
||||
// https://refactoring.guru/design-patterns/builder/typescript/example
|
||||
/**
|
||||
* A fresh builder instance should contain a blank product object, which is
|
||||
* used in further assembly.
|
||||
*/
|
||||
constructor() {
|
||||
this.reset()
|
||||
}
|
||||
|
||||
public reset(): void {
|
||||
this.receiver.to = ''
|
||||
this.type = EmailType.NONE
|
||||
this.locals = resetInterface(this.locals)
|
||||
}
|
||||
|
||||
protected setLocalsFromConfig(): void {
|
||||
this.locals.overviewURL = CONFIG.EMAIL_LINK_OVERVIEW
|
||||
this.locals.supportEmail = CONFIG.COMMUNITY_SUPPORT_MAIL
|
||||
this.locals.communityURL = CONFIG.COMMUNITY_URL
|
||||
switch (this.type) {
|
||||
case EmailType.ACCOUNT_ACTIVATION:
|
||||
case EmailType.ACCOUNT_MULTI_REGISTRATION:
|
||||
case EmailType.RESET_PASSWORD:
|
||||
this.locals.resendLink = CONFIG.EMAIL_LINK_FORGOTPASSWORD
|
||||
}
|
||||
}
|
||||
|
||||
protected checkIfFieldsSet(names: string[]): void {
|
||||
for (const name of names) {
|
||||
// eslint-disable-next-line security/detect-object-injection
|
||||
if (!this.locals[name]) {
|
||||
throw new LogError(`missing field with ${name}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* check if non default fields a set for type
|
||||
*/
|
||||
protected checkRequiredFields(): void {
|
||||
switch (this.type) {
|
||||
case EmailType.NONE:
|
||||
throw new LogError('please call setType before to set email type')
|
||||
case EmailType.ACCOUNT_ACTIVATION:
|
||||
this.checkIfFieldsSet(['activationLink', 'timeDurationObject', 'resendLink'])
|
||||
break
|
||||
case EmailType.ACCOUNT_MULTI_REGISTRATION:
|
||||
this.checkIfFieldsSet(['resendLink'])
|
||||
break
|
||||
// CONTRIBUTION_CONFIRMED has same required fields as ADDED_CONTRIBUTION_MESSAGE plus contributionAmount
|
||||
case EmailType.CONTRIBUTION_CONFIRMED:
|
||||
this.checkIfFieldsSet(['contributionAmount'])
|
||||
// eslint-disable-next-line no-fallthrough
|
||||
case EmailType.ADDED_CONTRIBUTION_MESSAGE:
|
||||
case EmailType.CONTRIBUTION_DELETED:
|
||||
case EmailType.CONTRIBUTION_DENIED:
|
||||
this.checkIfFieldsSet(['senderFirstName', 'senderLastName', 'contributionMemo'])
|
||||
break
|
||||
case EmailType.CONTRIBUTION_CHANGED_BY_MODERATOR:
|
||||
// this.checkIfFieldsSet([''])
|
||||
break
|
||||
case EmailType.RESET_PASSWORD:
|
||||
this.checkIfFieldsSet(['resetLink', 'timeDurationObject', 'resendLink'])
|
||||
break
|
||||
// TRANSACTION_LINK_REDEEMED has same required fields as TRANSACTION_RECEIVED plus transactionMemo
|
||||
case EmailType.TRANSACTION_LINK_REDEEMED:
|
||||
this.checkIfFieldsSet(['transactionMemo'])
|
||||
// eslint-disable-next-line no-fallthrough
|
||||
case EmailType.TRANSACTION_RECEIVED:
|
||||
this.checkIfFieldsSet([
|
||||
'senderFirstName',
|
||||
'senderLastName',
|
||||
'senderEmail',
|
||||
'transactionAmount',
|
||||
])
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Concrete Builders are supposed to provide their own methods for
|
||||
* retrieving results. That's because various types of builders may create
|
||||
* entirely different products that don't follow the same interface.
|
||||
* Therefore, such methods cannot be declared in the base Builder interface
|
||||
* (at least in a statically typed programming language).
|
||||
*
|
||||
* Usually, after returning the end result to the client, a builder instance
|
||||
* is expected to be ready to start producing another product. That's why
|
||||
* it's a usual practice to call the reset method at the end of the
|
||||
* `getProduct` method body. However, this behavior is not mandatory, and
|
||||
* you can make your builders wait for an explicit reset call from the
|
||||
* client code before disposing of the previous result.
|
||||
*/
|
||||
public sendEmail(): Promise<Record<string, unknown> | boolean | null> {
|
||||
this.setLocalsFromConfig()
|
||||
// will throw if a field is missing
|
||||
this.checkRequiredFields()
|
||||
const result = sendEmailTranslated({
|
||||
receiver: this.receiver,
|
||||
template: this.type.toString(),
|
||||
locals: this.locals,
|
||||
})
|
||||
this.reset()
|
||||
return result
|
||||
}
|
||||
|
||||
public setRecipient(recipient: User): this {
|
||||
this.receiver.to = `${recipient.firstName} ${recipient.lastName} <${recipient.emailContact.email}>`
|
||||
this.locals.firstName = recipient.firstName
|
||||
this.locals.lastName = recipient.lastName
|
||||
return this
|
||||
}
|
||||
|
||||
public setSender(sender: User): this {
|
||||
this.locals.senderEmail = sender.emailContact.email
|
||||
this.locals.senderFirstName = sender.firstName
|
||||
this.locals.senderLastName = sender.lastName
|
||||
return this
|
||||
}
|
||||
|
||||
public setType(type: EmailType): this {
|
||||
this.type = type
|
||||
return this
|
||||
}
|
||||
|
||||
public setLanguage(locale: string): this {
|
||||
this.locals.locale = locale
|
||||
return this
|
||||
}
|
||||
|
||||
public setResetLink(resetLink: string): this {
|
||||
this.locals.resentLink = resetLink
|
||||
return this
|
||||
}
|
||||
|
||||
public setContribution(contribution: Contribution): this {
|
||||
this.locals.contributionMemo = contribution.memo
|
||||
if (!this.locals.locale || this.locals.locale === '') {
|
||||
throw new LogError('missing locale please call setLanguage before')
|
||||
}
|
||||
this.locals.contributionAmount = decimalSeparatorByLanguage(
|
||||
contribution.amount,
|
||||
this.locals.locale,
|
||||
)
|
||||
return this
|
||||
}
|
||||
|
||||
public setTransaction(transaction: Transaction): this {
|
||||
this.locals.transactionMemo = transaction.memo
|
||||
if (!this.locals.locale || this.locals.locale === '') {
|
||||
throw new LogError('missing locale please call setLanguage before')
|
||||
}
|
||||
this.locals.transactionAmount = decimalSeparatorByLanguage(
|
||||
transaction.amount,
|
||||
this.locals.locale,
|
||||
)
|
||||
return this
|
||||
}
|
||||
|
||||
public setActivationLink(activationLink: string): this {
|
||||
this.locals.activationLink = activationLink
|
||||
return this
|
||||
}
|
||||
|
||||
public setTimeDurationObject(timeDurationObject: Date): this {
|
||||
this.locals.timeDurationObject = timeDurationObject
|
||||
return this
|
||||
}
|
||||
}
|
||||
@ -15,3 +15,17 @@ export const decimalSeparatorByLanguage = (a: Decimal, language: string): string
|
||||
|
||||
export const fullName = (firstName: string, lastName: string): string =>
|
||||
[firstName, lastName].filter(Boolean).join(' ')
|
||||
|
||||
// Function to reset an interface by chatGPT
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export function resetInterface<T extends Record<string, any>>(obj: T): T {
|
||||
// Iterate over all properties of the object
|
||||
for (const key in obj) {
|
||||
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
||||
// Set all optional properties to undefined
|
||||
// eslint-disable-next-line security/detect-object-injection
|
||||
obj[key] = undefined as T[Extract<keyof T, string>]
|
||||
}
|
||||
}
|
||||
return obj
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user