diff --git a/src/common/enums/message.enum.ts b/src/common/enums/message.enum.ts index d36fdbe..64bc9f0 100755 --- a/src/common/enums/message.enum.ts +++ b/src/common/enums/message.enum.ts @@ -348,6 +348,9 @@ export const enum ContactUsMessage { TITLE_IS_REQUIRED = "عنوان مورد نیاز است", TITLE_STRING = "عنوان باید یک رشته باشد", NOT_FOUND = "پیامی با این ایدی یافت نشد", + BUSINESS_NAME_IS_REQUIRED = "نام شرکت مورد نیاز است", + BUSINESS_NAME_STRING = "نام شرکت باید یک رشته باشد", + BUSINESS_NAME_LENGTH = "نام شرکت باید بین ۲ تا ۱۰۰ کاراکتر باشد", } export const enum NotificationMessage { @@ -630,7 +633,7 @@ export const enum BlogMessage { TITLE_STRING = "عنوان باید یک رشته باشد", DESCRIPTION_REQUIRED = "توضیحات الزامی است", DESCRIPTION_STRING = "توضیحات باید یک رشته باشد", - TITLE_MAX_LENGTH = "حداکثر طول عنوان باید ۱۰۰ کاراکتر باشد", + TITLE_MAX_LENGTH = "حداکثر طول عنوان باید ۲۵۵ کاراکتر باشد", DESCRIPTION_MAX_LENGTH = "حداکثر طول توضیحات باید ۲۵۵ کاراکتر باشد", ICON_URL_REQUIRED = "آدرس آیکون الزامی است", ICON_URL_INVALID = "آدرس آیکون نامعتبر است", @@ -643,7 +646,7 @@ export const enum BlogMessage { BLOG_NOT_FOUND = "بلاگ مورد نظر یافت نشد", PREVIEW_CONTENT_REQUIRED = "محتوای پیشنمایش الزامی است", PREVIEW_CONTENT_STRING = "محتوای پیشنمایش باید یک رشته باشد", - PREVIEW_CONTENT_MAX_LENGTH = "حداکثر طول محتوای پیشنمایش باید ۱۵۰ کاراکتر باشد", + PREVIEW_CONTENT_MAX_LENGTH = "حداکثر طول محتوای پیشنمایش باید 600 کاراکتر باشد", CONTENT_REQUIRED = "محتوای بلاگ الزامی است", CONTENT_STRING = "محتوای بلاگ باید یک رشته باشد", CONTENT_MAX_LENGTH = "حداکثر طول محتوای بلاگ باید ۶۰۰ کاراکتر باشد", @@ -674,7 +677,7 @@ export const enum BlogMessage { SEARCH_QUERY_STRING = "رشته جستجو باید یک رشته باشد", IS_PINNED_SHOULD_BE_A_BOOLEAN = "وضعیت سنجاق بودن باید یک بولین باشد", SLUG_REQUIRED = "اسلاگ بلاگ الزامی است", - SLUG_MAX_LENGTH = "حداکثر طول اسلاگ بلاگ باید ۱۵۰ کاراکتر باشد", + SLUG_MAX_LENGTH = "حداکثر طول اسلاگ بلاگ باید ۲۵۵ کاراکتر باشد", } export const enum SliderMessage { diff --git a/src/configs/typeorm.config.ts b/src/configs/typeorm.config.ts index 9391ab2..8d8378f 100755 --- a/src/configs/typeorm.config.ts +++ b/src/configs/typeorm.config.ts @@ -13,7 +13,7 @@ export function databaseConfigs(): TypeOrmModuleAsyncOptions { username: configService.getOrThrow("DB_USER"), password: configService.getOrThrow("DB_PASS"), autoLoadEntities: true, - synchronize: configService.getOrThrow("NODE_ENV") == "production" ? false : false, + synchronize: configService.getOrThrow("NODE_ENV") == "production" ? false : true, logging: configService.getOrThrow("NODE_ENV") == "production" ? false : true, migrationsTableName: "typeorm_migrations", migrationsRun: false, diff --git a/src/modules/auth/providers/auth.service.ts b/src/modules/auth/providers/auth.service.ts index 269f510..f1a679e 100755 --- a/src/modules/auth/providers/auth.service.ts +++ b/src/modules/auth/providers/auth.service.ts @@ -67,12 +67,7 @@ export class AuthService { const hashedPassword = await this.passwordService.hashPassword(completeRegistrationDto.password); const user = await this.usersService.createUser(completeRegistrationDto, hashedPassword, queryRunner); - if (referralCode) { - await this.referralsService.useReferralCode({ - referralCode, - userId: user.id, - }); - } + if (referralCode) await this.referralsService.useReferralCode({ referralCode, userId: user.id }, queryRunner); const tokens = await this.tokensService.generateTokens(user, queryRunner); diff --git a/src/modules/blogs/DTO/create-blog.dto.ts b/src/modules/blogs/DTO/create-blog.dto.ts index 4ac24d1..d725d02 100644 --- a/src/modules/blogs/DTO/create-blog.dto.ts +++ b/src/modules/blogs/DTO/create-blog.dto.ts @@ -6,25 +6,24 @@ import { BlogMessage } from "../../../common/enums/message.enum"; export class CreateBlogDto { @IsNotEmpty({ message: BlogMessage.TITLE_REQUIRED }) @IsString({ message: BlogMessage.TITLE_STRING }) - @MaxLength(150, { message: BlogMessage.TITLE_MAX_LENGTH }) + @MaxLength(250, { message: BlogMessage.TITLE_MAX_LENGTH }) @ApiProperty({ description: "Title of the blog", example: "Blog Title" }) title: string; @IsNotEmpty({ message: BlogMessage.SLUG_REQUIRED }) @IsString({ message: BlogMessage.SLUG_STRING }) - @MaxLength(150, { message: BlogMessage.SLUG_MAX_LENGTH }) + @MaxLength(250, { message: BlogMessage.SLUG_MAX_LENGTH }) @ApiProperty({ description: "Slug of the blog", example: "blog-title" }) slug: string; @IsNotEmpty({ message: BlogMessage.PREVIEW_CONTENT_REQUIRED }) @IsString({ message: BlogMessage.PREVIEW_CONTENT_STRING }) - @MaxLength(150, { message: BlogMessage.PREVIEW_CONTENT_MAX_LENGTH }) + @MaxLength(600, { message: BlogMessage.PREVIEW_CONTENT_MAX_LENGTH }) @ApiProperty({ description: "Preview content of the blog", example: "Preview content of the blog" }) previewContent: string; @IsNotEmpty({ message: BlogMessage.CONTENT_REQUIRED }) @IsString({ message: BlogMessage.CONTENT_STRING }) - @MaxLength(600, { message: BlogMessage.CONTENT_MAX_LENGTH }) @ApiProperty({ description: "Content of the blog", example: "Content of the blog" }) content: string; @@ -36,7 +35,7 @@ export class CreateBlogDto { @IsNotEmpty({ message: BlogMessage.META_TITLE_REQUIRED }) @IsString({ message: BlogMessage.META_TITLE_STRING }) - @MaxLength(60, { message: BlogMessage.META_TITLE_MAX_LENGTH }) + @MaxLength(250, { message: BlogMessage.META_TITLE_MAX_LENGTH }) @ApiProperty({ description: "Meta title of the blog", example: "Meta title of the blog" }) metaTitle: string; diff --git a/src/modules/blogs/entities/blog-category.entity.ts b/src/modules/blogs/entities/blog-category.entity.ts index c4119c2..2b9bc9b 100644 --- a/src/modules/blogs/entities/blog-category.entity.ts +++ b/src/modules/blogs/entities/blog-category.entity.ts @@ -20,6 +20,6 @@ export class BlogCategory extends BaseEntity { @OneToMany(() => Blog, (blog) => blog.category) blogs: Blog[]; - @DeleteDateColumn({ type: "timestamptz" }) + @DeleteDateColumn() deletedAt?: Date; } diff --git a/src/modules/blogs/entities/blog-comment.entity.ts b/src/modules/blogs/entities/blog-comment.entity.ts index 045bea0..633e8df 100644 --- a/src/modules/blogs/entities/blog-comment.entity.ts +++ b/src/modules/blogs/entities/blog-comment.entity.ts @@ -19,6 +19,6 @@ export class BlogComment extends BaseEntity { @ManyToOne(() => User, (user) => user.blogComments) user: User; - @ManyToOne(() => Blog, (blog) => blog.comments) + @ManyToOne(() => Blog, (blog) => blog.comments, { onDelete: "CASCADE" }) blog: Blog; } diff --git a/src/modules/blogs/entities/blog.entity.ts b/src/modules/blogs/entities/blog.entity.ts index 67e52e8..bd7f7c3 100644 --- a/src/modules/blogs/entities/blog.entity.ts +++ b/src/modules/blogs/entities/blog.entity.ts @@ -7,10 +7,10 @@ import { User } from "../../users/entities/user.entity"; @Entity() export class Blog extends BaseEntity { - @Column({ type: "varchar", length: 150, unique: true }) + @Column({ type: "varchar", length: 250, unique: true }) title: string; - @Column({ type: "varchar", length: 150, unique: true }) + @Column({ type: "varchar", length: 250, unique: true }) slug: string; @Column({ type: "text" }) @@ -19,13 +19,13 @@ export class Blog extends BaseEntity { @Column({ type: "text" }) content: string; - @Column({ type: "varchar", length: 150, nullable: true }) + @Column({ type: "varchar", length: 250, nullable: true }) imageUrl: string; - @Column({ type: "varchar", length: 100, nullable: true }) + @Column({ type: "varchar", length: 250, nullable: true }) metaTitle: string; - @Column({ type: "varchar", length: 160, nullable: true }) + @Column({ type: "varchar", length: 250, nullable: true }) metaDescription: string; @Column({ type: "simple-array", nullable: true }) @@ -40,15 +40,15 @@ export class Blog extends BaseEntity { @Column({ type: "boolean", default: false }) isPinned: boolean; - @ManyToOne(() => User, (user) => user.blogs) + @ManyToOne(() => User, (user) => user.blogs, { onDelete: "CASCADE", nullable: false }) author: User; - @ManyToOne(() => BlogCategory, (category) => category.blogs) + @ManyToOne(() => BlogCategory, (category) => category.blogs, { nullable: false }) category: BlogCategory; @OneToMany(() => BlogComment, (comment) => comment.blog) comments: BlogComment[]; - @DeleteDateColumn({ type: "timestamptz" }) + @DeleteDateColumn() deletedAt?: Date; } diff --git a/src/modules/blogs/providers/blogs.service.ts b/src/modules/blogs/providers/blogs.service.ts index a1930b4..b95f88f 100644 --- a/src/modules/blogs/providers/blogs.service.ts +++ b/src/modules/blogs/providers/blogs.service.ts @@ -56,9 +56,13 @@ export class BlogsService { //*********************************** */ async deleteCategory(id: string) { - await this.findCategoryById(id); + const category = await this.blogCategoriesRepository.findOneBy({ id }); + if (!category) throw new BadRequestException(BlogMessage.CATEGORY_NOT_FOUND); - await this.blogCategoriesRepository.softDelete(id); + await this.blogCategoriesRepository.softDelete({ id }); + return { + message: CommonMessage.DELETED, + }; } //*********************************** */ @@ -70,7 +74,10 @@ export class BlogsService { await this.blogCategoriesRepository.save(category); // - return { message: CommonMessage.UPDATE_SUCCESS, category }; + return { + message: CommonMessage.UPDATE_SUCCESS, + category, + }; } //*********************************** */ @@ -146,21 +153,24 @@ export class BlogsService { //*********************************** */ async deleteBlog(id: string) { - await this.findBlogById(id); + const blog = await this.blogsRepository.findOneBy({ id }); + if (!blog) throw new BadRequestException(BlogMessage.BLOG_NOT_FOUND); - await this.blogsRepository.softDelete(id); - return { message: CommonMessage.DELETED }; + await this.blogsRepository.softDelete({ id }); + return { + message: CommonMessage.DELETED, + }; } //*********************************** */ async getBlogById(id: string, isAdmin: boolean = false) { const blog = await this.findBlogById(id); - const comments = await this.blogCommentsRepository.findBlogCommentsByBlogId(id); - if (!isAdmin) { - blog.viewCount += 1; - await this.blogsRepository.save(blog); - } + if (isAdmin) return { blog }; + + blog.viewCount += 1; + await this.blogsRepository.save(blog); + const comments = await this.blogCommentsRepository.findBlogCommentsByBlogId(id); return { blog, comments }; } @@ -170,12 +180,13 @@ export class BlogsService { const blog = await this.blogsRepository.findOneBySlug(slug); if (!blog) throw new BadRequestException(BlogMessage.BLOG_NOT_FOUND); - if (!isAdmin) { - blog.viewCount += 1; - await this.blogsRepository.save(blog); - } + if (!isAdmin) return { blog }; - return { blog }; + blog.viewCount += 1; + await this.blogsRepository.save(blog); + const comments = await this.blogCommentsRepository.findBlogCommentsByBlogId(blog.id); + + return { blog, comments }; } //*********************************** */ diff --git a/src/modules/blogs/repositories/blogs.repository.ts b/src/modules/blogs/repositories/blogs.repository.ts index 1d0d4eb..559cbb9 100644 --- a/src/modules/blogs/repositories/blogs.repository.ts +++ b/src/modules/blogs/repositories/blogs.repository.ts @@ -13,73 +13,66 @@ export class BlogsRepository extends Repository { } async findOneById(id: string): Promise { - return this.findOne({ - where: { id, deletedAt: IsNull() }, - relations: { - author: true, - category: true, - }, - select: { - id: true, - title: true, - previewContent: true, - metaTitle: true, - metaDescription: true, - imageUrl: true, - slug: true, - createdAt: true, - content: true, - tags: true, - viewCount: true, - isActive: true, - isPinned: true, - category: { - id: true, - title: true, - iconUrl: true, - description: true, - }, - author: { - id: true, - firstName: true, - lastName: true, - }, - }, - }); + const queryBuilder = this.createQueryBuilder("blog") + .leftJoinAndSelect("blog.author", "author") + .leftJoinAndSelect("blog.category", "category") + .where("blog.id = :id", { id }) + .andWhere("blog.deletedAt IS NULL") + .select([ + "blog.id", + "blog.title", + "blog.previewContent", + "blog.metaTitle", + "blog.metaDescription", + "blog.imageUrl", + "blog.slug", + "blog.createdAt", + "blog.content", + "blog.tags", + "blog.viewCount", + "blog.isActive", + "blog.isPinned", + "category.id", + "category.title", + "category.iconUrl", + "category.description", + "author.id", + "author.firstName", + "author.lastName", + ]); + + return queryBuilder.getOne(); } async findOneBySlug(slug: string): Promise { - return this.findOne({ - where: { slug, deletedAt: IsNull(), isActive: true }, - relations: { - author: true, - category: true, - }, - select: { - id: true, - title: true, - previewContent: true, - metaTitle: true, - metaDescription: true, - imageUrl: true, - slug: true, - createdAt: true, - content: true, - tags: true, - viewCount: true, - category: { - id: true, - title: true, - iconUrl: true, - description: true, - }, - author: { - id: true, - firstName: true, - lastName: true, - }, - }, - }); + const queryBuilder = this.createQueryBuilder("blog") + .leftJoinAndSelect("blog.author", "author") + .leftJoinAndSelect("blog.category", "category") + .where("blog.slug = :slug", { slug }) + .andWhere("blog.deletedAt IS NULL") + .andWhere("blog.isActive = :isActive", { isActive: true }) + .select([ + "blog.id", + "blog.title", + "blog.previewContent", + "blog.metaTitle", + "blog.metaDescription", + "blog.imageUrl", + "blog.slug", + "blog.createdAt", + "blog.content", + "blog.tags", + "blog.viewCount", + "category.id", + "category.title", + "category.iconUrl", + "category.description", + "author.id", + "author.firstName", + "author.lastName", + ]); + + return queryBuilder.getOne(); } async fineOneByTitle(title: string): Promise { diff --git a/src/modules/contact-us/DTO/create-contact-us.dto.ts b/src/modules/contact-us/DTO/create-contact-us.dto.ts index b4dc46d..9e90713 100755 --- a/src/modules/contact-us/DTO/create-contact-us.dto.ts +++ b/src/modules/contact-us/DTO/create-contact-us.dto.ts @@ -1,26 +1,33 @@ import { ApiProperty } from "@nestjs/swagger"; -import { IsEmail, IsNotEmpty, IsString } from "class-validator"; +import { IsEmail, IsMobilePhone, IsNotEmpty, IsString, Length } from "class-validator"; -import { ContactUsMessage } from "../../../common/enums/message.enum"; +import { AuthMessage, ContactUsMessage } from "../../../common/enums/message.enum"; export class CreateContactUsDto { @IsNotEmpty({ message: ContactUsMessage.FULLNAME_IS_REQUIRED }) @IsString({ message: ContactUsMessage.FULLNAME_STRING }) - @ApiProperty({ description: "full name of person need to create contact us", example: "متین جمشیدی" }) + @ApiProperty({ description: "Full name of the person creating the contact request", example: "mahyar gdz" }) fullName: string; - @IsNotEmpty({ message: ContactUsMessage.FULLNAME_STRING }) - @IsEmail() - @ApiProperty({ description: "email of person need to contact us", example: "matin@gmail.com" }) + @IsNotEmpty({ message: AuthMessage.EMAIL_NOT_EMPTY }) + @IsEmail({}, { message: AuthMessage.INVALID_EMAIL_FORMAT }) + @ApiProperty({ description: "Email address", example: "ma@gmail.com" }) email: string; - @IsNotEmpty({ message: ContactUsMessage.TITLE_IS_REQUIRED }) - @IsString({ message: ContactUsMessage.TITLE_STRING }) - @ApiProperty({ description: "title of contact us", example: "عنوان پیام" }) - title: string; + @IsNotEmpty({ message: AuthMessage.PHONE_NOT_EMPTY }) + @IsMobilePhone("fa-IR", {}, { message: AuthMessage.INVALID_PHONE_FORMAT }) + @Length(11, 11, { message: AuthMessage.PHONE_SHOULD_BE_11_DIGIT }) + @ApiProperty({ description: "Phone number", default: "09922320740" }) + phone: string; + + @IsNotEmpty({ message: ContactUsMessage.BUSINESS_NAME_IS_REQUIRED }) + @IsString({ message: ContactUsMessage.BUSINESS_NAME_STRING }) + @Length(2, 100, { message: ContactUsMessage.BUSINESS_NAME_LENGTH }) + @ApiProperty({ description: "Business name", example: "Tech Corp" }) + businessName: string; @IsNotEmpty({ message: ContactUsMessage.CONTENT_IS_REQUIRED }) @IsString({ message: ContactUsMessage.CONTENT_STRING }) - @ApiProperty({ description: "content of contact us", example: "متن پیام" }) + @ApiProperty({ description: "Content of the contact request", example: "متن پیام" }) content: string; } diff --git a/src/modules/contact-us/contact-us.controller.ts b/src/modules/contact-us/contact-us.controller.ts index b9788b0..afd5097 100755 --- a/src/modules/contact-us/contact-us.controller.ts +++ b/src/modules/contact-us/contact-us.controller.ts @@ -1,20 +1,23 @@ import { Body, Controller, Get, Param, Post, Query } from "@nestjs/common"; -import { ApiOperation, ApiTags } from "@nestjs/swagger"; +import { ApiOperation } from "@nestjs/swagger"; import { CreateContactUsDto } from "./DTO/create-contact-us.dto"; import { SearchContactUsQueryDto } from "./DTO/search-contact-us-query.dto"; import { ContactUsService } from "./providers/contact-us.service"; +import { AdminRoute } from "../../common/decorators/admin.decorator"; import { AuthGuards } from "../../common/decorators/auth-guard.decorator"; import { PermissionsDec } from "../../common/decorators/permission.decorator"; +import { SkipAuth } from "../../common/decorators/skip-auth.decorator"; import { ParamDto } from "../../common/DTO/param.dto"; import { PermissionEnum } from "../users/enums/permission.enum"; @Controller("contact-us") -@ApiTags("ContactUs") +@AuthGuards() export class ContactUsController { constructor(private readonly contactUsService: ContactUsService) {} @ApiOperation({ summary: "Create a new contact us" }) + @SkipAuth() @Post() createContactUs(@Body() createContactUsDto: CreateContactUsDto) { return this.contactUsService.createContactUs(createContactUsDto); @@ -22,9 +25,9 @@ export class ContactUsController { //******************** */ - @AuthGuards() @PermissionsDec(PermissionEnum.CONTACTS_US) - @ApiOperation({ summary: "Get all contact us ===> login as admin" }) + @AdminRoute() + @ApiOperation({ summary: "Get all contact us (admin route)" }) @Get() getAllContactUs(@Query() queryDto: SearchContactUsQueryDto) { return this.contactUsService.getAllContactUs(queryDto); @@ -32,9 +35,9 @@ export class ContactUsController { //******************** */ - @AuthGuards() + @AdminRoute() @PermissionsDec(PermissionEnum.CONTACTS_US) - @ApiOperation({ summary: "Get one contact us with id ===> login as admin" }) + @ApiOperation({ summary: "Get one contact us with id (admin route)" }) @Get(":id") getOneContactUs(@Param() paramDto: ParamDto) { return this.contactUsService.getOneContactUs(paramDto.id); diff --git a/src/modules/contact-us/entities/contact-us.entity.ts b/src/modules/contact-us/entities/contact-us.entity.ts index a26bdf4..f75589b 100755 --- a/src/modules/contact-us/entities/contact-us.entity.ts +++ b/src/modules/contact-us/entities/contact-us.entity.ts @@ -7,18 +7,18 @@ export class ContactUs extends BaseEntity { @Column({ type: "int", generated: "identity", insert: false }) numericId: number; - @Column({ type: "varchar", length: 150, nullable: false }) - title: string; + @Column({ type: "varchar", length: 150 }) + businessName: string; - @Column({ type: "varchar", length: 350 }) + @Column({ type: "varchar", length: 250 }) fullName: string; + @Column({ type: "varchar", length: 11 }) + phone: string; + @Column({ type: "varchar", length: 150 }) email: string; @Column({ type: "text", nullable: false }) content: string; - - @Column({ type: "boolean", default: false }) - isRead: boolean; } diff --git a/src/modules/contact-us/providers/contact-us.service.ts b/src/modules/contact-us/providers/contact-us.service.ts index 5036435..321f837 100755 --- a/src/modules/contact-us/providers/contact-us.service.ts +++ b/src/modules/contact-us/providers/contact-us.service.ts @@ -13,9 +13,7 @@ export class ContactUsService { //****************************** */ async createContactUs(createContactUsDto: CreateContactUsDto) { - const contactUs = this.contactUsRepository.create({ - ...createContactUsDto, - }); + const contactUs = this.contactUsRepository.create({ ...createContactUsDto }); await this.contactUsRepository.save(contactUs); return { @@ -56,15 +54,10 @@ export class ContactUsService { //****************************** */ async getOneContactUs(id: string) { - const contactUs = await this.contactUsRepository.findOneBy({ - id, - }); + const contactUs = await this.contactUsRepository.findOneBy({ id }); if (!contactUs) throw new BadRequestException(ContactUsMessage.NOT_FOUND); - contactUs.isRead = true; - await this.contactUsRepository.save(contactUs); - return { contactUs }; } } diff --git a/src/modules/danak-services/danak-services.controller.ts b/src/modules/danak-services/danak-services.controller.ts index 15f5fe0..ecda947 100755 --- a/src/modules/danak-services/danak-services.controller.ts +++ b/src/modules/danak-services/danak-services.controller.ts @@ -129,7 +129,6 @@ export class DanakServicesController { } @ApiOperation({ summary: "get services for user side" }) - @AuthGuards() @Get("suggested") getDanakSuggestServices() { return this.danakServicesService.getDanakSuggestServices(); diff --git a/src/modules/payments/providers/payments.service.ts b/src/modules/payments/providers/payments.service.ts index 3562979..0045c7f 100755 --- a/src/modules/payments/providers/payments.service.ts +++ b/src/modules/payments/providers/payments.service.ts @@ -2,6 +2,7 @@ import { InjectQueue } from "@nestjs/bullmq"; import { BadRequestException, HttpException, HttpStatus, Injectable, InternalServerErrorException } from "@nestjs/common"; import { ConfigService } from "@nestjs/config"; import { Queue } from "bullmq"; +import dayjs from "dayjs"; // eslint-disable-next-line import/no-named-as-default import Decimal from "decimal.js"; import { FastifyReply } from "fastify"; @@ -10,11 +11,9 @@ import { DataSource, Not, QueryRunner } from "typeorm"; import { PaginationDto } from "../../../common/DTO/pagination.dto"; import { CommonMessage, PaymentMessage, UserMessage, WalletMessage } from "../../../common/enums/message.enum"; import { NotificationsService } from "../../notifications/providers/notifications.service"; -import { ReferralReward } from "../../referrals/entities/referral-reward.entity"; -import { Referral } from "../../referrals/entities/referral.entity"; import { ReferralRewardStatus } from "../../referrals/enums/referral-reward-status.enum"; import { ReferralStatus } from "../../referrals/enums/referral-status.enum"; -// import { ReferralsService } from "../../referrals/providers/referrals.service"; +import { ReferralsService } from "../../referrals/providers/referrals.service"; import { User } from "../../users/entities/user.entity"; import { PaginationUtils } from "../../utils/providers/pagination.utils"; import { WalletsService } from "../../wallets/providers/wallets.service"; @@ -51,8 +50,8 @@ export class PaymentsService { private readonly bankAccountsRepository: BankAccountsRepository, private readonly walletsService: WalletsService, private readonly depositRequestsRepository: DepositRequestsRepository, + private readonly referralsService: ReferralsService, private dataSource: DataSource, - // private readonly referralsService: ReferralsService, ) {} //*********************************** */ @@ -261,55 +260,38 @@ export class PaymentsService { } //*********************************** */ - private async isFirstPurchase(userId: string, queryRunner: QueryRunner): Promise { + private async isFirstPurchase(userId: string, queryRunner: QueryRunner) { const paymentCount = await queryRunner.manager.count(Payment, { where: { user: { id: userId }, status: PaymentStatus.COMPLETED }, }); return paymentCount === 0; } - //*********************************** */ - private async getPendingReferral(userId: string, queryRunner: QueryRunner): Promise { - return queryRunner.manager.findOne(Referral, { - where: { - referredUser: { id: userId }, - status: ReferralStatus.PENDING, - }, - relations: { referrer: true }, - }); - } //*********************************** */ private async handleReferralReward(payment: Payment, queryRunner: QueryRunner) { const isFirstPurchase = await this.isFirstPurchase(payment.user.id, queryRunner); if (!isFirstPurchase) return; - const referral = await this.getPendingReferral(payment.user.id, queryRunner); + const referral = await this.referralsService.getPendingReferral(payment.user.id, queryRunner); if (!referral) return; - // Reward amount is 10% of the purchase amount + // 10% const rewardAmount = new Decimal(payment.amount).mul(0.1); - // Create reward record - const reward = queryRunner.manager.create(ReferralReward, { - user: { id: referral.referrer.id }, - referral: { id: referral.id }, - amount: rewardAmount, - status: ReferralRewardStatus.PENDING, - }); - await queryRunner.manager.save(reward); + const reward = await this.referralsService.createReferralReward(referral.referrer.id, referral.id, rewardAmount, queryRunner); // Charge referrer's wallet await this.walletsService.createReferralRewardTransaction(referral.referrer.id, rewardAmount, queryRunner); // Update reward status reward.status = ReferralRewardStatus.PAID; - reward.paidAt = new Date(); + reward.paidAt = dayjs().toDate(); await queryRunner.manager.save(reward); // Update referral status referral.status = ReferralStatus.COMPLETED; - referral.completedAt = new Date(); + referral.completedAt = dayjs().toDate(); await queryRunner.manager.save(referral); } //*********************************** */ diff --git a/src/modules/referrals/entities/referral-reward.entity.ts b/src/modules/referrals/entities/referral-reward.entity.ts index e85af9a..65e2e70 100644 --- a/src/modules/referrals/entities/referral-reward.entity.ts +++ b/src/modules/referrals/entities/referral-reward.entity.ts @@ -16,8 +16,8 @@ export class ReferralReward extends BaseEntity { @ManyToOne(() => Referral, (referral) => referral.rewards) referral: Referral; - @Column({ type: "decimal", precision: 10, scale: 2, nullable: false, transformer: new DecimalTransformer() }) - amount: Decimal; + @Column({ type: "decimal", precision: 10, scale: 2, nullable: true, transformer: new DecimalTransformer() }) + amount?: Decimal; @Column({ type: "enum", enum: ReferralRewardStatus, default: ReferralRewardStatus.PENDING }) status: ReferralRewardStatus; diff --git a/src/modules/referrals/providers/referrals.service.ts b/src/modules/referrals/providers/referrals.service.ts index ff89ada..3f3f8c2 100644 --- a/src/modules/referrals/providers/referrals.service.ts +++ b/src/modules/referrals/providers/referrals.service.ts @@ -3,13 +3,11 @@ import { randomBytes } from "node:crypto"; import { BadRequestException, Injectable } from "@nestjs/common"; // eslint-disable-next-line import/no-named-as-default import Decimal from "decimal.js"; -import { DataSource, QueryRunner } from "typeorm"; +import { QueryRunner } from "typeorm"; import { ReferralMessage, UserMessage } from "../../../common/enums/message.enum"; import { User } from "../../users/entities/user.entity"; -import { WalletsService } from "../../wallets/providers/wallets.service"; import { UseReferralCodeDto } from "../DTO/use-referral-code.dto"; -import { ReferralRewardStatus } from "../enums/referral-reward-status.enum"; import { ReferralStatus } from "../enums/referral-status.enum"; import { ReferralCodesRepository } from "../repositories/referral-codes.repository"; import { ReferralRewardsRepository } from "../repositories/referral-rewards.repository"; @@ -20,8 +18,6 @@ export class ReferralsService { private readonly referralsRepository: ReferralsRepository, private readonly referralCodesRepository: ReferralCodesRepository, private readonly referralRewardsRepository: ReferralRewardsRepository, - private readonly walletsService: WalletsService, - private readonly dataSource: DataSource, ) {} async generateReferralCode(userId: string, queryRunner: QueryRunner) { @@ -54,75 +50,76 @@ export class ReferralsService { } //*********************************** */ - async useReferralCode(dto: UseReferralCodeDto) { - const queryRunner = this.dataSource.createQueryRunner(); + async useReferralCode(dto: UseReferralCodeDto, queryRunner: QueryRunner) { + const referredUser = await queryRunner.manager.findOne(User, { where: { id: dto.userId } }); + if (!referredUser) throw new BadRequestException(UserMessage.USER_NOT_FOUND); - try { - await queryRunner.connect(); - await queryRunner.startTransaction(); + const referralCode = await queryRunner.manager.findOne(this.referralCodesRepository.target, { + where: { code: dto.referralCode, isActive: true }, + relations: { user: true }, + }); + if (!referralCode || !referralCode.user) throw new BadRequestException(ReferralMessage.INVALID_OR_INACTIVE_REFERRAL_CODE); - const referredUser = await queryRunner.manager.findOne(User, { where: { id: dto.userId } }); - if (!referredUser) throw new BadRequestException(UserMessage.USER_NOT_FOUND); + const existingReferral = await queryRunner.manager.findOne(this.referralsRepository.target, { + where: { referredUser: { id: referredUser.id } }, + }); + if (existingReferral) throw new BadRequestException(ReferralMessage.USER_ALREADY_REFERRED); - const referralCode = await queryRunner.manager.findOne(this.referralCodesRepository.target, { - where: { code: dto.referralCode, isActive: true }, - relations: { user: true }, - }); - if (!referralCode || !referralCode.user) throw new BadRequestException(ReferralMessage.INVALID_OR_INACTIVE_REFERRAL_CODE); + const referral = queryRunner.manager.create(this.referralsRepository.target, { + referrer: { id: referralCode.user.id }, + referredUser: { id: referredUser.id }, + referralCode: { id: referralCode.id }, + }); - // Check if user has already been referred - const existingReferral = await queryRunner.manager.findOne(this.referralsRepository.target, { - where: { referredUser: { id: referredUser.id } }, - }); - if (existingReferral) throw new BadRequestException(ReferralMessage.USER_ALREADY_REFERRED); + await queryRunner.manager.save(referral); - // Create referral record - const referral = queryRunner.manager.create(this.referralsRepository.target, { - referrer: { id: referralCode.user.id }, - referredUser: { id: referredUser.id }, - referralCode: { id: referralCode.id }, - status: ReferralStatus.PENDING, - }); + referralCode.usedCount += 1; + await queryRunner.manager.save(referralCode); - await queryRunner.manager.save(referral); + // const reward = queryRunner.manager.create(this.referralRewardsRepository.target, { + // user: { id: referralCode.user.id }, + // referral: { id: referral.id }, + // }); - // Increment usedCount - referralCode.usedCount += 1; - await queryRunner.manager.save(referralCode); + // await queryRunner.manager.save(reward); - // Create pending reward - const reward = queryRunner.manager.create(this.referralRewardsRepository.target, { - user: { id: referralCode.user.id }, - referral: { id: referral.id }, - amount: 100, // Set your reward amount here - status: ReferralRewardStatus.PENDING, - }); - - await queryRunner.manager.save(reward); - - // Charge the referred user's wallet - const chargeAmount = new Decimal(100); - await this.walletsService.createReferralRewardTransaction(referredUser.id, chargeAmount, queryRunner); - - // Update referral status to COMPLETED - referral.status = ReferralStatus.COMPLETED; - await queryRunner.manager.save(referral); - - // Update reward status to PAID - reward.status = ReferralRewardStatus.PAID; - reward.paidAt = new Date(); - await queryRunner.manager.save(reward); - - await queryRunner.commitTransaction(); - return { referral, reward }; - } catch (error) { - await queryRunner.rollbackTransaction(); - throw error; - } finally { - await queryRunner.release(); - } + return referral; } + //*********************************** */ + async getPendingReferral(userId: string, queryRunner: QueryRunner) { + return queryRunner.manager.findOne(this.referralsRepository.target, { + where: { + referredUser: { id: userId }, + status: ReferralStatus.PENDING, + }, + relations: { referrer: true }, + }); + } + //*********************************** */ + + async createReferral(userId: string, queryRunner: QueryRunner) { + const referral = queryRunner.manager.create(this.referralsRepository.target, { + referrer: { id: userId }, + referredUser: { id: userId }, + }); + await queryRunner.manager.save(referral); + } + + //*********************************** */ + + async createReferralReward(userId: string, referralId: string, amount: Decimal, queryRunner: QueryRunner) { + const reward = queryRunner.manager.create(this.referralRewardsRepository.target, { + user: { id: userId }, + referral: { id: referralId }, + amount, + }); + await queryRunner.manager.save(reward); + + return reward; + } + + //*********************************** */ private generateRandomCode(): string { return randomBytes(6).toString("hex"); } diff --git a/src/modules/referrals/referrals.controller.ts b/src/modules/referrals/referrals.controller.ts index fe75b1a..0858be1 100644 --- a/src/modules/referrals/referrals.controller.ts +++ b/src/modules/referrals/referrals.controller.ts @@ -1,31 +1,23 @@ -import { Body, Controller, Post } from "@nestjs/common"; -import { ApiOperation } from "@nestjs/swagger"; +// import { Body, Controller, Post } from "@nestjs/common"; -import { UseReferralCodeDto } from "./DTO/use-referral-code.dto"; -import { ReferralsService } from "./providers/referrals.service"; -import { AuthGuards } from "../../common/decorators/auth-guard.decorator"; -// import { UserDec } from "../../common/decorators/user.decorator"; +// import { ReferralsService } from "./providers/referrals.service"; +// import { AuthGuards } from "../../common/decorators/auth-guard.decorator"; +// // import { UserDec } from "../../common/decorators/user.decorator"; -@Controller("referrals") -@AuthGuards() -export class ReferralsController { - constructor(private readonly referralsService: ReferralsService) {} +// @Controller("referrals") +// @AuthGuards() +// export class ReferralsController { +// constructor(private readonly referralsService: ReferralsService) {} - @ApiOperation({ summary: "Use a referral code" }) - @Post("use-code") - useReferralCode(@Body() dto: UseReferralCodeDto) { - return this.referralsService.useReferralCode(dto); - } +// // @ApiOperation({ summary: "Get referral statistics for a user" }) +// // @Get("stats/:userId") +// // getReferralStats(@Param("userId") userId: string) { +// // return this.referralsService.getReferralStats(userId); +// // } - // @ApiOperation({ summary: "Get referral statistics for a user" }) - // @Get("stats/:userId") - // getReferralStats(@Param("userId") userId: string) { - // return this.referralsService.getReferralStats(userId); - // } - - // @Get("my-stats") - // @ApiOperation({ summary: "Get current user's referral statistics" }) - // getMyReferralStats(@UserDec("id") userId: string) { - // return this.referralsService.getReferralStats(userId); - // } -} +// // @Get("my-stats") +// // @ApiOperation({ summary: "Get current user's referral statistics" }) +// // getMyReferralStats(@UserDec("id") userId: string) { +// // return this.referralsService.getReferralStats(userId); +// // } +// } diff --git a/src/modules/referrals/referrals.module.ts b/src/modules/referrals/referrals.module.ts index 711a94a..d71b345 100644 --- a/src/modules/referrals/referrals.module.ts +++ b/src/modules/referrals/referrals.module.ts @@ -5,16 +5,14 @@ import { ReferralCode } from "./entities/referral-code.entity"; import { ReferralReward } from "./entities/referral-reward.entity"; import { Referral } from "./entities/referral.entity"; import { ReferralsService } from "./providers/referrals.service"; -import { ReferralsController } from "./referrals.controller"; -import { WalletsModule } from "../wallets/wallets.module"; import { ReferralCodesRepository } from "./repositories/referral-codes.repository"; import { ReferralRewardsRepository } from "./repositories/referral-rewards.repository"; import { ReferralsRepository } from "./repositories/referrals.repository"; @Module({ - imports: [TypeOrmModule.forFeature([Referral, ReferralReward, ReferralCode]), WalletsModule], + imports: [TypeOrmModule.forFeature([Referral, ReferralReward, ReferralCode])], providers: [ReferralsService, ReferralsRepository, ReferralCodesRepository, ReferralRewardsRepository], - controllers: [ReferralsController], + controllers: [], exports: [ReferralsService], }) export class ReferralsModule {}