chore: add email service and password service

This commit is contained in:
mahyargdz
2025-01-20 09:13:27 +03:30
parent 7347a696d0
commit 9701c51445
18 changed files with 10575 additions and 24 deletions
+17 -1
View File
@@ -11,4 +11,20 @@ DB_PASS=jkm3q2wux56RHYuTuKN11Too6+4pIpGQj3Q/fkNbahM
URL=https://RestfulSms.com/api/UltraFastSend/direct URL=https://RestfulSms.com/api/UltraFastSend/direct
SMS_API_KEY= SMS_API_KEY=
SMS_SECRET= SMS_SECRET=
SMS_PATTERN_OTP= SMS_PATTERN_OTP=
PGADMIN_EMAIL=root@abrclick.ir
PGADMIN_PASSWORD=password
SMTP_HOST=
SMTP_PORT=
SMTP_USER=
SMTP_PASS=
MAIL_FROM=
REDIS_URI=redis://:@localhost:6379/0
CACHE_TTL=120000
+10 -1
View File
@@ -17,11 +17,13 @@
"test:watch": "jest --watch", "test:watch": "jest --watch",
"test:cov": "jest --coverage", "test:cov": "jest --coverage",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"test:e2e": "jest --config ./test/jest-e2e.json" "test:e2e": "jest --config ./test/jest-e2e.json",
"prepare": "husky || true"
}, },
"dependencies": { "dependencies": {
"@fastify/static": "^7.0.4", "@fastify/static": "^7.0.4",
"@keyv/redis": "^4.2.0", "@keyv/redis": "^4.2.0",
"@nestjs-modules/mailer": "^2.0.2",
"@nestjs/cache-manager": "^3.0.0", "@nestjs/cache-manager": "^3.0.0",
"@nestjs/common": "^10.4.15", "@nestjs/common": "^10.4.15",
"@nestjs/config": "^4.0.0", "@nestjs/config": "^4.0.0",
@@ -29,8 +31,15 @@
"@nestjs/platform-fastify": "^10.4.15", "@nestjs/platform-fastify": "^10.4.15",
"@nestjs/swagger": "^8.1.1", "@nestjs/swagger": "^8.1.1",
"@nestjs/typeorm": "^10.0.2", "@nestjs/typeorm": "^10.0.2",
"@types/bcrypt": "^5.0.2",
"axios": "^1.7.9", "axios": "^1.7.9",
"bcrypt": "^5.1.1",
"cache-manager": "^6.3.2", "cache-manager": "^6.3.2",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.1",
"fastify": "^5.2.1",
"handlebars": "^4.7.8",
"nodemailer": "^6.9.16",
"pg": "^8.13.1", "pg": "^8.13.1",
"reflect-metadata": "^0.2.2", "reflect-metadata": "^0.2.2",
"rxjs": "^7.8.1", "rxjs": "^7.8.1",
+10323
View File
File diff suppressed because it is too large Load Diff
+2 -5
View File
@@ -10,10 +10,7 @@ import { UsersModule } from "./modules/users/users.module";
@Module({ @Module({
imports: [ imports: [
ConfigModule.forRoot({ ConfigModule.forRoot({ cache: true, isGlobal: true }),
cache: true,
isGlobal: true,
}),
CacheModule.registerAsync(cacheConfig()), CacheModule.registerAsync(cacheConfig()),
TypeOrmModule.forRootAsync(DatabaseConfigs()), TypeOrmModule.forRootAsync(DatabaseConfigs()),
UsersModule, UsersModule,
@@ -22,4 +19,4 @@ import { UsersModule } from "./modules/users/users.module";
controllers: [], controllers: [],
providers: [], providers: [],
}) })
export class AppModule { } export class AppModule {}
+8
View File
@@ -0,0 +1,8 @@
export interface IPageFormat {
page: number;
limit: number;
totalItems: number;
totalPages: number;
prevPage: string | boolean;
nextPage: string | boolean;
}
+31
View File
@@ -0,0 +1,31 @@
import { ConfigService } from "@nestjs/config";
import { HandlebarsAdapter } from "@nestjs-modules/mailer/dist/adapters/handlebars.adapter";
import { MailerAsyncOptions } from "@nestjs-modules/mailer/dist/interfaces/mailer-async-options.interface";
export function mailerConfig(): MailerAsyncOptions {
return {
inject: [ConfigService],
useFactory: (configService: ConfigService) => ({
transport: {
host: configService.get("SMTP_HOST"),
port: configService.get("SMTP_PORT"),
secure: false,
auth: {
user: configService.get("SMTP_USER"),
pass: configService.get("SMTP_PASS"),
},
},
defaults: {
from: configService.get("MAIL_FROM"),
},
template: {
dir: __dirname + "/templates",
adapter: new HandlebarsAdapter(),
options: {
strict: true,
},
},
}),
};
}
@@ -0,0 +1,61 @@
import { CallHandler, ExecutionContext, Injectable, Logger, NestInterceptor } from "@nestjs/common";
import { FastifyRequest } from "fastify";
import { Observable, map } from "rxjs";
import { IPageFormat } from "../../common/interfaces/IPagination";
@Injectable()
export class PaginationInterceptor implements NestInterceptor {
private readonly logger = new Logger(PaginationInterceptor.name);
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const request = context.switchToHttp().getRequest<FastifyRequest>();
const query = request.query as any;
const page = parseInt(query.page as string, 10) || 1;
const limit = parseInt(query.limit as string, 10) || 10;
const className = context.getClass().name;
const handlerName = context.getHandler().name;
return next.handle().pipe(
map((data) => {
if (data.paginate || data.count) {
const { count, ...response } = data;
this.logger.log(`paginate response from ${className}.${handlerName}`);
const pager = this.formatPage(page, limit, count, request);
return {
pager,
...response,
};
}
return data;
}),
);
}
private formatPage(page: number, limit: number, totalItems: number, request: FastifyRequest): IPageFormat {
const totalPages = Math.ceil(totalItems / limit);
const prevPage = page === 1 ? false : page - 1;
const nextPage = page >= totalPages ? false : page + 1;
const formatLink = (pageNum: number | boolean): string | boolean => {
if (!pageNum) return false;
const protocol = request.protocol;
const hostname = request.hostname;
const originalUrl = request.url;
return `${protocol}://${hostname}${originalUrl.split("?")[0]}?page=${pageNum}&limit=${limit}`;
};
return {
page,
limit,
totalItems,
totalPages,
prevPage: formatLink(prevPage),
nextPage: formatLink(nextPage),
};
}
}
@@ -0,0 +1,30 @@
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from "@nestjs/common";
import { Response } from "express";
import { Observable } from "rxjs";
import { map } from "rxjs/operators";
@Injectable()
export class ResponseInterceptor implements NestInterceptor {
//
intercept(context: ExecutionContext, next: CallHandler<Record<string, unknown>>): Observable<unknown> {
const ctx = context.switchToHttp().getResponse<Response>();
const statusCode = ctx.statusCode;
return next.handle().pipe(
map((data) => {
if (data && data.data !== undefined) {
return {
statusCode,
success: true,
data: data.data,
};
}
return {
statusCode,
success: true,
data,
};
}),
);
}
}
+24
View File
@@ -0,0 +1,24 @@
// import { Injectable, Logger, NestMiddleware } from "@nestjs/common";
// import { FastifyReply, FastifyRequest } from "fastify";
// @Injectable()
// export class HTTPLoggerMiddleware implements NestMiddleware {
// private readonly logger = new Logger(HTTPLoggerMiddleware.name);
// // use(req: any, res: any, next: (error?: Error | any) => void) {}
// use(req: FastifyRequest, res: FastifyReply, next: NextFunction) {
// const { ip, method, baseUrl } = req;
// const userAgent = req.get("user-agent") || "";
// const startAt = process.hrtime();
// res.on("finish", () => {
// const { statusCode } = res;
// const contentLength = res.get("content-length") || 0;
// const dif = process.hrtime(startAt);
// const responseTime = dif[0] * 1e3 + dif[1] * 1e-6;
// this.logger.log(`${method} - ${baseUrl} - ${statusCode} - ${contentLength} - ${userAgent} - ${ip} - ${responseTime.toFixed(2)}ms`);
// });
// next();
// }
// }
+10 -1
View File
@@ -1,4 +1,4 @@
import { Logger } from "@nestjs/common"; import { Logger, ValidationPipe } from "@nestjs/common";
import { ConfigService } from "@nestjs/config"; import { ConfigService } from "@nestjs/config";
import { NestFactory } from "@nestjs/core"; import { NestFactory } from "@nestjs/core";
import { FastifyAdapter, NestFastifyApplication } from "@nestjs/platform-fastify"; import { FastifyAdapter, NestFastifyApplication } from "@nestjs/platform-fastify";
@@ -9,6 +9,15 @@ import { getSwaggerDocument } from "./configs/swagger.config";
async function bootstrap() { async function bootstrap() {
const logger = new Logger("APP"); const logger = new Logger("APP");
const app = await NestFactory.create<NestFastifyApplication>(AppModule, new FastifyAdapter()); const app = await NestFactory.create<NestFastifyApplication>(AppModule, new FastifyAdapter());
app.useGlobalPipes(new ValidationPipe({ transform: true, whitelist: true }));
app.enableCors({
origin: true,
credentials: true,
optionsSuccessStatus: 204,
});
const configService = app.get<ConfigService>(ConfigService); const configService = app.get<ConfigService>(ConfigService);
getSwaggerDocument(app); getSwaggerDocument(app);
@@ -0,0 +1 @@
export class UserRegisterDto {}
+5 -1
View File
@@ -1,4 +1,8 @@
import { Injectable } from "@nestjs/common"; import { Injectable } from "@nestjs/common";
// import { UserRepository } from "./users.repository";
@Injectable() @Injectable()
export class UsersService {} export class UsersService {
// constructor(private userRepository: UserRepository) {}
}
+1 -1
View File
@@ -1 +1 @@
export const SMS_CONFIG = "SMS_CONFIG"; export const SMS_CONFIG = "SMS_CONFIG";
+11 -11
View File
@@ -1,18 +1,18 @@
export interface ISmsBody { export interface ISmsBody {
ParameterArray: IParameterArray[]; ParameterArray: IParameterArray[];
Mobile: string; Mobile: string;
TemplateId: string; TemplateId: string;
UserApiKey: string; UserApiKey: string;
SecretKey: string; SecretKey: string;
} }
export interface IParameterArray { export interface IParameterArray {
Parameter: string; Parameter: string;
ParameterValue: string; ParameterValue: string;
} }
export interface ISmsResponse { export interface ISmsResponse {
VerificationCodeId: number; VerificationCodeId: number;
isSuccessful: boolean; isSuccessful: boolean;
Message: string; Message: string;
} }
@@ -0,0 +1,25 @@
import { Injectable, Logger } from "@nestjs/common";
import { MailerService } from "@nestjs-modules/mailer";
@Injectable()
export class EmailService {
private readonly logger = new Logger(EmailService.name);
constructor(private readonly mailerService: MailerService) {}
async sendEmail(to: string, subject: string) {
try {
await this.mailerService.sendMail({
to: to,
from: "noreply@nestjs.com",
subject: subject,
template: "otp",
context: {
//Data to be sent to template
},
});
} catch (error) {
this.logger.error("Email sending failed:", error);
return false;
}
}
}
@@ -1,6 +1,14 @@
import { Injectable } from "@nestjs/common"; import { Injectable } from "@nestjs/common";
import { compare, genSalt, hash } from "bcrypt";
@Injectable() @Injectable()
export class PasswordService { export class PasswordService {
async hashPassword(rawPassword: string) {} //** */
async hashPassword(rawPassword: string) {
const salt = await genSalt(10);
return hash(rawPassword, salt);
}
//** */
async comparePassword(rawPassword: string, hashedPassword: string) {
return compare(rawPassword, hashedPassword);
}
} }
@@ -1,5 +1,6 @@
import { Inject, Injectable, Logger } from "@nestjs/common"; import { Inject, Injectable, Logger } from "@nestjs/common";
import axios from "axios"; import axios from "axios";
import { ISmsConfigs } from "../../../configs/sms.config"; import { ISmsConfigs } from "../../../configs/sms.config";
import { SMS_CONFIG } from "../constants"; import { SMS_CONFIG } from "../constants";
import { ISmsBody } from "../interfaces/ISms"; import { ISmsBody } from "../interfaces/ISms";
+5 -1
View File
@@ -3,18 +3,22 @@ import { Module } from "@nestjs/common";
import { SMS_CONFIG } from "./constants"; import { SMS_CONFIG } from "./constants";
import { CacheService } from "./providers/cache.service"; import { CacheService } from "./providers/cache.service";
import { OTPService } from "./providers/otp.service"; import { OTPService } from "./providers/otp.service";
import { PasswordService } from "./providers/password.service";
import { SmsService } from "./providers/sms.service";
import { SmsConfigs } from "../../configs/sms.config"; import { SmsConfigs } from "../../configs/sms.config";
@Module({ @Module({
providers: [ providers: [
OTPService, OTPService,
PasswordService,
CacheService, CacheService,
SmsService,
{ {
provide: SMS_CONFIG, provide: SMS_CONFIG,
useFactory: SmsConfigs().useFactory, useFactory: SmsConfigs().useFactory,
inject: SmsConfigs().inject, inject: SmsConfigs().inject,
}, },
], ],
exports: [SMS_CONFIG], exports: [SMS_CONFIG, OTPService, PasswordService, CacheService, SmsService],
}) })
export class UtilsModule {} export class UtilsModule {}