chore: add email service and password service
This commit is contained in:
+17
-1
@@ -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
@@ -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",
|
||||||
|
|||||||
Generated
+10323
File diff suppressed because it is too large
Load Diff
+2
-5
@@ -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 {}
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
export interface IPageFormat {
|
||||||
|
page: number;
|
||||||
|
limit: number;
|
||||||
|
totalItems: number;
|
||||||
|
totalPages: number;
|
||||||
|
prevPage: string | boolean;
|
||||||
|
nextPage: string | boolean;
|
||||||
|
}
|
||||||
@@ -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,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
@@ -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 {}
|
||||||
@@ -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 @@
|
|||||||
export const SMS_CONFIG = "SMS_CONFIG";
|
export const SMS_CONFIG = "SMS_CONFIG";
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|||||||
@@ -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 {}
|
||||||
|
|||||||
Reference in New Issue
Block a user