chore: first commit

This commit is contained in:
mahyargdz
2025-06-24 11:26:06 +03:30
commit ccbf743ecb
186 changed files with 22258 additions and 0 deletions
+2
View File
@@ -0,0 +1,2 @@
export const SMS_CONFIG = "SMS_CONFIG";
export const S3_CONFIG = "S3_CONFIG";
+8
View File
@@ -0,0 +1,8 @@
export interface IFile {
fieldname: string;
originalname: string;
encoding: string;
mimetype: string;
buffer: Buffer;
size: number;
}
+1
View File
@@ -0,0 +1 @@
export type OtpCacheKeyType = "LOGIN" | "REGISTER" | "FORGOT_PASSWORD" | "INVOICE_VERIFY";
+51
View File
@@ -0,0 +1,51 @@
export interface IParameterArray {
name: TemplateParams;
value: string;
}
export interface ISmsResponse {
status: number;
message: string;
}
//----------------------------------------------
export interface ISmsGetLineResponse extends ISmsResponse {
data: string[];
}
export interface ISmsVerifyResponse extends ISmsResponse {
data: { MessageId: number; Cost: number };
}
//----------------------------------------------
export interface ISmsVerifyBody {
Parameters: IParameterArray[];
Mobile: string;
TemplateId: string;
}
export type TemplateParams =
| "VERIFICATIONCODE"
| "code"
| "invoiceId"
| "price"
| "items"
| "loginDate"
| "invoiceDate"
| "title"
| "description"
| "ticketId"
| "serviceName"
| "date"
| "amount"
| "newBalance"
| "reason"
| "dueDate"
| "lateFee"
| "paidDate"
| "user"
| "subject"
| "planName"
| "message"
| "blogTitle"
| "fullName"
| "mobile";
+23
View File
@@ -0,0 +1,23 @@
import { CACHE_MANAGER, Cache } from "@nestjs/cache-manager";
import { Inject, Injectable } from "@nestjs/common";
@Injectable()
export class CacheService {
constructor(@Inject(CACHE_MANAGER) private cacheManager: Cache) {}
async get<T>(key: string) {
return await this.cacheManager.get<T>(key);
}
async set(key: string, value: string, ttl: number = 120 * 1000) {
await this.cacheManager.set(key, value, ttl);
}
async del(key: string) {
await this.cacheManager.del(key);
}
async getTtl(key: string) {
return await this.cacheManager.ttl(key);
}
}
View File
+21
View File
@@ -0,0 +1,21 @@
export function numberFormat(number: number, locale: string = "fa-IR") {
return Intl.NumberFormat(locale).format(number);
}
export function dateFormat(date: Date | string | number, locale: string = "fa-IR") {
try {
// Convert to Date object if it's a string or number
const dateObj = date instanceof Date ? date : new Date(date);
// Check if the date is valid
if (isNaN(dateObj.getTime())) {
console.warn("Invalid date provided to dateFormat:", date);
return "Invalid Date";
}
return new Intl.DateTimeFormat(locale).format(dateObj);
} catch (error) {
console.error("Error formatting date:", error);
return "Invalid Date";
}
}
@@ -0,0 +1,65 @@
/**
* Message utility for handling dynamic string replacement in Farsi messages
*/
/**
* Replace placeholders in a message string with actual values
* @param message The message template with placeholders like [name], [value], etc.
* @param replacements Object containing key-value pairs for replacement
* @returns The message with placeholders replaced by actual values
*
* @example
* replaceMessagePlaceholders("دامنه [name] قبلا ثبت شده است", { name: "example.com" })
* // Returns: "دامنه example.com قبلا ثبت شده است"
*
* @example
* replaceMessagePlaceholders("یک رکورد MX با نام [name] که به [value] با اولویت [priority] اشاره کند اضافه کنید", {
* name: "@",
* value: "mail.example.com",
* priority: "10"
* })
* // Returns: "یک رکورد MX با نام @ که به mail.example.com با اولویت 10 اشاره کند اضافه کنید"
*/
export function replaceMessagePlaceholders(message: string, replacements: Record<string, string | number>): string {
let result = message;
// Replace each placeholder with its corresponding value
Object.entries(replacements).forEach(([key, value]) => {
const placeholder = `[${key}]`;
result = result.replace(placeholder, String(value));
});
console.log("result", result);
return result;
}
/**
* Common replacement values for domain-related messages
*/
export interface DomainMessageReplacements {
name?: string;
value?: string;
priority?: string | number;
type?: string;
count?: string | number;
token?: string;
error?: string;
}
/**
* Convenience function specifically for domain messages
* @param message The domain message template
* @param replacements Domain-specific replacement values
* @returns The message with placeholders replaced
*/
export function formatDomainMessage(message: string, replacements: DomainMessageReplacements): string {
// Filter out undefined values before passing to replaceMessagePlaceholders
const filteredReplacements: Record<string, string | number> = {};
Object.entries(replacements).forEach(([key, value]) => {
if (value !== undefined) {
filteredReplacements[key] = value;
}
});
return replaceMessagePlaceholders(message, filteredReplacements);
}
+44
View File
@@ -0,0 +1,44 @@
import { randomInt } from "node:crypto";
import { Injectable } from "@nestjs/common";
import { CacheService } from "./cache.service";
import { OtpCacheKeyType } from "../interfaces/IOtpKey";
@Injectable()
export class OTPService {
constructor(private cacheService: CacheService) {}
//
private generateOTP(): string {
return randomInt(10000, 99999).toString();
}
//
async generateAndSetInCache(identifier: string, cacheKey: OtpCacheKeyType) {
const otp = this.generateOTP();
const key = `${identifier}:${cacheKey}:OTP`;
await this.cacheService.set(key, otp, 180 * 1000);
return otp;
}
//
async verifyOtp(identifier: string, otpCode: string, cacheKey: OtpCacheKeyType) {
const key = `${identifier}:${cacheKey}:OTP`;
const codeInCache = await this.cacheService.get<string | null>(key);
return codeInCache && otpCode === codeInCache;
}
//
async delOtpFormCache(identifier: string, cacheKey: OtpCacheKeyType) {
const key = `${identifier}:${cacheKey}:OTP`;
await this.cacheService.del(key);
}
async checkExistOtp(identifier: string, cacheKey: OtpCacheKeyType) {
const key = `${identifier}:${cacheKey}:OTP`;
const codeInCache = await this.cacheService.get<string | null>(key);
if (!codeInCache) return null;
const ttlInMsSeconds = await this.cacheService.getTtl(key);
return Math.floor((ttlInMsSeconds! - Date.now()) / 1000);
}
}
+17
View File
@@ -0,0 +1,17 @@
import { PaginationDto } from "../../../common/DTO/pagination.dto";
export function PaginationUtils(paginationData: PaginationDto) {
const { limit = 10, page = 1 } = paginationData;
// const pageN = Number.parseInt(page) || 1;
// const limitN = Number.parseInt(limit) || 10;
const skip = (page - 1) * limit;
return {
// page: page,
// limit: limit > 50 ? 50 : limit,
limit: limit,
skip: skip,
};
}
+15
View File
@@ -0,0 +1,15 @@
import { Injectable } from "@nestjs/common";
import * as bcrypt from "bcrypt";
@Injectable()
export class PasswordService {
private readonly saltRounds = 12;
async hashPassword(password: string): Promise<string> {
return bcrypt.hash(password, this.saltRounds);
}
async comparePasswords(plainPassword: string, hashedPassword: string): Promise<boolean> {
return bcrypt.compare(plainPassword, hashedPassword);
}
}
+542
View File
@@ -0,0 +1,542 @@
import { HttpService } from "@nestjs/axios";
import { Inject, Injectable, InternalServerErrorException, Logger } from "@nestjs/common";
import { AxiosError } from "axios";
import { catchError, firstValueFrom } from "rxjs";
import { SmsMessage } from "../../../common/enums/message.enum";
import { ISmsConfigs } from "../../../configs/sms.config";
import { SMS_CONFIG } from "../constants";
import { ISmsGetLineResponse, ISmsVerifyBody, ISmsVerifyResponse } from "../interfaces/ISms";
@Injectable()
export class SmsService {
private readonly logger = new Logger(SmsService.name);
constructor(
@Inject(SMS_CONFIG) private smsConfigs: ISmsConfigs,
private readonly httpService: HttpService,
) {}
//************************************************* */
async sendSmsVerifyCode(mobile: string, otpCode: string) {
//
const smsData: ISmsVerifyBody = {
Parameters: [{ name: "code", value: otpCode }],
Mobile: mobile,
TemplateId: this.smsConfigs.SMS_PATTERN_OTP,
};
//
try {
const { data } = await firstValueFrom(
this.httpService
.post<ISmsVerifyResponse>(
`${this.smsConfigs.API_URL}/send/verify`,
{ ...smsData },
{
headers: { "X-API-KEY": this.smsConfigs.API_KEY },
},
)
.pipe(
catchError((err: AxiosError) => {
this.logger.error("error in sending verify sms", err);
throw new InternalServerErrorException("error in sending verify sms");
}),
),
);
return data;
} catch (error) {
this.logger.error("error in sending sms", error);
throw new InternalServerErrorException(SmsMessage.SEND_SMS_ERROR);
}
}
//************************************************* */
async sendInvoiceVerifyCode(mobile: string, otpCode: string, invoiceId: number, price: number, items: string) {
const smsData: ISmsVerifyBody = {
Parameters: [
{ name: "code", value: otpCode },
{ name: "invoiceId", value: invoiceId.toString() },
{ name: "price", value: Intl.NumberFormat("fa-IR").format(price) },
{ name: "items", value: items },
],
Mobile: mobile,
TemplateId: this.smsConfigs.SMS_PATTERN_INVOICE,
};
try {
const { data } = await firstValueFrom(
this.httpService
.post<ISmsVerifyResponse>(
`${this.smsConfigs.API_URL}/send/verify`,
{ ...smsData },
{
headers: { "X-API-KEY": this.smsConfigs.API_KEY },
},
)
.pipe(
catchError((err: AxiosError) => {
this.logger.error("error in sending verify sms", err);
throw new InternalServerErrorException("error in sending verify sms");
}),
),
);
return data;
} catch (error) {
this.logger.error("error in sending sms", error);
throw new InternalServerErrorException(SmsMessage.SEND_SMS_ERROR);
}
}
//************************************************* */
async sendLoginSms(mobile: string, loginDate: string) {
const smsData: ISmsVerifyBody = {
Parameters: [{ name: "loginDate", value: loginDate }],
Mobile: mobile,
TemplateId: this.smsConfigs.SMS_PATTERN_LOGIN,
};
try {
const { data } = await firstValueFrom(
this.httpService
.post<ISmsVerifyResponse>(`${this.smsConfigs.API_URL}/send/verify`, smsData, {
headers: { "X-API-KEY": this.smsConfigs.API_KEY },
})
.pipe(
catchError((err: AxiosError) => {
this.logger.error("error in sending verify sms", err);
throw new InternalServerErrorException("error in sending verify sms");
}),
),
);
return data;
} catch (error) {
this.logger.error("error in sending sms", error);
}
}
//************************************************* */
async sendInvoiceCreationSms(mobile: string, invoiceId: string, price: number, items: string, invoiceDate: string, dueDate: string) {
const smsData: ISmsVerifyBody = {
Parameters: [
{ name: "invoiceId", value: invoiceId },
{ name: "price", value: Intl.NumberFormat("fa-IR").format(price) },
{ name: "items", value: items },
{ name: "invoiceDate", value: invoiceDate },
{ name: "dueDate", value: dueDate },
],
Mobile: mobile,
TemplateId: this.smsConfigs.SMS_PATTERN_INVOICE_CREATED,
};
try {
const { data } = await firstValueFrom(
this.httpService
.post<ISmsVerifyResponse>(`${this.smsConfigs.API_URL}/send/verify`, smsData, {
headers: { "X-API-KEY": this.smsConfigs.API_KEY },
})
.pipe(
catchError((err: AxiosError) => {
console.error(err);
this.logger.error("error in sending invoice created sms", err);
throw new InternalServerErrorException("error in sending invoice created sms");
}),
),
);
return data;
} catch (error) {
this.logger.error("error in sending sms", error);
}
}
//************************************************* */
async sendAnnouncementSms(mobile: string, title: string, description: string, date: string) {
const smsData: ISmsVerifyBody = {
Parameters: [
{ name: "title", value: title },
{ name: "description", value: description },
{ name: "date", value: date },
],
Mobile: mobile,
TemplateId: this.smsConfigs.SMS_PATTERN_ANNOUNCEMENT,
};
try {
const { data } = await firstValueFrom(
this.httpService
.post<ISmsVerifyResponse>(`${this.smsConfigs.API_URL}/send/verify`, smsData, {
headers: { "X-API-KEY": this.smsConfigs.API_KEY },
})
.pipe(
catchError((err: AxiosError) => {
this.logger.error("error in sending announcement sms", err);
throw new InternalServerErrorException("error in sending announcement sms");
}),
),
);
return data;
} catch (error) {
this.logger.error("error in sending sms", error);
}
}
//************************************************* */
async sendCreateTicketSms(mobile: string, ticketId: string, subject: string, date: string) {
const smsData: ISmsVerifyBody = {
Parameters: [
{ name: "ticketId", value: ticketId },
{ name: "subject", value: subject },
{ name: "date", value: date },
],
Mobile: mobile,
TemplateId: this.smsConfigs.SMS_PATTERN_TICKET_CREATED,
};
try {
const { data } = await firstValueFrom(
this.httpService
.post<ISmsVerifyResponse>(`${this.smsConfigs.API_URL}/send/verify`, smsData, {
headers: { "X-API-KEY": this.smsConfigs.API_KEY },
})
.pipe(
catchError((err: AxiosError) => {
this.logger.error("error in sending ticket created sms", err);
throw new InternalServerErrorException("error in sending ticket created sms");
}),
),
);
return data;
} catch (error) {
this.logger.error("error in sending sms", error);
}
}
//************************************************* */
async sendAnswerTicketSms(mobile: string, ticketId: string, subject: string) {
const smsData: ISmsVerifyBody = {
Parameters: [
{ name: "ticketId", value: ticketId },
{ name: "subject", value: subject },
],
Mobile: mobile,
TemplateId: this.smsConfigs.SMS_PATTERN_TICKET_ANSWERED,
};
try {
const { data } = await firstValueFrom(
this.httpService
.post<ISmsVerifyResponse>(`${this.smsConfigs.API_URL}/send/verify`, smsData, {
headers: { "X-API-KEY": this.smsConfigs.API_KEY },
})
.pipe(
catchError((err: AxiosError) => {
this.logger.error("error in sending ticket answered sms", err);
throw new InternalServerErrorException("error in sending ticket answered sms");
}),
),
);
return data;
} catch (error) {
this.logger.error("error in sending sms", error);
}
}
//************************************************* */
async sendTicketAssignedToAdminSms(mobile: string, ticketId: string, subject: string, user: string) {
const smsData: ISmsVerifyBody = {
Parameters: [
{ name: "ticketId", value: ticketId },
{ name: "subject", value: subject },
{ name: "user", value: user },
],
Mobile: mobile,
TemplateId: this.smsConfigs.SMS_PATTERN_TICKET_ASSIGNED_ADMIN,
};
try {
const { data } = await firstValueFrom(
this.httpService
.post<ISmsVerifyResponse>(`${this.smsConfigs.API_URL}/send/verify`, smsData, {
headers: { "X-API-KEY": this.smsConfigs.API_KEY },
})
.pipe(
catchError((err: AxiosError) => {
this.logger.error("error in sending ticket assigned sms", err);
throw new InternalServerErrorException("error in sending ticket assigned sms");
}),
),
);
return data;
} catch (error) {
this.logger.error("error in sending sms", error);
}
}
//************************************************* */
async sendInvoiceApprovedSms(mobile: string, invoiceId: string, price: number, dueDate: string) {
const smsData: ISmsVerifyBody = {
Parameters: [
{ name: "invoiceId", value: invoiceId },
{ name: "price", value: Intl.NumberFormat("fa-IR").format(price) },
{ name: "dueDate", value: dueDate },
],
Mobile: mobile,
TemplateId: this.smsConfigs.SMS_PATTERN_INVOICE_APPROVED,
};
try {
const { data } = await firstValueFrom(
this.httpService
.post<ISmsVerifyResponse>(`${this.smsConfigs.API_URL}/send/verify`, smsData, {
headers: { "X-API-KEY": this.smsConfigs.API_KEY },
})
.pipe(
catchError((err: AxiosError) => {
this.logger.error("error in sending invoice approved sms", err);
throw new InternalServerErrorException("error in sending invoice approved sms");
}),
),
);
return data;
} catch (error) {
this.logger.error("error in sending sms", error);
// throw new InternalServerErrorException("error in sending sms");
}
}
//************************************************* */
async sendInvoicePaidSms(mobile: string, invoiceId: string, amount: number, paidDate: string) {
const smsData: ISmsVerifyBody = {
Parameters: [
{ name: "invoiceId", value: invoiceId },
{ name: "amount", value: amount.toString() },
{ name: "paidDate", value: paidDate },
],
Mobile: mobile,
TemplateId: this.smsConfigs.SMS_PATTERN_INVOICE_PAID,
};
try {
const { data } = await firstValueFrom(
this.httpService
.post<ISmsVerifyResponse>(`${this.smsConfigs.API_URL}/send/verify`, smsData, {
headers: { "X-API-KEY": this.smsConfigs.API_KEY },
})
.pipe(
catchError((err: AxiosError) => {
this.logger.error("error in sending invoice paid sms", err);
throw new InternalServerErrorException("error in sending invoice paid sms");
}),
),
);
return data;
} catch (error) {
this.logger.error("error in sending sms", error);
// throw new InternalServerErrorException("error in sending sms");
}
}
//************************************************* */
async sendInvoicePaymentReminderSms(mobile: string, invoiceId: string, amount: number, dueDate: string) {
const smsData: ISmsVerifyBody = {
Parameters: [
{ name: "invoiceId", value: invoiceId },
{ name: "amount", value: amount.toString() },
{ name: "dueDate", value: dueDate },
],
Mobile: mobile,
TemplateId: this.smsConfigs.SMS_PATTERN_INVOICE_REMINDER,
};
try {
const { data } = await firstValueFrom(
this.httpService
.post<ISmsVerifyResponse>(`${this.smsConfigs.API_URL}/send/verify`, smsData, {
headers: { "X-API-KEY": this.smsConfigs.API_KEY },
})
.pipe(
catchError((err: AxiosError) => {
this.logger.error("error in sending invoice reminder sms", err);
throw new InternalServerErrorException("error in sending invoice reminder sms");
}),
),
);
return data;
} catch (error) {
this.logger.error("error in sending sms", error);
}
}
//************************************************* */
async sendInvoiceOverdueSms(mobile: string, invoiceId: string, amount: number, lateFee: number, dueDate: string) {
const smsData: ISmsVerifyBody = {
Parameters: [
{ name: "invoiceId", value: invoiceId },
{ name: "amount", value: amount.toString() },
{ name: "lateFee", value: lateFee.toString() },
{ name: "dueDate", value: dueDate },
],
Mobile: mobile,
TemplateId: this.smsConfigs.SMS_PATTERN_INVOICE_OVERDUE,
};
try {
const { data } = await firstValueFrom(
this.httpService
.post<ISmsVerifyResponse>(`${this.smsConfigs.API_URL}/send/verify`, smsData, {
headers: { "X-API-KEY": this.smsConfigs.API_KEY },
})
.pipe(
catchError((err: AxiosError) => {
this.logger.error("error in sending invoice overdue sms", err);
throw new InternalServerErrorException("error in sending invoice overdue sms");
}),
),
);
return data;
} catch (error) {
this.logger.error("error in sending sms", error);
}
}
//************************************************* */
async sendRecurringInvoiceDraftSms(adminMobile: string, invoiceId: string, price: number) {
const smsData: ISmsVerifyBody = {
Parameters: [
{ name: "invoiceId", value: invoiceId },
{ name: "price", value: Intl.NumberFormat("fa-IR").format(price) },
],
Mobile: adminMobile,
TemplateId: this.smsConfigs.SMS_PATTERN_RECURRING_INVOICE_DRAFT,
};
try {
const { data } = await firstValueFrom(
this.httpService
.post<ISmsVerifyResponse>(`${this.smsConfigs.API_URL}/send/verify`, smsData, {
headers: { "X-API-KEY": this.smsConfigs.API_KEY },
})
.pipe(
catchError((err: AxiosError) => {
this.logger.error("error in sending recurring invoice draft sms", err);
throw new InternalServerErrorException("error in sending recurring invoice draft sms");
}),
),
);
return data;
} catch (error) {
this.logger.error("error in sending sms", error);
// throw new InternalServerErrorException("error in sending sms");
}
}
//************************************************* */
async sendBlockServiceSms(mobile: string, invoiceId: string, planName: string) {
const smsData: ISmsVerifyBody = {
Parameters: [
{ name: "invoiceId", value: invoiceId },
{ name: "planName", value: planName },
],
Mobile: mobile,
TemplateId: this.smsConfigs.SMS_PATTERN_SUBSCRIPTION_CANCELLED,
};
try {
const { data } = await firstValueFrom(
this.httpService
.post<ISmsVerifyResponse>(`${this.smsConfigs.API_URL}/send/verify`, smsData, {
headers: { "X-API-KEY": this.smsConfigs.API_KEY },
})
.pipe(
catchError((err: AxiosError) => {
this.logger.error("error in sending subscription cancelled sms", err);
throw new InternalServerErrorException("error in sending subscription cancelled sms");
}),
),
);
return data;
} catch (error) {
this.logger.error("error in sending sms", error);
// throw new InternalServerErrorException("error in sending sms");
}
}
//************************************************* */
//************************************************* */
async sendPaymentReminderSms(mobile: string, amount: string) {
const smsData: ISmsVerifyBody = {
Parameters: [{ name: "amount", value: amount }],
Mobile: mobile,
TemplateId: this.smsConfigs.SMS_PATTERN_PAYMENT_REMINDER,
};
try {
const { data } = await firstValueFrom(
this.httpService
.post<ISmsVerifyResponse>(`${this.smsConfigs.API_URL}/send/verify`, smsData, {
headers: { "X-API-KEY": this.smsConfigs.API_KEY },
})
.pipe(
catchError((err: AxiosError) => {
this.logger.error("error in sending payment reminder sms", err);
throw new InternalServerErrorException("error in sending payment reminder sms");
}),
),
);
return data;
} catch (error) {
this.logger.error("error in sending sms", error);
}
}
//************************************************* */
async sendPaymentCancellationSms(mobile: string, amount: string) {
const smsData: ISmsVerifyBody = {
Parameters: [{ name: "amount", value: amount }],
Mobile: mobile,
TemplateId: this.smsConfigs.SMS_PATTERN_PAYMENT_CANCELLATION,
};
try {
const { data } = await firstValueFrom(
this.httpService
.post<ISmsVerifyResponse>(`${this.smsConfigs.API_URL}/send/verify`, smsData, {
headers: { "X-API-KEY": this.smsConfigs.API_KEY },
})
.pipe(
catchError((err: AxiosError) => {
this.logger.error("error in sending payment cancellation sms", err);
throw new InternalServerErrorException("error in sending payment cancellation sms");
}),
),
);
return data;
} catch (error) {
this.logger.error("error in sending sms", error);
}
}
async getSmsLines() {
try {
const { data } = await firstValueFrom(
this.httpService
.get<ISmsGetLineResponse>(`${this.smsConfigs.API_URL}/lines`, {
headers: { "X-API-KEY": this.smsConfigs.API_KEY },
})
.pipe(
catchError((error: AxiosError) => {
this.logger.error("error in getting sms lines", error);
throw new InternalServerErrorException("error in getting sms lines");
}),
),
);
return data;
} catch (error) {
this.logger.error("error in getting sms lines", error);
}
}
}
+30
View File
@@ -0,0 +1,30 @@
import { Module } from "@nestjs/common";
import { S3_CONFIG, SMS_CONFIG } from "./constants";
import { CacheService } from "./services/cache.service";
import { OTPService } from "./services/otp.service";
import { PasswordService } from "./services/password.service";
import { SmsService } from "./services/sms.service";
import { S3Configs } from "../../configs/s3.config";
import { smsConfigs } from "../../configs/sms.config";
@Module({
providers: [
OTPService,
PasswordService,
CacheService,
SmsService,
{
provide: SMS_CONFIG,
useFactory: smsConfigs().useFactory,
inject: smsConfigs().inject,
},
{
provide: S3_CONFIG,
useFactory: S3Configs().useFactory,
inject: S3Configs().inject,
},
],
exports: [SMS_CONFIG, S3_CONFIG, OTPService, PasswordService, CacheService, SmsService],
})
export class UtilsModule {}