feat(nestjs): migrate to NestJS

This commit is contained in:
Ahmed Bouhuolia
2025-04-07 11:51:24 +02:00
parent f068218a16
commit 55fcc908ef
3779 changed files with 631 additions and 195332 deletions

View File

@@ -0,0 +1,82 @@
import { castArray } from 'lodash';
import { Inject, Injectable } from '@nestjs/common';
import { MailTenancy } from '../MailTenancy/MailTenancy.service';
import { TenancyContext } from '../Tenancy/TenancyContext.service';
import { Customer } from '../Customers/models/Customer';
import { CommonMailOptions } from './MailNotification.types';
import { formatMessage } from '@/utils/format-message';
import { TenantModelProxy } from '../System/models/TenantBaseModel';
@Injectable()
export class ContactMailNotification {
constructor(
private readonly mailTenancy: MailTenancy,
private readonly tenantContext: TenancyContext,
@Inject(Customer.name)
private readonly customerModel: TenantModelProxy<typeof Customer>,
) {}
/**
* Gets the default mail address of the given contact.
* @param {number} invoiceId - Contact id.
* @returns {Promise<Pick<CommonMailOptions, 'to' | 'from'>>}
*/
public async getDefaultMailOptions(
customerId: number,
): Promise<
Pick<CommonMailOptions, 'to' | 'from' | 'toOptions' | 'fromOptions'>
> {
const customer = await this.customerModel()
.query()
.findById(customerId)
.throwIfNotFound();
const toOptions = customer.contactAddresses;
const fromOptions = await this.mailTenancy.senders();
const toAddress = toOptions.find((a) => a.primary);
const fromAddress = fromOptions.find((a) => a.primary);
const to = toAddress?.mail ? castArray(toAddress?.mail) : [];
const from = fromAddress?.mail ? castArray(fromAddress?.mail) : [];
return { to, from, toOptions, fromOptions };
}
/**
* Retrieves the mail options of the given contact.
* @param {number} tenantId - Tenant id.
* @returns {Promise<CommonMailOptions>}
*/
public async formatMailOptions(
mailOptions: CommonMailOptions,
formatterArgs?: Record<string, any>,
): Promise<CommonMailOptions> {
const commonFormatArgs = await this.getCommonFormatArgs();
const formatArgs = {
...commonFormatArgs,
...formatterArgs,
};
const subjectFormatted = formatMessage(mailOptions?.subject, formatArgs);
const messageFormatted = formatMessage(mailOptions?.message, formatArgs);
return {
...mailOptions,
subject: subjectFormatted,
message: messageFormatted,
};
}
/**
* Retrieves the common format args.
* @returns {Promise<Record<string, string>>}
*/
public async getCommonFormatArgs(): Promise<Record<string, string>> {
const tenantMetadata = await this.tenantContext.getTenantMetadata();
return {
['Company Name']: tenantMetadata.name,
};
}
}

View File

@@ -0,0 +1,11 @@
import { Module } from '@nestjs/common';
import { ContactMailNotification } from './ContactMailNotification';
import { MailTenancyModule } from '../MailTenancy/MailTenancy.module';
import { TenancyContext } from '../Tenancy/TenancyContext.service';
@Module({
imports: [MailTenancyModule],
providers: [ContactMailNotification, TenancyContext],
exports: [ContactMailNotification],
})
export class MailNotificationModule {}

View File

@@ -0,0 +1,44 @@
export type IMailAttachment = MailAttachmentPath | MailAttachmentContent;
export interface MailAttachmentPath {
filename: string;
path: string;
cid: string;
}
export interface MailAttachmentContent {
filename: string;
content: Buffer;
}
export interface IMailable {
constructor(view: string, data?: { [key: string]: string | number });
send(): Promise<any>;
build(): void;
setData(data: { [key: string]: string | number }): IMailable;
setTo(to: string): IMailable;
setFrom(from: string): IMailable;
setSubject(subject: string): IMailable;
setView(view: string): IMailable;
render(data?: { [key: string]: string | number }): string;
getViewContent(): string;
}
export interface AddressItem {
label: string;
mail: string;
primary?: boolean;
}
export interface CommonMailOptions {
from: Array<string>;
subject: string;
message: string;
to: Array<string>;
cc?: Array<string>;
bcc?: Array<string>;
formatArgs?: Record<string, any>;
toOptions: Array<AddressItem>;
fromOptions: Array<AddressItem>;
}
export interface CommonMailOptionsDTO extends Partial<CommonMailOptions> {}

View File

@@ -0,0 +1,6 @@
export const ERRORS = {
MAIL_FROM_NOT_FOUND: 'Mail from address not found',
MAIL_TO_NOT_FOUND: 'Mail to address not found',
MAIL_SUBJECT_NOT_FOUND: 'Mail subject not found',
MAIL_BODY_NOT_FOUND: 'Mail body not found',
};

View File

@@ -0,0 +1,56 @@
import { castArray, isEmpty } from 'lodash';
import { ERRORS } from './constants';
import { CommonMailOptions } from './MailNotification.types';
import { ServiceError } from '../Items/ServiceError';
/**
* Merges the mail options with incoming options.
* @param {Partial<SaleInvoiceMailOptions>} mailOptions
* @param {Partial<SendInvoiceMailDTO>} overridedOptions
*/
export function parseMailOptions(
mailOptions: CommonMailOptions,
overridedOptions: Partial<CommonMailOptions>
): CommonMailOptions {
const mergedMessageOptions = {
...mailOptions,
...overridedOptions,
};
const parsedMessageOptions = {
...mergedMessageOptions,
from: mergedMessageOptions?.from
? castArray(mergedMessageOptions?.from)
: [],
to: mergedMessageOptions?.to ? castArray(mergedMessageOptions?.to) : [],
cc: mergedMessageOptions?.cc ? castArray(mergedMessageOptions?.cc) : [],
bcc: mergedMessageOptions?.bcc ? castArray(mergedMessageOptions?.bcc) : [],
};
return parsedMessageOptions;
}
export function validateRequiredMailOptions(
mailOptions: Partial<CommonMailOptions>
) {
if (isEmpty(mailOptions.from)) {
throw new ServiceError(ERRORS.MAIL_FROM_NOT_FOUND);
}
if (isEmpty(mailOptions.to)) {
throw new ServiceError(ERRORS.MAIL_TO_NOT_FOUND);
}
if (isEmpty(mailOptions.subject)) {
throw new ServiceError(ERRORS.MAIL_SUBJECT_NOT_FOUND);
}
if (isEmpty(mailOptions.message)) {
throw new ServiceError(ERRORS.MAIL_BODY_NOT_FOUND);
}
}
export const mergeAndValidateMailOptions = (
mailOptions: CommonMailOptions,
overridedOptions: Partial<CommonMailOptions>
): CommonMailOptions => {
const parsedMessageOptions = parseMailOptions(mailOptions, overridedOptions);
validateRequiredMailOptions(parsedMessageOptions);
return parsedMessageOptions;
};