chore: update referral logic

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