chore: add new service to add subs to danak services in array
This commit is contained in:
+3
-4
@@ -13,10 +13,9 @@ PGADMIN_EMAIL=root@test.ir
|
|||||||
PGADMIN_PASSWORD=password
|
PGADMIN_PASSWORD=password
|
||||||
|
|
||||||
|
|
||||||
SMS_API_URL=https://RestfulSms.com/api/UltraFastSend/direct
|
SMS_API_URL= https://api.sms.ir/v1
|
||||||
SMS_API_KEY=
|
SMS_API_KEY="GoqtKIterHPHTRlmBtv3JTVU06a4a6YvcyCWcv0ISojcpmNW"
|
||||||
SMS_SECRET=
|
SMS_PATTERN_OTP=123456
|
||||||
SMS_PATTERN_OTP=
|
|
||||||
|
|
||||||
SMTP_HOST=
|
SMTP_HOST=
|
||||||
SMTP_PORT=
|
SMTP_PORT=
|
||||||
|
|||||||
+4
-1
@@ -6,9 +6,11 @@ import { MiddlewareConsumer, Module, NestModule } from "@nestjs/common";
|
|||||||
import { ConfigModule } from "@nestjs/config";
|
import { ConfigModule } from "@nestjs/config";
|
||||||
import { ThrottlerModule } from "@nestjs/throttler";
|
import { ThrottlerModule } from "@nestjs/throttler";
|
||||||
import { TypeOrmModule } from "@nestjs/typeorm";
|
import { TypeOrmModule } from "@nestjs/typeorm";
|
||||||
|
import { MailerModule } from "@nestjs-modules/mailer";
|
||||||
|
|
||||||
import { bullMqConfig } from "./configs/bullmq.config";
|
import { bullMqConfig } from "./configs/bullmq.config";
|
||||||
import { cacheConfig } from "./configs/cache.config";
|
import { cacheConfig } from "./configs/cache.config";
|
||||||
|
import { mailerConfig } from "./configs/mailer.config";
|
||||||
import { rateLimitConfig } from "./configs/rateLimit.config";
|
import { rateLimitConfig } from "./configs/rateLimit.config";
|
||||||
import { databaseConfigs } from "./configs/typeorm.config";
|
import { databaseConfigs } from "./configs/typeorm.config";
|
||||||
import { HTTPLogger } from "./core/middlewares/logger.middleware";
|
import { HTTPLogger } from "./core/middlewares/logger.middleware";
|
||||||
@@ -31,12 +33,13 @@ import { WalletsModule } from "./modules/wallets/wallets.module";
|
|||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
|
MailerModule.forRootAsync(mailerConfig()),
|
||||||
BullModule.forRootAsync(bullMqConfig()),
|
BullModule.forRootAsync(bullMqConfig()),
|
||||||
ThrottlerModule.forRootAsync(rateLimitConfig()),
|
ThrottlerModule.forRootAsync(rateLimitConfig()),
|
||||||
ConfigModule.forRoot({ cache: true, isGlobal: true }),
|
ConfigModule.forRoot({ cache: true, isGlobal: true }),
|
||||||
CacheModule.registerAsync(cacheConfig()),
|
CacheModule.registerAsync(cacheConfig()),
|
||||||
TypeOrmModule.forRootAsync(databaseConfigs()),
|
TypeOrmModule.forRootAsync(databaseConfigs()),
|
||||||
HttpModule.register({ global: true }),
|
HttpModule.register({ global: true, timeout: 5000, headers: { "Content-Type": "application/json" } }),
|
||||||
FastifyMulterModule,
|
FastifyMulterModule,
|
||||||
UsersModule,
|
UsersModule,
|
||||||
TicketsModule,
|
TicketsModule,
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ export const enum ServiceMessage {
|
|||||||
AUTHOR_LENGTH = "نویسنده سرویس باید بین 3 تا 100 کاراکتر باشد",
|
AUTHOR_LENGTH = "نویسنده سرویس باید بین 3 تا 100 کاراکتر باشد",
|
||||||
SERVICE_NOT_EXIST = "سرویس مورد نظر یافت نشد",
|
SERVICE_NOT_EXIST = "سرویس مورد نظر یافت نشد",
|
||||||
NAME_EXIST = "سرویس با این نام قبلا ثبت شده است",
|
NAME_EXIST = "سرویس با این نام قبلا ثبت شده است",
|
||||||
SERVICE_NOT_FOUND_BY_ID = "سرویسی با این شناسه یافت نشد",
|
SERVICE_NOT_FOUND_BY_ID = "سرویسی با این شناسه یافت نشد یا سرویس غیرفعال میباشد",
|
||||||
SERVICE_ID_SHOULD_BE_A_UUID = "شناسه سرویس باید یک UUID باشد",
|
SERVICE_ID_SHOULD_BE_A_UUID = "شناسه سرویس باید یک UUID باشد",
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -297,6 +297,11 @@ export const enum SubscriptionMessage {
|
|||||||
PLAN_ID_REQUIRED = "شناسه اشتراک مورد نیاز است",
|
PLAN_ID_REQUIRED = "شناسه اشتراک مورد نیاز است",
|
||||||
PLAN_ID_SHOULD_BE_UUID = "شناسه اشتراک باید یک UUID معتبر باشد",
|
PLAN_ID_SHOULD_BE_UUID = "شناسه اشتراک باید یک UUID معتبر باشد",
|
||||||
SUBSCRIBED = "اشتراک شما با موفقیت ثبت شد",
|
SUBSCRIBED = "اشتراک شما با موفقیت ثبت شد",
|
||||||
|
SEARCH_QUERY_STRING = "رشته جستجو باید یک رشته باشد",
|
||||||
|
STATUS_UPDATED = "وضعیت اشتراک با موفقیت به روز شد",
|
||||||
|
SUBS_MIN_SIZE = "حداقل یک اشتراک باید انتخاب شود",
|
||||||
|
SUBS_REQUIRED = "اشتراک مورد نیاز است",
|
||||||
|
SUBS_SHOULD_BE_ARRAY = "اشتراک باید یک آرایه باشد",
|
||||||
}
|
}
|
||||||
|
|
||||||
export const enum InvoiceMessage {
|
export const enum InvoiceMessage {
|
||||||
@@ -352,3 +357,6 @@ export const enum DiscountMessage {
|
|||||||
NOT_FOUND = "کد تخفیف با این مشخصات یافت نشد",
|
NOT_FOUND = "کد تخفیف با این مشخصات یافت نشد",
|
||||||
CODE_EXIST = "کد تخفیف قبلا ایجاد شده است",
|
CODE_EXIST = "کد تخفیف قبلا ایجاد شده است",
|
||||||
}
|
}
|
||||||
|
export const enum EmailMessage {
|
||||||
|
EMAIL_VERIFICATION = "تایید ایمیل",
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import { HttpModuleAsyncOptions } from "@nestjs/axios";
|
||||||
|
import { ConfigService } from "@nestjs/config";
|
||||||
|
|
||||||
|
export function httpConfig(): HttpModuleAsyncOptions {
|
||||||
|
return {
|
||||||
|
inject: [ConfigService],
|
||||||
|
useFactory: async (configService: ConfigService) => {
|
||||||
|
return {
|
||||||
|
timeout: configService.getOrThrow<number>("AXIOS_TIMEOUT"),
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
global: true,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -7,20 +7,20 @@ export function mailerConfig(): MailerAsyncOptions {
|
|||||||
inject: [ConfigService],
|
inject: [ConfigService],
|
||||||
useFactory: (configService: ConfigService) => ({
|
useFactory: (configService: ConfigService) => ({
|
||||||
transport: {
|
transport: {
|
||||||
host: configService.get("SMTP_HOST"),
|
host: configService.getOrThrow<string>("SMTP_HOST"),
|
||||||
port: configService.get("SMTP_PORT"),
|
port: configService.getOrThrow<number>("SMTP_PORT"),
|
||||||
secure: false,
|
secure: false,
|
||||||
auth: {
|
auth: {
|
||||||
user: configService.get("SMTP_USER"),
|
user: configService.getOrThrow<string>("SMTP_USER"),
|
||||||
pass: configService.get("SMTP_PASS"),
|
pass: configService.getOrThrow<string>("SMTP_PASS"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
defaults: {
|
defaults: {
|
||||||
from: configService.get("MAIL_FROM"),
|
from: configService.getOrThrow<string>("MAIL_FROM"),
|
||||||
},
|
},
|
||||||
|
|
||||||
template: {
|
template: {
|
||||||
dir: __dirname + "/templates",
|
dir: process.cwd() + "/templates",
|
||||||
adapter: new HandlebarsAdapter(),
|
adapter: new HandlebarsAdapter(),
|
||||||
options: {
|
options: {
|
||||||
strict: true,
|
strict: true,
|
||||||
|
|||||||
@@ -7,24 +7,16 @@ export function smsConfigs() {
|
|||||||
return {
|
return {
|
||||||
API_URL: configService.getOrThrow<string>("SMS_API_URL"),
|
API_URL: configService.getOrThrow<string>("SMS_API_URL"),
|
||||||
API_KEY: configService.getOrThrow<string>("SMS_API_KEY"),
|
API_KEY: configService.getOrThrow<string>("SMS_API_KEY"),
|
||||||
SECRET_KEY: configService.getOrThrow<string>("SMS_SECRET"),
|
// SECRET_KEY: configService.getOrThrow<string>("SMS_SECRET"),
|
||||||
SMS_PATTERN_OTP: configService.getOrThrow<string>("SMS_PATTERN_OTP"),
|
SMS_PATTERN_OTP: configService.getOrThrow<string>("SMS_PATTERN_OTP"),
|
||||||
// SMS_SECRET_BODY: {
|
|
||||||
// UserApiKey: configService.getOrThrow<string>("SMS_API_KEY"),
|
|
||||||
// SecretKey: configService.getOrThrow<string>("SECRET_KEY"),
|
|
||||||
// },
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ISmsConfigs {
|
export interface ISmsConfigs {
|
||||||
URL: string;
|
API_URL: string;
|
||||||
API_KEY: string;
|
API_KEY: string;
|
||||||
SECRET_KEY: string;
|
// SECRET_KEY: string;
|
||||||
SMS_PATTERN_OTP: string;
|
SMS_PATTERN_OTP: string;
|
||||||
SMS_SECRET_BODY: {
|
|
||||||
UserApiKey: string;
|
|
||||||
SecretKey: string;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ export class HttpExceptionFilter implements ExceptionFilter {
|
|||||||
if (typeof response === "string") {
|
if (typeof response === "string") {
|
||||||
return [response];
|
return [response];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof response === "object" && "message" in response) {
|
if (typeof response === "object" && "message" in response) {
|
||||||
return Array.isArray(response.message) ? response.message : [response.message];
|
return Array.isArray(response.message) ? response.message : [response.message];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { RoleEnum } from "../../users/enums/role.enum";
|
|||||||
import { UsersService } from "../../users/providers/users.service";
|
import { UsersService } from "../../users/providers/users.service";
|
||||||
import { OTPService } from "../../utils/providers/otp.service";
|
import { OTPService } from "../../utils/providers/otp.service";
|
||||||
import { PasswordService } from "../../utils/providers/password.service";
|
import { PasswordService } from "../../utils/providers/password.service";
|
||||||
|
import { SmsService } from "../../utils/providers/sms.service";
|
||||||
import { CompleteRegistrationDto } from "../DTO/complete-register.dto";
|
import { CompleteRegistrationDto } from "../DTO/complete-register.dto";
|
||||||
import { CheckUserExistDto, LoginPasswordDTO } from "../DTO/loginPassword.dto";
|
import { CheckUserExistDto, LoginPasswordDTO } from "../DTO/loginPassword.dto";
|
||||||
import { RequestOtpDto } from "../DTO/request-otp.dto";
|
import { RequestOtpDto } from "../DTO/request-otp.dto";
|
||||||
@@ -24,6 +25,7 @@ export class AuthService {
|
|||||||
private readonly tokensService: TokensService,
|
private readonly tokensService: TokensService,
|
||||||
private readonly dataSource: DataSource,
|
private readonly dataSource: DataSource,
|
||||||
private readonly notificationService: NotificationsService,
|
private readonly notificationService: NotificationsService,
|
||||||
|
private readonly smsService: SmsService,
|
||||||
) {}
|
) {}
|
||||||
//****************** */
|
//****************** */
|
||||||
//****************** */
|
//****************** */
|
||||||
@@ -40,7 +42,8 @@ export class AuthService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
const otpCode = await this.otpService.generateAndSetInCache(phone, "REGISTER");
|
const otpCode = await this.otpService.generateAndSetInCache(phone, "REGISTER");
|
||||||
// await SmsService.sendOtp(phone, otpCode);
|
//
|
||||||
|
await this.smsService.sendSmsVerifyCode(phone, otpCode);
|
||||||
this.logger.debug(`OTP sent to ${phone}: ${otpCode}`);
|
this.logger.debug(`OTP sent to ${phone}: ${otpCode}`);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -142,7 +145,8 @@ export class AuthService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const otpCode = await this.otpService.generateAndSetInCache(phone, "LOGIN");
|
const otpCode = await this.otpService.generateAndSetInCache(phone, "LOGIN");
|
||||||
// await SmsService.sendOtp(phone, otpCode);
|
//
|
||||||
|
await this.smsService.sendSmsVerifyCode(phone, otpCode);
|
||||||
this.logger.debug(`OTP sent to ${phone}: ${otpCode}`);
|
this.logger.debug(`OTP sent to ${phone}: ${otpCode}`);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { DanakServicesService } from "./providers/danak-services.service";
|
|||||||
import { AuthGuards } from "../../common/decorators/auth-guard.decorator";
|
import { AuthGuards } from "../../common/decorators/auth-guard.decorator";
|
||||||
import { Pagination } from "../../common/decorators/pagination.decorator";
|
import { Pagination } from "../../common/decorators/pagination.decorator";
|
||||||
import { Roles } from "../../common/decorators/roles.decorator";
|
import { Roles } from "../../common/decorators/roles.decorator";
|
||||||
|
import { UserDec } from "../../common/decorators/user.decorator";
|
||||||
import { ParamDto } from "../../common/DTO/param.dto";
|
import { ParamDto } from "../../common/DTO/param.dto";
|
||||||
import { RoleEnum } from "../users/enums/role.enum";
|
import { RoleEnum } from "../users/enums/role.enum";
|
||||||
|
|
||||||
@@ -44,7 +45,7 @@ export class DanakServicesController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@AuthGuards()
|
@AuthGuards()
|
||||||
@Roles(RoleEnum.USER)
|
@Roles(RoleEnum.USER, RoleEnum.ADMIN)
|
||||||
@ApiOperation({ summary: "Get all service categories user side" })
|
@ApiOperation({ summary: "Get all service categories user side" })
|
||||||
@Get("categories/public")
|
@Get("categories/public")
|
||||||
getCategoriesUserSide() {
|
getCategoriesUserSide() {
|
||||||
@@ -52,7 +53,7 @@ export class DanakServicesController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@AuthGuards()
|
@AuthGuards()
|
||||||
@Roles(RoleEnum.USER)
|
@Roles(RoleEnum.USER, RoleEnum.ADMIN)
|
||||||
@ApiOperation({ summary: "get category services with category id" })
|
@ApiOperation({ summary: "get category services with category id" })
|
||||||
@Get("categories/:id/services")
|
@Get("categories/:id/services")
|
||||||
getCategoryServices(@Param() paramDto: ParamDto) {
|
getCategoryServices(@Param() paramDto: ParamDto) {
|
||||||
@@ -103,10 +104,10 @@ export class DanakServicesController {
|
|||||||
|
|
||||||
@ApiOperation({ summary: "get danak service by id" })
|
@ApiOperation({ summary: "get danak service by id" })
|
||||||
@AuthGuards()
|
@AuthGuards()
|
||||||
@Roles(RoleEnum.USER)
|
@Roles(RoleEnum.USER, RoleEnum.ADMIN)
|
||||||
@Get(":id")
|
@Get(":id")
|
||||||
getDanakServiceById(@Param() paramDto: ParamDto) {
|
getDanakServiceById(@Param() paramDto: ParamDto, @UserDec("role") userRole: RoleEnum) {
|
||||||
return this.danakServicesService.getDanakServiceByID(paramDto.id);
|
return this.danakServicesService.getDanakServiceByIdWithSubs(paramDto.id, userRole);
|
||||||
}
|
}
|
||||||
|
|
||||||
// @AuthGuards()
|
// @AuthGuards()
|
||||||
|
|||||||
@@ -25,5 +25,5 @@ export class DanakServiceCategory extends BaseEntity {
|
|||||||
parentId: string | null;
|
parentId: string | null;
|
||||||
|
|
||||||
@OneToMany(() => DanakService, (danakService) => danakService.category)
|
@OneToMany(() => DanakService, (danakService) => danakService.category)
|
||||||
danakService: DanakService[];
|
danakServices: DanakService[];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,9 @@ export class DanakService extends BaseEntity {
|
|||||||
@Column({ type: "varchar", length: 100, nullable: false, unique: true })
|
@Column({ type: "varchar", length: 100, nullable: false, unique: true })
|
||||||
name: string;
|
name: string;
|
||||||
|
|
||||||
|
@Column({ type: "varchar", length: 150, nullable: true })
|
||||||
|
title: string;
|
||||||
|
|
||||||
@Column({ type: "boolean", nullable: false, default: false })
|
@Column({ type: "boolean", nullable: false, default: false })
|
||||||
isDanakSuggest: boolean;
|
isDanakSuggest: boolean;
|
||||||
|
|
||||||
@@ -47,7 +50,7 @@ export class DanakService extends BaseEntity {
|
|||||||
@Column({ type: "varchar", length: 255, nullable: false })
|
@Column({ type: "varchar", length: 255, nullable: false })
|
||||||
icon: string;
|
icon: string;
|
||||||
|
|
||||||
@ManyToOne(() => DanakServiceCategory, (danakServiceCategory) => danakServiceCategory.danakService, {
|
@ManyToOne(() => DanakServiceCategory, (danakServiceCategory) => danakServiceCategory.danakServices, {
|
||||||
nullable: false,
|
nullable: false,
|
||||||
onDelete: "RESTRICT",
|
onDelete: "RESTRICT",
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { FindOptionsWhere, In, IsNull } from "typeorm";
|
|||||||
|
|
||||||
import { ParamDto } from "../../../common/DTO/param.dto";
|
import { ParamDto } from "../../../common/DTO/param.dto";
|
||||||
import { CategoryMessage, CommonMessage, ServiceMessage } from "../../../common/enums/message.enum";
|
import { CategoryMessage, CommonMessage, ServiceMessage } from "../../../common/enums/message.enum";
|
||||||
|
import { RoleEnum } from "../../users/enums/role.enum";
|
||||||
import { PaginationUtils } from "../../utils/providers/pagination.utils";
|
import { PaginationUtils } from "../../utils/providers/pagination.utils";
|
||||||
import { CategoryListSearchQueryDto, CategorySearchQueryDto } from "../DTO/category-search-query.dto";
|
import { CategoryListSearchQueryDto, CategorySearchQueryDto } from "../DTO/category-search-query.dto";
|
||||||
import { CreateCategoryDto } from "../DTO/create-category.dto";
|
import { CreateCategoryDto } from "../DTO/create-category.dto";
|
||||||
@@ -92,9 +93,9 @@ export class DanakServicesService {
|
|||||||
|
|
||||||
async getCategoryServices(categoryId: string) {
|
async getCategoryServices(categoryId: string) {
|
||||||
const category = await this.danakServicesCategoryRepository.findOne({
|
const category = await this.danakServicesCategoryRepository.findOne({
|
||||||
where: { id: categoryId },
|
where: { id: categoryId, isActive: true, danakServices: { isActive: true } },
|
||||||
relations: {
|
relations: {
|
||||||
danakService: true,
|
danakServices: true,
|
||||||
},
|
},
|
||||||
order: { createdAt: "DESC" },
|
order: { createdAt: "DESC" },
|
||||||
});
|
});
|
||||||
@@ -172,9 +173,9 @@ export class DanakServicesService {
|
|||||||
}
|
}
|
||||||
/******************************************** */
|
/******************************************** */
|
||||||
|
|
||||||
async getDanakServiceByID(serviceId: string) {
|
async getDanakServiceByIdWithSubs(serviceId: string, role: RoleEnum) {
|
||||||
const danakService = await this.danakServicesRepository.findOne({
|
const danakService = await this.danakServicesRepository.findOne({
|
||||||
where: { id: serviceId, isActive: true },
|
where: { id: serviceId, ...(role !== RoleEnum.ADMIN && { isActive: true, subscriptionPlans: { isActive: true } }) },
|
||||||
relations: { images: true, subscriptionPlans: true },
|
relations: { images: true, subscriptionPlans: true },
|
||||||
});
|
});
|
||||||
if (!danakService) throw new BadRequestException(ServiceMessage.SERVICE_NOT_FOUND_BY_ID);
|
if (!danakService) throw new BadRequestException(ServiceMessage.SERVICE_NOT_FOUND_BY_ID);
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ export class DanakServicesRepository extends Repository<DanakService> {
|
|||||||
|
|
||||||
const queryBuilder = this.createQueryBuilder("service")
|
const queryBuilder = this.createQueryBuilder("service")
|
||||||
.leftJoinAndSelect("service.category", "category")
|
.leftJoinAndSelect("service.category", "category")
|
||||||
|
.loadRelationCountAndMap("service.subscriptionCount", "service.subscriptionPlans")
|
||||||
.orderBy("service.createdAt", "DESC")
|
.orderBy("service.createdAt", "DESC")
|
||||||
.skip(skip)
|
.skip(skip)
|
||||||
.take(limit);
|
.take(limit);
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { ApiProperty } from "@nestjs/swagger";
|
import { ApiProperty } from "@nestjs/swagger";
|
||||||
import { IsBoolean, IsInt, IsNotEmpty, IsString, IsUUID, Length, Min } from "class-validator";
|
import { Type } from "class-transformer";
|
||||||
|
import { ArrayMinSize, IsArray, IsBoolean, IsInt, IsNotEmpty, IsString, IsUUID, Length, Min, ValidateNested } from "class-validator";
|
||||||
|
|
||||||
import { SubscriptionMessage } from "../../../common/enums/message.enum";
|
import { SubscriptionMessage } from "../../../common/enums/message.enum";
|
||||||
|
|
||||||
export class CreateSubscriptionPlanDto {
|
export class SubscriptionPlanDto {
|
||||||
@IsNotEmpty({ message: SubscriptionMessage.NAME_REQUIRED })
|
@IsNotEmpty({ message: SubscriptionMessage.NAME_REQUIRED })
|
||||||
@IsString({ message: SubscriptionMessage.NAME_SHOULD_BE_STRING })
|
@IsString({ message: SubscriptionMessage.NAME_SHOULD_BE_STRING })
|
||||||
@Length(3, 100, { message: SubscriptionMessage.NAME_LENGTH })
|
@Length(3, 100, { message: SubscriptionMessage.NAME_LENGTH })
|
||||||
@@ -25,6 +26,18 @@ export class CreateSubscriptionPlanDto {
|
|||||||
@IsBoolean({ message: SubscriptionMessage.IS_ACTIVE_SHOULD_BE_BOOLEAN })
|
@IsBoolean({ message: SubscriptionMessage.IS_ACTIVE_SHOULD_BE_BOOLEAN })
|
||||||
@ApiProperty({ description: "the status of the subscription plan", example: true })
|
@ApiProperty({ description: "the status of the subscription plan", example: true })
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AddSubscriptionsToServiceDto {
|
||||||
|
@IsNotEmpty({ message: SubscriptionMessage.SUBS_REQUIRED })
|
||||||
|
@IsArray({ message: SubscriptionMessage.SUBS_SHOULD_BE_ARRAY })
|
||||||
|
@ArrayMinSize(1, { message: SubscriptionMessage.SUBS_MIN_SIZE })
|
||||||
|
@ValidateNested({ each: true })
|
||||||
|
@Type(() => SubscriptionPlanDto)
|
||||||
|
@ApiProperty({
|
||||||
|
type: [SubscriptionPlanDto],
|
||||||
|
})
|
||||||
|
subs: SubscriptionPlanDto[];
|
||||||
|
|
||||||
@IsNotEmpty({ message: SubscriptionMessage.SERVICE_REQUIRED })
|
@IsNotEmpty({ message: SubscriptionMessage.SERVICE_REQUIRED })
|
||||||
@IsUUID("4", { message: SubscriptionMessage.SERVICE_MUST_BE_UUID })
|
@IsUUID("4", { message: SubscriptionMessage.SERVICE_MUST_BE_UUID })
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import { ApiPropertyOptional } from "@nestjs/swagger";
|
||||||
|
import { Type } from "class-transformer";
|
||||||
|
import { IsOptional, IsString } from "class-validator";
|
||||||
|
|
||||||
|
import { PaginationDto } from "../../../common/DTO/pagination.dto";
|
||||||
|
import { SubscriptionMessage } from "../../../common/enums/message.enum";
|
||||||
|
|
||||||
|
export class ServiceSubsQueryDto extends PaginationDto {
|
||||||
|
@IsOptional()
|
||||||
|
@Type(() => Number)
|
||||||
|
// @IsBoolean({ message: CategoryMessage.IS_ACTIVE_SHOULD_BE_BOOLEAN })
|
||||||
|
@ApiPropertyOptional({ description: "Category status", example: 1 })
|
||||||
|
isActive?: number;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString({ message: SubscriptionMessage.SEARCH_QUERY_STRING })
|
||||||
|
@ApiPropertyOptional({ description: "Search query", example: "search query" })
|
||||||
|
q?: string;
|
||||||
|
}
|
||||||
@@ -1,5 +1,12 @@
|
|||||||
import { PartialType } from "@nestjs/swagger";
|
import { ApiProperty, PartialType } from "@nestjs/swagger";
|
||||||
|
import { IsNotEmpty, IsUUID } from "class-validator";
|
||||||
|
|
||||||
import { CreateSubscriptionPlanDto } from "./create-subscription.dto";
|
import { SubscriptionPlanDto } from "./create-subscription.dto";
|
||||||
|
import { SubscriptionMessage } from "../../../common/enums/message.enum";
|
||||||
|
|
||||||
export class UpdateSubscriptionPlanDto extends PartialType(CreateSubscriptionPlanDto) {}
|
export class UpdateSubscriptionPlanDto extends PartialType(SubscriptionPlanDto) {
|
||||||
|
@IsNotEmpty({ message: SubscriptionMessage.SERVICE_REQUIRED })
|
||||||
|
@IsUUID("4", { message: SubscriptionMessage.SERVICE_MUST_BE_UUID })
|
||||||
|
@ApiProperty({ description: "Service ID", example: "d290f1ee-6c54-4b01-90e6-d701748f0851" })
|
||||||
|
serviceId: string;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
// eslint-disable-next-line import/no-named-as-default
|
// eslint-disable-next-line import/no-named-as-default
|
||||||
import Decimal from "decimal.js";
|
import Decimal from "decimal.js";
|
||||||
import { Column, Entity, ManyToOne } from "typeorm";
|
import { Column, Entity, Index, ManyToOne } from "typeorm";
|
||||||
|
|
||||||
import { BaseEntity } from "../../../common/entities/base.entity";
|
import { BaseEntity } from "../../../common/entities/base.entity";
|
||||||
import { DecimalTransformer } from "../../../common/transformers/decimal.transformer";
|
import { DecimalTransformer } from "../../../common/transformers/decimal.transformer";
|
||||||
import { DanakService } from "../../danak-services/entities/danak-service.entity";
|
import { DanakService } from "../../danak-services/entities/danak-service.entity";
|
||||||
|
|
||||||
@Entity()
|
@Entity()
|
||||||
|
@Index(["service", "name"], { unique: true })
|
||||||
export class SubscriptionPlan extends BaseEntity {
|
export class SubscriptionPlan extends BaseEntity {
|
||||||
@Column({ type: "varchar", length: 150, nullable: false, unique: true })
|
@Column({ type: "varchar", length: 150, nullable: false })
|
||||||
name: string;
|
name: string;
|
||||||
|
|
||||||
@Column({ type: "int", nullable: false })
|
@Column({ type: "int", nullable: false })
|
||||||
|
|||||||
@@ -1,15 +1,17 @@
|
|||||||
import { BadRequestException, Injectable } from "@nestjs/common";
|
import { BadRequestException, Injectable } from "@nestjs/common";
|
||||||
// eslint-disable-next-line import/no-named-as-default
|
// eslint-disable-next-line import/no-named-as-default
|
||||||
import Decimal from "decimal.js";
|
import Decimal from "decimal.js";
|
||||||
import { DataSource } from "typeorm";
|
import { DataSource, In } from "typeorm";
|
||||||
|
|
||||||
import { ServiceMessage, SubscriptionMessage, WalletMessage } from "../../../common/enums/message.enum";
|
import { ServiceMessage, SubscriptionMessage, WalletMessage } from "../../../common/enums/message.enum";
|
||||||
import { DanakServicesService } from "../../danak-services/providers/danak-services.service";
|
import { DanakServicesService } from "../../danak-services/providers/danak-services.service";
|
||||||
import { InvoicesService } from "../../invoices/providers/invoices.service";
|
import { InvoicesService } from "../../invoices/providers/invoices.service";
|
||||||
import { UsersService } from "../../users/providers/users.service";
|
import { UsersService } from "../../users/providers/users.service";
|
||||||
|
import { PaginationUtils } from "../../utils/providers/pagination.utils";
|
||||||
import { Wallet } from "../../wallets/entities/wallet.entity";
|
import { Wallet } from "../../wallets/entities/wallet.entity";
|
||||||
import { WalletsService } from "../../wallets/providers/wallets.service";
|
import { WalletsService } from "../../wallets/providers/wallets.service";
|
||||||
import { CreateSubscriptionPlanDto } from "../DTO/create-subscription.dto";
|
import { AddSubscriptionsToServiceDto } from "../DTO/create-subscription.dto";
|
||||||
|
import { ServiceSubsQueryDto } from "../DTO/service-subs-query.dto";
|
||||||
import { SubscribeServiceDto } from "../DTO/subscribe-service.dto";
|
import { SubscribeServiceDto } from "../DTO/subscribe-service.dto";
|
||||||
import { UpdateSubscriptionPlanDto } from "../DTO/update-subscription.dto";
|
import { UpdateSubscriptionPlanDto } from "../DTO/update-subscription.dto";
|
||||||
import { SubscriptionPlan } from "../entities/subscription.entity";
|
import { SubscriptionPlan } from "../entities/subscription.entity";
|
||||||
@@ -31,18 +33,29 @@ export class SubscriptionsService {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
//************************************ */
|
//************************************ */
|
||||||
async createSubscriptionsPlan(createDto: CreateSubscriptionPlanDto) {
|
async createSubscriptionsPlan(createDto: AddSubscriptionsToServiceDto) {
|
||||||
const danakService = await this.danakServices.findServiceById(createDto.serviceId);
|
const danakService = await this.danakServices.findServiceById(createDto.serviceId);
|
||||||
if (!danakService) throw new BadRequestException(ServiceMessage.SERVICE_NOT_FOUND_BY_ID);
|
if (!danakService) throw new BadRequestException(ServiceMessage.SERVICE_NOT_FOUND_BY_ID);
|
||||||
|
|
||||||
const existSubscription = await this.subscriptionsPlanRepository.findOneByName(createDto.name);
|
const subscriptions = createDto.subs.map((sub) => ({ ...sub, service: { id: createDto.serviceId } }));
|
||||||
if (existSubscription) throw new BadRequestException(SubscriptionMessage.NAME_EXIST);
|
|
||||||
|
const subscriptionNames = createDto.subs.map((sub) => sub.name);
|
||||||
|
const existingSubscriptions = await this.subscriptionsPlanRepository.find({
|
||||||
|
where: {
|
||||||
|
name: In(subscriptionNames),
|
||||||
|
service: { id: createDto.serviceId },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (existingSubscriptions.length > 0)
|
||||||
|
throw new BadRequestException({ message: [SubscriptionMessage.NAME_EXIST, existingSubscriptions] });
|
||||||
|
|
||||||
|
const createdSubscriptions = this.subscriptionsPlanRepository.create(subscriptions);
|
||||||
|
await this.subscriptionsPlanRepository.save(createdSubscriptions);
|
||||||
|
|
||||||
const subscription = this.subscriptionsPlanRepository.create({ ...createDto, service: { id: createDto.serviceId } });
|
|
||||||
await this.subscriptionsPlanRepository.save(subscription);
|
|
||||||
return {
|
return {
|
||||||
message: SubscriptionMessage.CREATED,
|
message: SubscriptionMessage.CREATED,
|
||||||
subscription,
|
subscriptions: createdSubscriptions,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
//************************************ */
|
//************************************ */
|
||||||
@@ -82,15 +95,51 @@ export class SubscriptionsService {
|
|||||||
}
|
}
|
||||||
//************************************ */
|
//************************************ */
|
||||||
|
|
||||||
async getSubscriptionsPlans() {
|
async getServiceSubscriptions(serviceId: string, queryDto: ServiceSubsQueryDto) {
|
||||||
const subscriptions = await this.subscriptionsPlanRepository.find({ relations: ["service"] });
|
const service = await this.danakServices.findServiceById(serviceId);
|
||||||
|
|
||||||
|
const { limit, skip } = PaginationUtils(queryDto);
|
||||||
|
|
||||||
|
const queryBuilder = this.subscriptionsPlanRepository
|
||||||
|
.createQueryBuilder("subscription")
|
||||||
|
.leftJoin("subscription.service", "service")
|
||||||
|
.addSelect(["service.id"])
|
||||||
|
.where("service.id = :serviceId", { serviceId });
|
||||||
|
|
||||||
|
if (queryDto.q) {
|
||||||
|
queryBuilder.andWhere("subscription.name ILIKE :query", { query: `%${queryDto.q}%` });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (queryDto.isActive !== undefined) {
|
||||||
|
queryBuilder.andWhere("subscription.isActive = :isActive", { isActive: queryDto.isActive === 1 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const [subscriptions, count] = await queryBuilder.skip(skip).take(limit).getManyAndCount();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
service,
|
||||||
subscriptions,
|
subscriptions,
|
||||||
|
count,
|
||||||
|
pagination: true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
//************************************ */
|
//************************************ */
|
||||||
|
|
||||||
|
async toggleSubStatus(subId: string) {
|
||||||
|
const subscription = await this.subscriptionsPlanRepository.findOneBy({ id: subId });
|
||||||
|
if (!subscription) throw new BadRequestException(SubscriptionMessage.NOT_FOUND);
|
||||||
|
|
||||||
|
subscription.isActive = !subscription.isActive;
|
||||||
|
|
||||||
|
await this.subscriptionsPlanRepository.save(subscription);
|
||||||
|
return {
|
||||||
|
message: SubscriptionMessage.STATUS_UPDATED,
|
||||||
|
isActive: subscription.isActive,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
//************************************ */
|
||||||
|
|
||||||
async subscribeToPlan(serviceId: string, subscribeDto: SubscribeServiceDto, userId: string) {
|
async subscribeToPlan(serviceId: string, subscribeDto: SubscribeServiceDto, userId: string) {
|
||||||
const queryRunner = this.dataSource.createQueryRunner();
|
const queryRunner = this.dataSource.createQueryRunner();
|
||||||
await queryRunner.connect();
|
await queryRunner.connect();
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { Body, Controller, Get, Param, Patch, Post } from "@nestjs/common";
|
import { Body, Controller, Get, Param, Patch, Post, Query } from "@nestjs/common";
|
||||||
import { ApiOperation } from "@nestjs/swagger";
|
import { ApiOperation } from "@nestjs/swagger";
|
||||||
|
|
||||||
import { CreateSubscriptionPlanDto } from "./DTO/create-subscription.dto";
|
import { AddSubscriptionsToServiceDto } from "./DTO/create-subscription.dto";
|
||||||
import { ServiceIdParamDto } from "./DTO/service-id.param.dto";
|
import { ServiceIdParamDto } from "./DTO/service-id.param.dto";
|
||||||
|
import { ServiceSubsQueryDto } from "./DTO/service-subs-query.dto";
|
||||||
import { SubscribeServiceDto } from "./DTO/subscribe-service.dto";
|
import { SubscribeServiceDto } from "./DTO/subscribe-service.dto";
|
||||||
import { UpdateSubscriptionPlanDto } from "./DTO/update-subscription.dto";
|
import { UpdateSubscriptionPlanDto } from "./DTO/update-subscription.dto";
|
||||||
import { SubscriptionsService } from "./providers/subscriptions.service";
|
import { SubscriptionsService } from "./providers/subscriptions.service";
|
||||||
@@ -20,16 +21,24 @@ export class SubscriptionsController {
|
|||||||
@AuthGuards()
|
@AuthGuards()
|
||||||
@Roles(RoleEnum.ADMIN)
|
@Roles(RoleEnum.ADMIN)
|
||||||
@Post()
|
@Post()
|
||||||
createSubscription(@Body() createDto: CreateSubscriptionPlanDto) {
|
createSubscription(@Body() createDto: AddSubscriptionsToServiceDto) {
|
||||||
return this.subscriptionService.createSubscriptionsPlan(createDto);
|
return this.subscriptionService.createSubscriptionsPlan(createDto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ApiOperation({ summary: "get all subscription plans" })
|
@ApiOperation({ summary: "get all subscription plans" })
|
||||||
@AuthGuards()
|
@AuthGuards()
|
||||||
@Roles(RoleEnum.ADMIN)
|
@Roles(RoleEnum.ADMIN)
|
||||||
@Get()
|
@Get("service/:serviceId")
|
||||||
getAllSubscriptions() {
|
getServiceSubscriptions(@Param() paramDto: ServiceIdParamDto, @Query() queryDto: ServiceSubsQueryDto) {
|
||||||
return this.subscriptionService.getSubscriptionsPlans();
|
return this.subscriptionService.getServiceSubscriptions(paramDto.serviceId, queryDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOperation({ summary: "toggle status of service subs" })
|
||||||
|
@AuthGuards()
|
||||||
|
@Roles(RoleEnum.ADMIN)
|
||||||
|
@Post("toggle-status/:id")
|
||||||
|
toggleStatusOfServiceSub(@Param() paramDto: ParamDto) {
|
||||||
|
return this.subscriptionService.toggleSubStatus(paramDto.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ApiOperation({ summary: "get a subscription plan by id" })
|
@ApiOperation({ summary: "get a subscription plan by id" })
|
||||||
|
|||||||
@@ -1,18 +1,28 @@
|
|||||||
export interface ISmsBody {
|
export interface IParameterArray {
|
||||||
ParameterArray: 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;
|
Mobile: string;
|
||||||
TemplateId: string;
|
TemplateId: string;
|
||||||
UserApiKey: string;
|
|
||||||
SecretKey: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IParameterArray {
|
// export type SmsBodyType = ISmsVerifyBody;
|
||||||
Parameter: string;
|
|
||||||
ParameterValue: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ISmsResponse {
|
export type TemplateParams = "Code";
|
||||||
VerificationCodeId: number;
|
|
||||||
isSuccessful: boolean;
|
|
||||||
Message: string;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,22 +1,27 @@
|
|||||||
import { Injectable, Logger } from "@nestjs/common";
|
import { Injectable, Logger } from "@nestjs/common";
|
||||||
import { MailerService } from "@nestjs-modules/mailer";
|
import { MailerService } from "@nestjs-modules/mailer";
|
||||||
|
|
||||||
|
import { EmailMessage } from "../../../common/enums/message.enum";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class EmailService {
|
export class EmailService {
|
||||||
private readonly logger = new Logger(EmailService.name);
|
private readonly logger = new Logger(EmailService.name);
|
||||||
constructor(private readonly mailerService: MailerService) {}
|
constructor(private readonly mailerService: MailerService) {}
|
||||||
|
|
||||||
async sendEmail(to: string, subject: string) {
|
async sendEmail(to: string, subject: string, link: string, fullName: string) {
|
||||||
try {
|
try {
|
||||||
await this.mailerService.sendMail({
|
const emailInfo = await this.mailerService.sendMail({
|
||||||
to: to,
|
to: to,
|
||||||
from: "noreply@nestjs.com",
|
// from: "noreply@nestjs.com",
|
||||||
subject: subject,
|
subject: subject,
|
||||||
template: "otp",
|
template: "email-verify",
|
||||||
context: {
|
context: {
|
||||||
//Data to be sent to template
|
title: EmailMessage.EMAIL_VERIFICATION,
|
||||||
|
link,
|
||||||
|
fullName,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
return emailInfo;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error("Email sending failed:", error);
|
this.logger.error("Email sending failed:", error);
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -1,36 +1,76 @@
|
|||||||
import { Inject, Injectable, Logger } from "@nestjs/common";
|
import { HttpService } from "@nestjs/axios";
|
||||||
import axios from "axios";
|
import { Inject, Injectable, InternalServerErrorException, Logger } from "@nestjs/common";
|
||||||
|
import { AxiosError } from "axios";
|
||||||
|
import { catchError, firstValueFrom } from "rxjs";
|
||||||
|
|
||||||
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 { ISmsGetLineResponse, ISmsVerifyBody, ISmsVerifyResponse } from "../interfaces/ISms";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class SmsService {
|
export class SmsService {
|
||||||
private logger = new Logger(SmsService.name);
|
private readonly logger = new Logger(SmsService.name);
|
||||||
constructor(@Inject(SMS_CONFIG) private smsConfigs: ISmsConfigs) {}
|
|
||||||
|
constructor(
|
||||||
|
@Inject(SMS_CONFIG) private smsConfigs: ISmsConfigs,
|
||||||
|
private readonly httpService: HttpService,
|
||||||
|
) {}
|
||||||
|
//************************************************* */
|
||||||
|
|
||||||
async sendSmsVerifyCode(mobile: string, otpCode: string) {
|
async sendSmsVerifyCode(mobile: string, otpCode: string) {
|
||||||
const smsData: ISmsBody = {
|
//
|
||||||
ParameterArray: [{ Parameter: "otp", ParameterValue: otpCode }],
|
const smsData: ISmsVerifyBody = {
|
||||||
|
Parameters: [{ name: "Code", value: otpCode }],
|
||||||
Mobile: mobile,
|
Mobile: mobile,
|
||||||
TemplateId: this.smsConfigs.SMS_PATTERN_OTP,
|
TemplateId: this.smsConfigs.SMS_PATTERN_OTP,
|
||||||
...this.smsConfigs.SMS_SECRET_BODY,
|
|
||||||
};
|
};
|
||||||
|
//
|
||||||
return this.sendSms(smsData);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async sendSms(body: ISmsBody) {
|
|
||||||
try {
|
try {
|
||||||
const { data } = await axios.post(this.smsConfigs.URL, body, {
|
const { data } = await firstValueFrom(
|
||||||
headers: {
|
this.httpService
|
||||||
"Content-Type": "application/json",
|
.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;
|
return data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error(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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//************************************************* */
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { Module } from "@nestjs/common";
|
|||||||
|
|
||||||
import { S3_CONFIG, SMS_CONFIG } from "./constants";
|
import { S3_CONFIG, SMS_CONFIG } from "./constants";
|
||||||
import { CacheService } from "./providers/cache.service";
|
import { CacheService } from "./providers/cache.service";
|
||||||
|
import { EmailService } from "./providers/email.service";
|
||||||
import { OTPService } from "./providers/otp.service";
|
import { OTPService } from "./providers/otp.service";
|
||||||
import { PasswordService } from "./providers/password.service";
|
import { PasswordService } from "./providers/password.service";
|
||||||
import { S3Service } from "./providers/s3.service";
|
import { S3Service } from "./providers/s3.service";
|
||||||
@@ -11,6 +12,7 @@ import { smsConfigs } from "../../configs/sms.config";
|
|||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
providers: [
|
providers: [
|
||||||
|
EmailService,
|
||||||
OTPService,
|
OTPService,
|
||||||
PasswordService,
|
PasswordService,
|
||||||
CacheService,
|
CacheService,
|
||||||
@@ -27,6 +29,6 @@ import { smsConfigs } from "../../configs/sms.config";
|
|||||||
inject: S3Configs().inject,
|
inject: S3Configs().inject,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
exports: [SMS_CONFIG, S3_CONFIG, S3Service, OTPService, PasswordService, CacheService, SmsService],
|
exports: [SMS_CONFIG, S3_CONFIG, S3Service, OTPService, PasswordService, CacheService, SmsService, EmailService],
|
||||||
})
|
})
|
||||||
export class UtilsModule {}
|
export class UtilsModule {}
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
<html lang="fa">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<title>{{title}}</title>
|
||||||
|
<style>
|
||||||
|
body { direction: rtl; font-family: Arial, sans-serif; background-color: #121212; color: #ffffff; text-align: right; margin: 0;
|
||||||
|
padding: 0; } .container { width: 100%; max-width: 600px; margin: 40px auto; padding: 20px; background-color: #1e1e1e; border-radius:
|
||||||
|
8px; box-shadow: 0 0 10px rgba(255, 255, 255, 0.1); } .logo { text-align: center; margin-bottom: 20px; } .logo img { max-width: 150px;
|
||||||
|
} .btn { display: block; width: fit-content; padding: 10px 8px; margin: 20px auto; background-color: #373e47; color: white;
|
||||||
|
text-decoration: none; border-radius: 5px; text-align: center; } .btn:hover { background-color: #010d1a; } .footer { margin-top: 20px;
|
||||||
|
font-size: 12px; color: #aaaaaa; text-align: center; } .footer a { color: #ffffff; text-decoration: none; } .footer a:hover {
|
||||||
|
text-decoration: underline; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="logo">
|
||||||
|
<img src="https://danakcorp.com/logo.png" alt="DanakCorp Logo" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p>سلام {{fullName}},</p>
|
||||||
|
|
||||||
|
<p>به <strong>DanakCorp</strong> خوش آمدید! لطفاً برای تأیید ایمیل خود روی دکمه زیر کلیک کنید:</p>
|
||||||
|
|
||||||
|
<a href="{{verificationLink}}" class="btn">تأیید ایمیل</a>
|
||||||
|
|
||||||
|
<p>اگر دکمه بالا کار نمیکند، میتوانید از لینک زیر استفاده کنید:</p>
|
||||||
|
<p style="word-break: break-all"><a href="{{verificationLink}}" style="color: #4da6ff">{{verificationLink}}</a></p>
|
||||||
|
|
||||||
|
<p>اگر شما این درخواست را ارسال نکردهاید، لطفاً این ایمیل را نادیده بگیرید.</p>
|
||||||
|
|
||||||
|
<div class="footer">
|
||||||
|
<p>با تشکر،<br />تیم پشتیبانی <strong>DanakCorp</strong></p>
|
||||||
|
<p><a href="https://danakcorp.com">DanakCorp.com</a></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user