From b92a65c22cd0fea9b21880971e8d669fe9796ec6 Mon Sep 17 00:00:00 2001 From: mahyargdz Date: Thu, 6 Mar 2025 09:35:59 +0330 Subject: [PATCH] chore: add create customer and update customer for admin --- src/app.module.ts | 1 - src/common/enums/message.enum.ts | 2 + .../entities/announcement.entity.ts | 2 +- src/modules/auth/DTO/complete-register.dto.ts | 5 - .../danak-services.controller.ts | 8 + .../entities/danak-service.entity.ts | 2 +- .../providers/danak-services.service.ts | 10 + .../dashboards/providers/dashboard.service.ts | 6 +- .../invoices/entities/invoice.entity.ts | 10 +- .../invoices/providers/invoices.service.ts | 1 - .../entities/user-subscription.entity.ts | 4 +- .../providers/subscriptions.service.ts | 4 +- src/modules/users/DTO/create-customer.dto.ts | 49 +++ src/modules/users/DTO/update-customer.dto.ts | 5 + src/modules/users/providers/admins.service.ts | 112 ++++++ .../users/providers/customers.service.ts | 314 +++++++++++++++ src/modules/users/providers/users.service.ts | 358 +----------------- .../users/repositories/users.repository.ts | 91 ++++- src/modules/users/users.controller.ts | 83 ++-- src/modules/users/users.module.ts | 6 +- 20 files changed, 674 insertions(+), 399 deletions(-) create mode 100644 src/modules/users/DTO/create-customer.dto.ts create mode 100644 src/modules/users/DTO/update-customer.dto.ts create mode 100644 src/modules/users/providers/admins.service.ts create mode 100644 src/modules/users/providers/customers.service.ts diff --git a/src/app.module.ts b/src/app.module.ts index bfc63c9..0fb46ee 100755 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -22,7 +22,6 @@ import { ContactUsModule } from "./modules/contact-us/contact-us.module"; import { CriticismModule } from "./modules/criticisms/criticisms.module"; import { DanakServicesModule } from "./modules/danak-services/danak-services.module"; import { DashboardModule } from "./modules/dashboards/dashboard.module"; -// import { DiscountModule } from "./modules/discounts/discounts.module"; import { InvoicesModule } from "./modules/invoices/invoices.module"; import { LearningModule } from "./modules/learnings/learning.module"; import { NotificationModule } from "./modules/notifications/notifications.module"; diff --git a/src/common/enums/message.enum.ts b/src/common/enums/message.enum.ts index 0f2206f..b332087 100755 --- a/src/common/enums/message.enum.ts +++ b/src/common/enums/message.enum.ts @@ -93,6 +93,8 @@ export const enum UserMessage { PHONE_SHOULD_BE_11_DIGIT = "شماره تلفن باید ۱۱ رقم باشد", FINANCIAL_TYPE_IS_LEGAL = "نوع کاربر حقوقی می‌باشد", FINANCIAL_TYPE_IS_REAL = "نوع کاربر حقیقی می‌باشد", + LEGAL_DATA_NOT_FOUND = "اطلاعات حقوقی یافت نشد", + REAL_DATA_NOT_FOUND = "اطلاعات حقیقی یافت نشد", } export const enum CommonMessage { diff --git a/src/modules/announcements/entities/announcement.entity.ts b/src/modules/announcements/entities/announcement.entity.ts index 425f147..ab1ec52 100755 --- a/src/modules/announcements/entities/announcement.entity.ts +++ b/src/modules/announcements/entities/announcement.entity.ts @@ -13,7 +13,7 @@ export class Announcement extends BaseEntity { @Column({ type: "text" }) content: string; - @Column({ type: "timestamp", nullable: true, default: null }) + @Column({ type: "timestamptz", nullable: true, default: null }) publishAt: Date | null; @ManyToOne(() => DanakService, (service) => service.announcements, { onDelete: "CASCADE" }) diff --git a/src/modules/auth/DTO/complete-register.dto.ts b/src/modules/auth/DTO/complete-register.dto.ts index d80b493..8394f45 100755 --- a/src/modules/auth/DTO/complete-register.dto.ts +++ b/src/modules/auth/DTO/complete-register.dto.ts @@ -29,11 +29,6 @@ export class CompleteRegistrationDto { @ApiProperty({ description: "Last name", example: "jamshidi" }) lastName: string; - // @IsNotEmpty({ message: AuthMessage.EMAIL_NOT_EMPTY }) - // @IsEmail({}, { message: AuthMessage.INVALID_EMAIL_FORMAT }) - // @ApiProperty({ description: "Email", example: "ma@gmail.com" }) - // email: string; - @IsNotEmpty({ message: AuthMessage.BIRTH_DATE_NOT_EMPTY }) @IsString({ message: AuthMessage.BIRTH_DATE_NOT_EMPTY }) @ApiProperty({ description: "Birth date", example: "1403/01/01" }) diff --git a/src/modules/danak-services/danak-services.controller.ts b/src/modules/danak-services/danak-services.controller.ts index 0b38358..a354e75 100755 --- a/src/modules/danak-services/danak-services.controller.ts +++ b/src/modules/danak-services/danak-services.controller.ts @@ -56,6 +56,14 @@ export class DanakServicesController { return this.danakServicesService.getCategoriesUserSide(); } + @AuthGuards() + @PermissionsDec(PermissionEnum.SERVICES) + @ApiOperation({ summary: "Get category by id => admin route" }) + @Get("categories/:id") + getCategoryById(@Param() paramDto: ParamDto) { + return this.danakServicesService.getCategoryById(paramDto.id); + } + @AuthGuards() @PermissionsDec(PermissionEnum.SERVICES) @ApiOperation({ summary: "update category by id => admin route" }) diff --git a/src/modules/danak-services/entities/danak-service.entity.ts b/src/modules/danak-services/entities/danak-service.entity.ts index 539a42f..dea052c 100755 --- a/src/modules/danak-services/entities/danak-service.entity.ts +++ b/src/modules/danak-services/entities/danak-service.entity.ts @@ -27,7 +27,7 @@ export class DanakService extends BaseEntity { @Column({ type: "varchar", length: 255, nullable: false }) author: string; - @Column({ type: "timestamp", nullable: false, default: () => "CURRENT_TIMESTAMP" }) + @Column({ type: "timestamptz", nullable: false, default: () => "CURRENT_TIMESTAMP" }) createDate: Date; @Column({ type: "int", nullable: false, default: 0 }) diff --git a/src/modules/danak-services/providers/danak-services.service.ts b/src/modules/danak-services/providers/danak-services.service.ts index dfeec5e..471fe55 100755 --- a/src/modules/danak-services/providers/danak-services.service.ts +++ b/src/modules/danak-services/providers/danak-services.service.ts @@ -45,6 +45,16 @@ export class DanakServicesService { } /******************************************** */ + async getCategoryById(categoryId: string) { + const category = await this.danakServicesCategoryRepository.findOneById(categoryId); + if (!category) throw new BadRequestException(CategoryMessage.CATEGORY_NOT_EXIST); + return { + category, + }; + } + + /******************************************** */ + async updateCategory(categoryId: string, updateDto: UpdateCategoryDto) { const category = await this.danakServicesCategoryRepository.findOneById(categoryId); if (!category) throw new BadRequestException(CategoryMessage.CATEGORY_NOT_EXIST); diff --git a/src/modules/dashboards/providers/dashboard.service.ts b/src/modules/dashboards/providers/dashboard.service.ts index 404b7df..44b798a 100755 --- a/src/modules/dashboards/providers/dashboard.service.ts +++ b/src/modules/dashboards/providers/dashboard.service.ts @@ -7,18 +7,18 @@ import { InvoicesService } from "../../invoices/providers/invoices.service"; import { NotificationsService } from "../../notifications/providers/notifications.service"; import { SubscriptionsService } from "../../subscriptions/providers/subscriptions.service"; import { TicketsService } from "../../tickets/providers/tickets.service"; -import { UsersService } from "../../users/providers/users.service"; +import { CustomersService } from "../../users/providers/customers.service"; @Injectable() export class DashboardService { constructor( private readonly danakServicesService: DanakServicesService, - private readonly usersService: UsersService, private readonly invoicesService: InvoicesService, private readonly ticketsService: TicketsService, private readonly adsService: AdsService, private readonly subscriptionsService: SubscriptionsService, private readonly notificationsService: NotificationsService, + private readonly customersService: CustomersService, // private readonly announcementService: AnnouncementService, ) {} @@ -76,7 +76,7 @@ export class DashboardService { //************************************ */ private async getCustomersCount() { - const customersCount = await this.usersService.getCustomersCount(); + const customersCount = await this.customersService.getCustomersCount(); return customersCount; } diff --git a/src/modules/invoices/entities/invoice.entity.ts b/src/modules/invoices/entities/invoice.entity.ts index 2952e00..0c98210 100755 --- a/src/modules/invoices/entities/invoice.entity.ts +++ b/src/modules/invoices/entities/invoice.entity.ts @@ -23,14 +23,14 @@ export class Invoice extends BaseEntity { @Column({ type: "enum", enum: InvoiceStatus, default: InvoiceStatus.PENDING }) status: InvoiceStatus; - @Column({ type: "timestamp", nullable: false }) + @Column({ type: "timestamptz", nullable: false }) dueDate: Date; - @Column({ type: "timestamp", nullable: true }) - paidAt: Date | null; + @Column({ type: "timestamptz", nullable: true }) + paidAt?: Date; - @Column({ type: "int", default: 0 }) - tax: number; + @Column({ type: "decimal", precision: 16, scale: 2, nullable: false, transformer: new DecimalTransformer() }) + tax: Decimal; //--------------------------------------------- diff --git a/src/modules/invoices/providers/invoices.service.ts b/src/modules/invoices/providers/invoices.service.ts index 46dfa25..ec1b377 100755 --- a/src/modules/invoices/providers/invoices.service.ts +++ b/src/modules/invoices/providers/invoices.service.ts @@ -187,7 +187,6 @@ export class InvoicesService { totalPrice: totalPrice, tax: taxAmount.toNumber(), status: InvoiceStatus.WAIT_PAYMENT, - paidAt: new Date(), dueDate, items: [invoiceItem], }); diff --git a/src/modules/subscriptions/entities/user-subscription.entity.ts b/src/modules/subscriptions/entities/user-subscription.entity.ts index 0d86efd..629e526 100755 --- a/src/modules/subscriptions/entities/user-subscription.entity.ts +++ b/src/modules/subscriptions/entities/user-subscription.entity.ts @@ -13,10 +13,10 @@ export class UserSubscription extends BaseEntity { @ManyToOne(() => SubscriptionPlan, (plan) => plan.userSubscriptions, { nullable: false, onDelete: "CASCADE" }) plan: SubscriptionPlan; - @Column({ type: "timestamp", nullable: false }) + @Column({ type: "timestamptz", nullable: false }) startDate: Date; - @Column({ type: "timestamp", nullable: false }) + @Column({ type: "timestamptz", nullable: false }) endDate: Date; @Column({ type: "varchar", length: 250, nullable: false }) diff --git a/src/modules/subscriptions/providers/subscriptions.service.ts b/src/modules/subscriptions/providers/subscriptions.service.ts index e43dae5..009cb5c 100755 --- a/src/modules/subscriptions/providers/subscriptions.service.ts +++ b/src/modules/subscriptions/providers/subscriptions.service.ts @@ -142,8 +142,8 @@ export class SubscriptionsService { .createQueryBuilder("subscription") .leftJoin("subscription.service", "service") .addSelect(["service.id"]) - .leftJoin("subscription.discounts", "discount") - .addSelect(["discount.calculationType", "discount.amount", "discount.startDate", "discount.endDate", "discount.isActive"]) + // .leftJoin("subscription.discounts", "discount") + // .addSelect(["discount.calculationType", "discount.amount", "discount.startDate", "discount.endDate", "discount.isActive"]) .where("service.id = :serviceId", { serviceId }); if (queryDto.q) { diff --git a/src/modules/users/DTO/create-customer.dto.ts b/src/modules/users/DTO/create-customer.dto.ts new file mode 100644 index 0000000..fb205ea --- /dev/null +++ b/src/modules/users/DTO/create-customer.dto.ts @@ -0,0 +1,49 @@ +import { ApiProperty } from "@nestjs/swagger"; +import { IsEmail, IsMobilePhone, IsNotEmpty, IsNumberString, IsString, Length, MinLength } from "class-validator"; + +import { CreateLegalUserDto } from "./create-legal-user.dto"; +import { IsNationalCode } from "../../../common/decorators/is-national-code.decorator"; +import { AuthMessage } from "../../../common/enums/message.enum"; + +export class CreateCustomerDto extends CreateLegalUserDto { + @IsNotEmpty({ message: AuthMessage.FIRST_NAME_NOT_EMPTY }) + @IsString({ message: AuthMessage.FIRST_NAME_NOT_EMPTY }) + @Length(2, 50, { message: AuthMessage.FIRST_NAME_SHOULD_BE_BETWEEN_2_AND_50 }) + @ApiProperty({ description: "First name", example: "mahyar" }) + firstName: string; + + @IsNotEmpty({ message: AuthMessage.LAST_NAME_NOT_EMPTY }) + @IsString({ message: AuthMessage.LAST_NAME_NOT_EMPTY }) + @Length(2, 50, { message: AuthMessage.LAST_NAME_SHOULD_BE_BETWEEN_2_AND_50 }) + @ApiProperty({ description: "Last name", example: "jamshidi" }) + lastName: string; + + @IsNotEmpty({ message: AuthMessage.EMAIL_NOT_EMPTY }) + @IsEmail({}, { message: AuthMessage.INVALID_EMAIL_FORMAT }) + @ApiProperty({ description: "Email", example: "ma@gmail.com" }) + email: string; + + @IsNotEmpty({ message: AuthMessage.BIRTH_DATE_NOT_EMPTY }) + @IsString({ message: AuthMessage.BIRTH_DATE_NOT_EMPTY }) + @ApiProperty({ description: "Birth date", example: "1403/01/01" }) + birthDate: string; + + @IsNotEmpty({ message: AuthMessage.NATIONAL_NOT_EMPTY }) + @IsNumberString({ no_symbols: true }, { message: AuthMessage.NATIONAL_CODE_INCORRECT }) + @Length(10, 10, { message: AuthMessage.NATIONAL_CODE_INCORRECT }) + @IsNationalCode({ message: AuthMessage.NATIONAL_CODE_INVALID }) + @ApiProperty({ description: "iranian format (10 char)", example: "4569852169" }) + nationalCode: string; + + @IsNotEmpty({ message: AuthMessage.PASSWORD_NOT_EMPTY }) + @IsString({ message: AuthMessage.PASSWORD_FORMAT_INVALID }) + @ApiProperty({ description: "password", example: "12S345SS678" }) + @MinLength(8, { message: AuthMessage.PASSWORD_LENGTH }) + password: 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; +} diff --git a/src/modules/users/DTO/update-customer.dto.ts b/src/modules/users/DTO/update-customer.dto.ts new file mode 100644 index 0000000..94b49d0 --- /dev/null +++ b/src/modules/users/DTO/update-customer.dto.ts @@ -0,0 +1,5 @@ +import { PartialType } from "@nestjs/swagger"; + +import { CreateCustomerDto } from "./create-customer.dto"; + +export class UpdateCustomerDto extends PartialType(CreateCustomerDto) {} diff --git a/src/modules/users/providers/admins.service.ts b/src/modules/users/providers/admins.service.ts new file mode 100644 index 0000000..9754e6c --- /dev/null +++ b/src/modules/users/providers/admins.service.ts @@ -0,0 +1,112 @@ +import { BadRequestException, Injectable } from "@nestjs/common"; +import slugify from "slugify"; +import { In } from "typeorm"; + +import { AdminMessage, AdsMessage, UserMessage } from "../../../common/enums/message.enum"; +import { PaginationUtils } from "../../utils/providers/pagination.utils"; +import { PasswordService } from "../../utils/providers/password.service"; +import { CreateAdminDto } from "../DTO/create-admin.dto"; +import { CreateRoleDto } from "../DTO/create-role.dto"; +import { SearchAdminQueryDto } from "../DTO/search-admins-query.dto"; +import { SearchRolesQueryDto } from "../DTO/search-roles.dto"; +import { PermissionsRepository } from "../repositories/permissions.repository"; +import { RoleRepository } from "../repositories/roles.repository"; +import { UserRepository } from "../repositories/users.repository"; + +@Injectable() +export class AdminsService { + constructor( + private readonly userRepository: UserRepository, + private readonly rolesRepository: RoleRepository, + private readonly permissionsRepository: PermissionsRepository, + private readonly passwordService: PasswordService, + ) {} + + async createAdmin(createDto: CreateAdminDto) { + const existEmail = await this.userRepository.findOneBy({ email: createDto.email }); + if (existEmail) throw new BadRequestException(UserMessage.EMAIL_EXIST); + + const existPhone = await this.userRepository.findOneBy({ phone: createDto.phone }); + if (existPhone) throw new BadRequestException(UserMessage.PHONE_EXIST); + + const role = await this.rolesRepository.findOne({ where: { id: createDto.roleId }, relations: { permissions: true } }); + if (!role) throw new BadRequestException(AdsMessage.ROLE_NOT_FOUND); + + await this.rolesRepository.save(role); + + if (createDto.password !== createDto.repeatPassword) throw new BadRequestException(AdminMessage.PASSWORD_NOT_MATCH); + + const hashedPassword = await this.passwordService.hashPassword(createDto.password); + + const userName = slugify(`${createDto.firstName} ${Date.now().toString().slice(-5)}`, { lower: true, trim: true }); + + const adminUser = this.userRepository.create({ ...createDto, roles: [role], password: hashedPassword, userName }); + await this.userRepository.save(adminUser); + return { + message: AdminMessage.ADMIN_CREATED, + adminUser, + }; + } + /************************************************************ */ + + async createRole(createDto: CreateRoleDto) { + const existRole = await this.rolesRepository.findOne({ where: { name: createDto.name } }); + if (existRole) throw new BadRequestException(AdminMessage.ROLE_EXIST); + + const permissions = await this.permissionsRepository.findBy({ id: In(createDto.permissions) }); + if (permissions.length !== createDto.permissions.length) throw new BadRequestException(AdminMessage.PERMISSIONS_NOT_FOUND); + + const role = this.rolesRepository.create({ ...createDto, permissions, isAdmin: true }); + await this.rolesRepository.save(role); + return { + message: AdminMessage.ROLE_CREATED, + role, + }; + } + + /************************************************************ */ + + async getAdmins(queryDto: SearchAdminQueryDto) { + const [users, count] = await this.userRepository.getAdmins(queryDto); + + return { + users, + count, + paginate: true, + }; + } + + /************************************************************ */ + + async getPermissions() { + const permissions = await this.permissionsRepository.find({ select: { id: true, name: true } }); + return { + permissions, + }; + } + /************************************************************ */ + + async getRoles(queryDto: SearchRolesQueryDto) { + const { limit, skip } = PaginationUtils(queryDto); + const queryBuilder = this.rolesRepository.createQueryBuilder("role"); + + queryBuilder + .where("role.isAdmin = :isAdmin", { isAdmin: true }) + // .leftJoinAndSelect("role.permissions", "permissions") + .loadRelationCountAndMap("role.userCount", "role.users"); + + if (queryDto.q) { + queryBuilder.andWhere("role.name ILIKE :q", { q: `%${queryDto.q}%` }); + } + + queryBuilder.take(limit).skip(skip).orderBy("role.createdAt", "DESC"); + + const [roles, count] = await queryBuilder.getManyAndCount(); + + return { + roles, + count, + paginate: true, + }; + } +} diff --git a/src/modules/users/providers/customers.service.ts b/src/modules/users/providers/customers.service.ts new file mode 100644 index 0000000..238f1aa --- /dev/null +++ b/src/modules/users/providers/customers.service.ts @@ -0,0 +1,314 @@ +import { BadRequestException, Injectable, Logger } from "@nestjs/common"; +import { DataSource, Not } from "typeorm"; + +import { CommonMessage, UserMessage } from "../../../common/enums/message.enum"; +import { AddressService } from "../../address/providers/address.service"; +import { UserSettingsService } from "../../settings/providers/user-settings.service"; +import { PasswordService } from "../../utils/providers/password.service"; +import { WalletsService } from "../../wallets/providers/wallets.service"; +import { CreateCustomerDto } from "../DTO/create-customer.dto"; +import { CreateLegalUserDto } from "../DTO/create-legal-user.dto"; +import { CreateRealUserDto } from "../DTO/create-real-user.dto"; +import { SearchCustomersDto } from "../DTO/search-customers.dto"; +import { UpdateCustomerDto } from "../DTO/update-customer.dto"; +import { FinancialType } from "../enums/financial-type.enum"; +import { RoleEnum } from "../enums/role.enum"; +import { LegalUserRepository } from "../repositories/legal-user.repository"; +import { RealUserRepository } from "../repositories/real-user.repository"; +import { RoleRepository } from "../repositories/roles.repository"; +import { UserRepository } from "../repositories/users.repository"; + +@Injectable() +export class CustomersService { + private readonly logger = new Logger(CustomersService.name); + constructor( + private readonly addressService: AddressService, + private readonly dataSource: DataSource, + private readonly userRepository: UserRepository, + private readonly legalUserRepository: LegalUserRepository, + private readonly realUserRepository: RealUserRepository, + private readonly passwordService: PasswordService, + private readonly userSettingsService: UserSettingsService, + private readonly rolesRepository: RoleRepository, + private readonly walletsService: WalletsService, + ) {} + + async createCustomer(createDto: CreateCustomerDto) { + const queryRunner = this.dataSource.createQueryRunner(); + + try { + await queryRunner.connect(); + await queryRunner.startTransaction(); + + const [existingEmail, existingNationalCode, existingPhone, userRole] = await Promise.all([ + queryRunner.manager.findOne(this.userRepository.target, { where: { email: createDto.email } }), + queryRunner.manager.findOne(this.userRepository.target, { where: { nationalCode: createDto.nationalCode } }), + queryRunner.manager.findOne(this.userRepository.target, { where: { phone: createDto.phone } }), + queryRunner.manager.findOne(this.rolesRepository.target, { where: { name: RoleEnum.USER } }), + ]); + + if (existingEmail) throw new BadRequestException(UserMessage.EMAIL_EXIST); + if (existingNationalCode) throw new BadRequestException(UserMessage.NATIONAL_CODE_EXIST); + if (existingPhone) throw new BadRequestException(UserMessage.PHONE_EXIST); + if (!userRole) throw new BadRequestException(UserMessage.ROLE_NOT_FOUND); + + const user = queryRunner.manager.create(this.userRepository.target, { + ...createDto, + password: await this.passwordService.hashPassword(createDto.password), + roles: [userRole], + }); + await queryRunner.manager.save(user); + + await Promise.all([ + this.userSettingsService.createUserSettings(user.id, queryRunner), + this.walletsService.createUserWallet(user.id, queryRunner), + ]); + + await this.createLegalUserData(createDto, user.id, queryRunner); + + await queryRunner.commitTransaction(); + return { message: CommonMessage.CREATED, user }; + } catch (error) { + await queryRunner.rollbackTransaction(); + this.logger.error("Error in create customer", error); + throw error; + } finally { + await queryRunner.release(); + } + } + //************************************************ */ + async updateCustomer(customerId: string, updateDto: UpdateCustomerDto) { + const queryRunner = this.dataSource.createQueryRunner(); + try { + await queryRunner.connect(); + await queryRunner.startTransaction(); + + const user = await queryRunner.manager.findOne(this.userRepository.target, { where: { id: customerId } }); + if (!user) throw new BadRequestException(UserMessage.USER_NOT_FOUND); + + if (updateDto.email) { + const existingEmail = await queryRunner.manager.findOne(this.userRepository.target, { + where: { email: updateDto.email, id: Not(user.id) }, + }); + if (existingEmail) throw new BadRequestException(UserMessage.EMAIL_EXIST); + } + + if (updateDto.nationalCode) { + const existingNationalCode = await queryRunner.manager.findOne(this.userRepository.target, { + where: { nationalCode: updateDto.nationalCode, id: Not(user.id) }, + }); + if (existingNationalCode) throw new BadRequestException(UserMessage.NATIONAL_CODE_EXIST); + } + + if (updateDto.phone) { + const existingPhone = await queryRunner.manager.findOne(this.userRepository.target, { + where: { phone: updateDto.phone, id: Not(user.id) }, + }); + if (existingPhone) throw new BadRequestException(UserMessage.PHONE_EXIST); + } + + await queryRunner.manager.update(this.userRepository.target, { id: customerId }, updateDto); + + const legalUser = await queryRunner.manager.findOne(this.legalUserRepository.target, { where: { user: { id: customerId } } }); + + if (!legalUser) throw new BadRequestException(UserMessage.LEGAL_DATA_NOT_FOUND); + + if (updateDto.economicCode) { + const existingEconomicCode = await queryRunner.manager.findOne(this.legalUserRepository.target, { + where: { economicCode: updateDto.economicCode, id: Not(legalUser.id) }, + }); + if (existingEconomicCode) throw new BadRequestException(UserMessage.ECONOMIC_CODE_EXIST); + } + + if (updateDto.registrationCode) { + const existingRegistrationCode = await queryRunner.manager.findOne(this.legalUserRepository.target, { + where: { registrationCode: updateDto.registrationCode, id: Not(legalUser.id) }, + }); + if (existingRegistrationCode) throw new BadRequestException(UserMessage.REGISTRATION_CODE_EXIST); + } + + if (updateDto.nationalIdentity) { + const existingNationalIdentity = await queryRunner.manager.findOne(this.legalUserRepository.target, { + where: { nationalIdentity: updateDto.nationalIdentity, id: Not(legalUser.id) }, + }); + if (existingNationalIdentity) throw new BadRequestException(UserMessage.NATIONAL_IDENTITY_EXIST); + } + + if (updateDto.cityId) { + const { city } = await this.addressService.getCity(+updateDto.cityId); + await queryRunner.manager.update(this.legalUserRepository.target, { id: legalUser.id }, { address: { city } }); + } + + const { address, ...legalUserUpdateData } = updateDto; + await queryRunner.manager.update(this.legalUserRepository.target, { id: legalUser.id }, legalUserUpdateData); + + if (address) { + await queryRunner.manager.update(this.legalUserRepository.target, { id: legalUser.id }, { address: { fullAddress: address } }); + } + + await queryRunner.commitTransaction(); + + return { + message: CommonMessage.UPDATE_SUCCESS, + }; + } catch (error) { + await queryRunner.rollbackTransaction(); + this.logger.error("Error in update customer", error); + throw error; + } finally { + await queryRunner.release(); + } + } + + /************************************************************ */ + + async getCustomersCount() { + const count = await this.userRepository.count({ where: { roles: { name: RoleEnum.USER } } }); + return count; + } + + /************************************************************ */ + async createRealUserData(createDto: CreateRealUserDto, userId: string, queryRunner = this.dataSource.createQueryRunner()) { + let transactionStarted = false; + + try { + if (!queryRunner.isTransactionActive) { + await queryRunner.connect(); + await queryRunner.startTransaction(); + transactionStarted = true; + } + + const user = await queryRunner.manager.findOne(this.userRepository.target, { where: { id: userId } }); + if (!user) throw new BadRequestException(UserMessage.USER_NOT_FOUND); + + if (user.financialType && user.financialType === FinancialType.LEGAL) + throw new BadRequestException(UserMessage.FINANCIAL_TYPE_IS_LEGAL); + + const existingRealUser = await queryRunner.manager.findOne(this.realUserRepository.target, { where: { user: { id: userId } } }); + if (existingRealUser) throw new BadRequestException(UserMessage.REAL_DATA_EXIST); + + const duplicateChecks = await Promise.all([ + queryRunner.manager.findOne(this.realUserRepository.target, { where: { nationalCode: createDto.nationalCode } }), + queryRunner.manager.findOne(this.realUserRepository.target, { where: { phone: createDto.phone } }), + ]); + + if (duplicateChecks[0]) throw new BadRequestException(UserMessage.NATIONAL_CODE_EXIST); + if (duplicateChecks[1]) throw new BadRequestException(UserMessage.PHONE_EXIST); + + const { city } = await this.addressService.getCity(+createDto.cityId); + + const realUser = queryRunner.manager.create(this.realUserRepository.target, { + ...createDto, + user, + address: { + fullAddress: createDto.address, + city, + postalCode: createDto.postalCode, + }, + }); + + await queryRunner.manager.save(realUser); + + user.financialType = FinancialType.REAL; + await queryRunner.manager.save(user); + + if (transactionStarted) await queryRunner.commitTransaction(); + + return { + message: CommonMessage.CREATED, + realUser, + }; + } catch (error) { + if (transactionStarted) await queryRunner.rollbackTransaction(); + + throw error; + } finally { + if (transactionStarted) await queryRunner.release(); + } + } + + // /************************************************************ */ + + async createLegalUserData(createDto: CreateLegalUserDto, userId: string, queryRunner = this.dataSource.createQueryRunner()) { + let transactionStarted = false; + + try { + if (!queryRunner.isTransactionActive) { + await queryRunner.connect(); + await queryRunner.startTransaction(); + transactionStarted = true; + } + + const user = await queryRunner.manager.findOne(this.userRepository.target, { where: { id: userId } }); + if (!user) throw new BadRequestException(UserMessage.USER_NOT_FOUND); + + if (user.financialType && user.financialType === FinancialType.REAL) + throw new BadRequestException(UserMessage.FINANCIAL_TYPE_IS_REAL); + + const existingLegalUser = await queryRunner.manager.findOne(this.legalUserRepository.target, { where: { user: { id: user.id } } }); + + if (existingLegalUser) throw new BadRequestException(UserMessage.LEGAL_DATA_EXIST); + + const duplicateChecks = await Promise.all([ + queryRunner.manager.findOne(this.legalUserRepository.target, { where: { economicCode: createDto.economicCode } }), + queryRunner.manager.findOne(this.legalUserRepository.target, { where: { phone: createDto.phone } }), + queryRunner.manager.findOne(this.legalUserRepository.target, { where: { registrationCode: createDto.registrationCode } }), + queryRunner.manager.findOne(this.legalUserRepository.target, { where: { nationalIdentity: createDto.nationalIdentity } }), + ]); + + if (duplicateChecks[0]) throw new BadRequestException(UserMessage.ECONOMIC_CODE_EXIST); + if (duplicateChecks[1]) throw new BadRequestException(UserMessage.PHONE_EXIST); + if (duplicateChecks[2]) throw new BadRequestException(UserMessage.REGISTRATION_CODE_EXIST); + if (duplicateChecks[3]) throw new BadRequestException(UserMessage.NATIONAL_IDENTITY_EXIST); + + const { city } = await this.addressService.getCity(+createDto.cityId); + + const legalUser = queryRunner.manager.create(this.legalUserRepository.target, { + ...createDto, + user, + address: { + fullAddress: createDto.address, + city, + postalCode: createDto.postalCode, + }, + }); + + await queryRunner.manager.save(legalUser); + + user.financialType = FinancialType.LEGAL; + await queryRunner.manager.save(user); + + if (transactionStarted) await queryRunner.commitTransaction(); + + return { + message: CommonMessage.CREATED, + legalUser, + }; + } catch (error) { + if (transactionStarted) await queryRunner.rollbackTransaction(); + + throw error; + } finally { + if (transactionStarted) await queryRunner.release(); + } + } + + /************************************************************ */ + + async getCustomers(queryDto: SearchCustomersDto) { + const [customers, count] = await this.userRepository.getCustomer(queryDto); + + return { customers, count }; + } + + /************************************************************ */ + + async getCustomerById(userId: string) { + const customer = await this.userRepository.findOne({ + where: { id: userId, roles: { name: RoleEnum.USER } }, + relations: { legalUser: true, realUser: true }, + }); + + return { customer }; + } +} diff --git a/src/modules/users/providers/users.service.ts b/src/modules/users/providers/users.service.ts index db1ebba..3729251 100755 --- a/src/modules/users/providers/users.service.ts +++ b/src/modules/users/providers/users.service.ts @@ -6,7 +6,7 @@ import { FastifyReply } from "fastify"; import slugify from "slugify"; import { In, Not, QueryRunner } from "typeorm"; -import { AdminMessage, AdsMessage, AuthMessage, CommonMessage, UserMessage } from "../../../common/enums/message.enum"; +import { AuthMessage, CommonMessage, UserMessage } from "../../../common/enums/message.enum"; import { AddressService } from "../../address/providers/address.service"; import { CompleteRegistrationDto } from "../../auth/DTO/complete-register.dto"; import { RequestOtpDto } from "../../auth/DTO/request-otp.dto"; @@ -15,31 +15,19 @@ import { UserSettingsService } from "../../settings/providers/user-settings.serv import { CacheService } from "../../utils/providers/cache.service"; import { EmailService } from "../../utils/providers/email.service"; import { OTPService } from "../../utils/providers/otp.service"; -import { PaginationUtils } from "../../utils/providers/pagination.utils"; -import { PasswordService } from "../../utils/providers/password.service"; import { SmsService } from "../../utils/providers/sms.service"; import { WalletsService } from "../../wallets/providers/wallets.service"; import { ChangeEmailDto } from "../DTO/change-email.dto"; import { CheckValidityDTO } from "../DTO/check-validity.dto"; -import { CreateAdminDto } from "../DTO/create-admin.dto"; -import { CreateLegalUserDto } from "../DTO/create-legal-user.dto"; -import { CreateRealUserDto } from "../DTO/create-real-user.dto"; -import { CreateRoleDto } from "../DTO/create-role.dto"; -import { SearchAdminQueryDto } from "../DTO/search-admins-query.dto"; -import { SearchCustomersDto } from "../DTO/search-customers.dto"; -import { SearchRolesQueryDto } from "../DTO/search-roles.dto"; import { UpdateProfileDto } from "../DTO/update-profile.dto"; import { CreateUserGroupDto } from "../DTO/user-group.dto"; import { EmailVerifyQueryDto } from "../DTO/verify-email-query.dto"; import { Role } from "../entities/role.entity"; import { User } from "../entities/user.entity"; -import { FinancialType } from "../enums/financial-type.enum"; import { RoleEnum } from "../enums/role.enum"; import { ValidityType } from "../enums/validity-type.enum"; import { LegalUserRepository } from "../repositories/legal-user.repository"; -import { PermissionsRepository } from "../repositories/permissions.repository"; import { RealUserRepository } from "../repositories/real-user.repository"; -import { RoleRepository } from "../repositories/roles.repository"; import { UserGroupRepository } from "../repositories/user-group.repository"; import { UserRepository } from "../repositories/users.repository"; @@ -51,9 +39,6 @@ export class UsersService { private readonly userGroupRepository: UserGroupRepository, private readonly userSettingsService: UserSettingsService, private readonly walletsService: WalletsService, - private readonly rolesRepository: RoleRepository, - private readonly permissionsRepository: PermissionsRepository, - private readonly passwordService: PasswordService, private readonly addressService: AddressService, private readonly realUserRepository: RealUserRepository, private readonly legalUserRepository: LegalUserRepository, @@ -81,6 +66,19 @@ export class UsersService { /************************************************************ */ + async findOneWithEmail(email: string): Promise { + const user = await this.userRepository.findOneWithEmail(email); + return user; + } + /************************************************************ */ + + async findOneWithPhone(phone: string): Promise { + const user = await this.userRepository.findOneWithPhone(phone); + return user; + } + + /************************************************************ */ + async getMe(userId: string) { const user = await this.userRepository.findOne({ where: { id: userId }, relations: { roles: true } }); if (!user) throw new BadRequestException(UserMessage.USER_NOT_FOUND); @@ -235,23 +233,13 @@ export class UsersService { /************************************************************ */ - async findOneWithEmail(email: string): Promise { - const user = await this.userRepository.findOneWithEmail(email); - return user; - } - /************************************************************ */ - - async findOneWithPhone(phone: string): Promise { - const user = await this.userRepository.findOneWithPhone(phone); - return user; - } - - /************************************************************ */ - async createUser(registerDto: CompleteRegistrationDto, hashedPassword: string, queryRunner: QueryRunner): Promise { const role = await queryRunner.manager.findOneBy(Role, { name: RoleEnum.USER }); if (!role) throw new BadRequestException(UserMessage.ROLE_NOT_FOUND); + const existPhone = await queryRunner.manager.findOneBy(User, { phone: registerDto.phone }); + if (existPhone) throw new BadRequestException(UserMessage.PHONE_EXIST); + const existUser = await queryRunner.manager.findOneBy(User, { nationalCode: registerDto.nationalCode }); if (existUser) throw new BadRequestException(UserMessage.NATIONAL_CODE_EXIST); @@ -271,50 +259,6 @@ export class UsersService { /************************************************************ */ - async createAdmin(createDto: CreateAdminDto) { - const existEmail = await this.userRepository.findOneBy({ email: createDto.email }); - if (existEmail) throw new BadRequestException(UserMessage.EMAIL_EXIST); - - const existPhone = await this.userRepository.findOneBy({ phone: createDto.phone }); - if (existPhone) throw new BadRequestException(UserMessage.PHONE_EXIST); - - const role = await this.rolesRepository.findOne({ where: { id: createDto.roleId }, relations: { permissions: true } }); - if (!role) throw new BadRequestException(AdsMessage.ROLE_NOT_FOUND); - - await this.rolesRepository.save(role); - - if (createDto.password !== createDto.repeatPassword) throw new BadRequestException(AdminMessage.PASSWORD_NOT_MATCH); - - const hashedPassword = await this.passwordService.hashPassword(createDto.password); - - const userName = slugify(`${createDto.firstName} ${Date.now().toString().slice(-5)}`, { lower: true, trim: true }); - - const adminUser = this.userRepository.create({ ...createDto, roles: [role], password: hashedPassword, userName }); - await this.userRepository.save(adminUser); - return { - message: AdminMessage.ADMIN_CREATED, - adminUser, - }; - } - /************************************************************ */ - - async createRole(createDto: CreateRoleDto) { - const existRole = await this.rolesRepository.findOne({ where: { name: createDto.name } }); - if (existRole) throw new BadRequestException(AdminMessage.ROLE_EXIST); - - const permissions = await this.permissionsRepository.findBy({ id: In(createDto.permissions) }); - if (permissions.length !== createDto.permissions.length) throw new BadRequestException(AdminMessage.PERMISSIONS_NOT_FOUND); - - const role = this.rolesRepository.create({ ...createDto, permissions, isAdmin: true }); - await this.rolesRepository.save(role); - return { - message: AdminMessage.ROLE_CREATED, - role, - }; - } - - /************************************************************ */ - async checkValidity(checkDto: CheckValidityDTO, userId: string) { const { type } = checkDto; if (type === ValidityType.USERNAME) checkDto.value = slugify(checkDto.value, { lower: true, trim: true }); @@ -386,7 +330,7 @@ export class UsersService { async findAllUsers() { const users = await this.userRepository.find({ - relations: ["groups"], + relations: { groups: true }, }); return { users }; @@ -398,279 +342,13 @@ export class UsersService { if (!user) throw new BadRequestException(UserMessage.USER_NOT_FOUND); return user.roles.some((role) => role.name === RoleEnum.SUPER_ADMIN); } - /************************************************************ */ - async getCustomers(queryDto: SearchCustomersDto) { - const { limit, skip } = PaginationUtils(queryDto); - - const queryBuilder = this.userRepository - .createQueryBuilder("user") - .leftJoin("user.roles", "role") - .where("role.name = :roleName", { roleName: RoleEnum.USER }) - .leftJoin("user.groups", "groups") - .addSelect(["groups.id", "groups.name"]) - .loadRelationCountAndMap("user.invoicesCount", "user.invoices") - .loadRelationCountAndMap("user.subscriptionsCount", "user.subscriptions") - .loadRelationCountAndMap("user.ticketsCount", "user.tickets"); - - if (queryDto.q) { - queryBuilder - .orWhere("user.firstName ILIKE :search", { search: `%${queryDto.q}%` }) - .orWhere("user.lastName ILIKE :search", { search: `%${queryDto.q}%` }) - .orWhere("user.userName ILIKE :search", { search: `%${queryDto.q}%` }); - } - - const [customers, count] = await queryBuilder.skip(skip).take(limit).getManyAndCount(); - - return { customers, count }; - } - - /************************************************************ */ - - // async createCustomer(createDto: CreateCustomerDto) { - // const { cityId, address, postalCode, economicCode, companyRegisteredName, registrationId, nationalId, number, ...userData } = createDto; - - // const queryRunner = this.userRepository.manager.connection.createQueryRunner(); - // await queryRunner.connect(); - // await queryRunner.startTransaction(); - - // try { - // const existUser = await queryRunner.manager.findOneBy(User, { email: createDto.email }); - // if (existUser) throw new BadRequestException(UserMessage.EMAIL_EXIST); - - // const role = await queryRunner.manager.findOneBy(Role, { name: RoleEnum.USER }); - // if (!role) throw new BadRequestException(UserMessage.ROLE_NOT_FOUND); - - // const hashedPassword = await this.passwordService.hashPassword(createDto.password); - - // const legalUserData = economicCode - // ? { - // economicCode, - // companyRegisteredName, - // registrationId, - // nationalId, - // number, - // address: { - // address, - // city: cityId - // ? { - // id: +cityId, - // } - // : undefined, - // postalCode, - // }, - // } - // : undefined; - - // const user = queryRunner.manager.create(User, { - // ...userData, - // password: hashedPassword, - // roles: [role], - // legalUser: legalUserData, - // }); - // await queryRunner.manager.save(user); - - // await this.userSettingsService.createUserSettings(user.id, queryRunner); - - // await this.walletsService.createUserWallet(user.id, queryRunner); - - // await queryRunner.commitTransaction(); - - // return { - // message: CommonMessage.CREATED, - // user, - // }; - // } catch (error) { - // await queryRunner.rollbackTransaction(); - // throw error; - // } finally { - // await queryRunner.release(); - // } - // } - - /************************************************************ */ - - async findOneCustomer(userId: string) { - const customer = await this.userRepository.findOne({ - where: { id: userId, roles: { name: RoleEnum.USER } }, - relations: { legalUser: true, realUser: true }, - }); - - return { customer }; - } /************************************************************ */ async getUsersByGroup(groupId: string) { const users = await this.userRepository.find({ where: { groups: { id: groupId } }, relations: { tickets: true } }); return { users }; } - /************************************************************ */ - - async getAdmins(queryDto: SearchAdminQueryDto) { - const { limit, skip } = PaginationUtils(queryDto); - const queryBuilder = this.userRepository.createQueryBuilder("user"); - - queryBuilder - .leftJoinAndSelect("user.roles", "role") - // .addSelect(["role.id", "role.name"]) - .where("role.isAdmin = :isAdmin", { isAdmin: true }) - .leftJoinAndSelect("user.groups", "groups") - .leftJoinAndSelect("role.permissions", "permissions"); - // .where("permissions.name IN (:...adminPermissions)", { - // adminPermissions: [PermissionEnum.ADMINS], - // }); - // .addSelect(["permissions.id", "permissions.name"]); - - if (queryDto.q) { - queryBuilder.andWhere("user.firstName ILIKE :q OR user.lastName ILIKE :q OR user.userName ILIKE :q", { - q: `%${queryDto.q}%`, - }); - } - - if (queryDto.permission) { - queryBuilder.andWhere("permissions.name = :permName", { permName: queryDto.permission }); - } - - queryBuilder.take(limit).skip(skip).orderBy("user.createdAt", "DESC"); - - const [users, count] = await queryBuilder.getManyAndCount(); - - return { - users, - count, - paginate: true, - }; - } - - /************************************************************ */ - - async getPermissions() { - const permissions = await this.permissionsRepository.find({ select: { id: true, name: true } }); - return { - permissions, - }; - } - /************************************************************ */ - - async getRoles(queryDto: SearchRolesQueryDto) { - const { limit, skip } = PaginationUtils(queryDto); - const queryBuilder = this.rolesRepository.createQueryBuilder("role"); - - queryBuilder - .where("role.isAdmin = :isAdmin", { isAdmin: true }) - // .leftJoinAndSelect("role.permissions", "permissions") - .loadRelationCountAndMap("role.userCount", "role.users"); - - if (queryDto.q) { - queryBuilder.andWhere("role.name ILIKE :q", { q: `%${queryDto.q}%` }); - } - - queryBuilder.take(limit).skip(skip).orderBy("role.createdAt", "DESC"); - - const [roles, count] = await queryBuilder.getManyAndCount(); - - return { - roles, - count, - paginate: true, - }; - } - /************************************************************ */ - //TODO:query manager - async createRealUserData(createDto: CreateRealUserDto, userId: string) { - const user = await this.userRepository.findOneBy({ id: userId }); - if (!user) throw new BadRequestException(UserMessage.USER_NOT_FOUND); - - if (user.financialType && user.financialType === FinancialType.LEGAL) - throw new BadRequestException(UserMessage.FINANCIAL_TYPE_IS_LEGAL); - // - const existRealUser = await this.realUserRepository.findOne({ where: { user: { id: user.id } } }); - - if (existRealUser) throw new BadRequestException(UserMessage.REAL_DATA_EXIST); - - const { city } = await this.addressService.getCity(+createDto.cityId); - - const existNationalCode = await this.realUserRepository.findOne({ where: { nationalCode: createDto.nationalCode } }); - if (existNationalCode) throw new BadRequestException(UserMessage.NATIONAL_CODE_EXIST); - - const existPhone = await this.realUserRepository.findOne({ where: { phone: createDto.phone } }); - if (existPhone) throw new BadRequestException(UserMessage.PHONE_EXIST); - - const realUser = this.realUserRepository.create({ - ...createDto, - user, - address: { - fullAddress: createDto.address, - city, - postalCode: createDto.postalCode, - }, - }); - - await this.realUserRepository.save(realUser); - - user.financialType = FinancialType.REAL; - await this.userRepository.save(user); - - return { - message: CommonMessage.CREATED, - realUser, - }; - } - - // /************************************************************ */ - - //TODO:query manager - async createLegalUserData(createDto: CreateLegalUserDto, userId: string) { - const user = await this.userRepository.findOneBy({ id: userId }); - if (!user) throw new BadRequestException(UserMessage.USER_NOT_FOUND); - - if (user.financialType && user.financialType === FinancialType.REAL) throw new BadRequestException(UserMessage.FINANCIAL_TYPE_IS_REAL); - - const existingLegalUser = await this.legalUserRepository.findOne({ where: { user: { id: user.id } } }); - - if (existingLegalUser) throw new BadRequestException(UserMessage.LEGAL_DATA_EXIST); - - const existEconomicCode = await this.legalUserRepository.findOne({ where: { economicCode: createDto.economicCode } }); - if (existEconomicCode) throw new BadRequestException(UserMessage.ECONOMIC_CODE_EXIST); - - const existPhone = await this.legalUserRepository.findOne({ where: { phone: createDto.phone } }); - if (existPhone) throw new BadRequestException(UserMessage.PHONE_EXIST); - - const existRegistrationCode = await this.legalUserRepository.findOne({ where: { registrationCode: createDto.registrationCode } }); - if (existRegistrationCode) throw new BadRequestException(UserMessage.REGISTRATION_CODE_EXIST); - - const existNationalIdentity = await this.legalUserRepository.findOne({ where: { nationalIdentity: createDto.nationalIdentity } }); - if (existNationalIdentity) throw new BadRequestException(UserMessage.NATIONAL_IDENTITY_EXIST); - - const { city } = await this.addressService.getCity(+createDto.cityId); - - const legalUser = this.legalUserRepository.create({ - ...createDto, - user, - address: { - fullAddress: createDto.address, - city, - postalCode: createDto.postalCode, - }, - }); - - await this.legalUserRepository.save(legalUser); - - user.financialType = FinancialType.LEGAL; - await this.userRepository.save(user); - - return { - message: CommonMessage.CREATED, - legalUser, - }; - } - - /************************************************************ */ - - async getCustomersCount() { - const count = await this.userRepository.count({ where: { roles: { name: RoleEnum.USER } } }); - return count; - } /************************************************************ */ diff --git a/src/modules/users/repositories/users.repository.ts b/src/modules/users/repositories/users.repository.ts index 4c2f1dc..7e9101f 100755 --- a/src/modules/users/repositories/users.repository.ts +++ b/src/modules/users/repositories/users.repository.ts @@ -2,14 +2,18 @@ import { Injectable } from "@nestjs/common"; import { InjectRepository } from "@nestjs/typeorm"; import { Repository } from "typeorm"; +import { PaginationUtils } from "../../utils/providers/pagination.utils"; +import { SearchAdminQueryDto } from "../DTO/search-admins-query.dto"; +import { SearchCustomersDto } from "../DTO/search-customers.dto"; import { User } from "../entities/user.entity"; +import { RoleEnum } from "../enums/role.enum"; @Injectable() export class UserRepository extends Repository { constructor(@InjectRepository(User) userRepository: Repository) { super(userRepository.target, userRepository.manager, userRepository.queryRunner); } - + //************* */ async findOneWithEmail(email: string): Promise { return this.findOne({ where: { @@ -22,6 +26,7 @@ export class UserRepository extends Repository { }, }); } + //************* */ async findOneWithPhone(phone: string): Promise { return this.findOne({ @@ -35,6 +40,7 @@ export class UserRepository extends Repository { }, }); } + //************* */ async findOneWithUserName(userName: string): Promise { return this.findOne({ @@ -48,4 +54,87 @@ export class UserRepository extends Repository { }, }); } + //************* */ + + async findOneWithId(id: string): Promise { + return this.findOne({ + where: { + id, + }, + relations: { + roles: { + permissions: true, + }, + }, + }); + } + //************* */ + + // async findOneWithRefreshToken(refreshToken: string): Promise { + // return this.findOne({ + // where: { + // refreshToken, + // }, + // relations: { + // roles: { + // permissions: true, + // }, + // }, + // }); + // } + + //************* */ + + async getCustomer(queryDto: SearchCustomersDto) { + const { limit, skip } = PaginationUtils(queryDto); + + const queryBuilder = this.createQueryBuilder("user") + .leftJoin("user.roles", "role") + .where("role.name = :roleName", { roleName: RoleEnum.USER }) + .leftJoin("user.groups", "groups") + .addSelect(["groups.id", "groups.name"]) + .loadRelationCountAndMap("user.invoicesCount", "user.invoices") + .loadRelationCountAndMap("user.subscriptionsCount", "user.subscriptions") + .loadRelationCountAndMap("user.ticketsCount", "user.tickets"); + + if (queryDto.q) { + queryBuilder + .orWhere("user.firstName ILIKE :search", { search: `%${queryDto.q}%` }) + .orWhere("user.lastName ILIKE :search", { search: `%${queryDto.q}%` }) + .orWhere("user.userName ILIKE :search", { search: `%${queryDto.q}%` }); + } + + return await queryBuilder.skip(skip).take(limit).getManyAndCount(); + } + + //************* */ + async getAdmins(queryDto: SearchAdminQueryDto) { + const { limit, skip } = PaginationUtils(queryDto); + const queryBuilder = this.createQueryBuilder("user"); + + queryBuilder + .leftJoinAndSelect("user.roles", "role") + // .addSelect(["role.id", "role.name"]) + .where("role.isAdmin = :isAdmin", { isAdmin: true }) + .leftJoinAndSelect("user.groups", "groups") + .leftJoinAndSelect("role.permissions", "permissions"); + // .where("permissions.name IN (:...adminPermissions)", { + // adminPermissions: [PermissionEnum.ADMINS], + // }); + // .addSelect(["permissions.id", "permissions.name"]); + + if (queryDto.q) { + queryBuilder.andWhere("user.firstName ILIKE :q OR user.lastName ILIKE :q OR user.userName ILIKE :q", { + q: `%${queryDto.q}%`, + }); + } + + if (queryDto.permission) { + queryBuilder.andWhere("permissions.name = :permName", { permName: queryDto.permission }); + } + + queryBuilder.take(limit).skip(skip).orderBy("user.createdAt", "DESC"); + + return await queryBuilder.getManyAndCount(); + } } diff --git a/src/modules/users/users.controller.ts b/src/modules/users/users.controller.ts index cf1ccf1..b29587d 100755 --- a/src/modules/users/users.controller.ts +++ b/src/modules/users/users.controller.ts @@ -5,6 +5,7 @@ import { FastifyReply } from "fastify"; import { ChangeEmailDto } from "./DTO/change-email.dto"; import { CheckValidityDTO } from "./DTO/check-validity.dto"; import { CreateAdminDto } from "./DTO/create-admin.dto"; +import { CreateCustomerDto } from "./DTO/create-customer.dto"; import { CreateLegalUserDto } from "./DTO/create-legal-user.dto"; import { CreateRealUserDto } from "./DTO/create-real-user.dto"; import { CreateRoleDto } from "./DTO/create-role.dto"; @@ -15,6 +16,8 @@ import { UpdateProfileDto } from "./DTO/update-profile.dto"; import { CreateUserGroupDto } from "./DTO/user-group.dto"; import { EmailVerifyQueryDto } from "./DTO/verify-email-query.dto"; import { PermissionEnum } from "./enums/permission.enum"; +import { AdminsService } from "./providers/admins.service"; +import { CustomersService } from "./providers/customers.service"; import { UsersService } from "./providers/users.service"; import { AuthGuards } from "../../common/decorators/auth-guard.decorator"; import { PermissionsDec } from "../../common/decorators/permission.decorator"; @@ -26,9 +29,14 @@ import { VerifyOtpDto } from "../auth/DTO/verify-otp.dto"; @Controller("users") @ApiTags("users") export class UsersController { - constructor(private usersService: UsersService) {} + constructor( + private readonly usersService: UsersService, + private readonly adminsService: AdminsService, + private readonly customersService: CustomersService, + ) {} + + //----------------------- user ----------------------- - // @ApiOperation({ summary: "Get user profile" }) @AuthGuards() @Get("me") @@ -49,7 +57,21 @@ export class UsersController { getRealUserData(@UserDec("id") userId: string) { return this.usersService.getRealUserData(userId); } - // + + @ApiOperation({ summary: "Create real user data ==> user route" }) + @AuthGuards() + @Post("real") + createRealUser(@Body() createDto: CreateRealUserDto, @UserDec("id") userId: string) { + return this.customersService.createRealUserData(createDto, userId); + } + + @ApiOperation({ summary: "Create legal user data ==> user route" }) + @AuthGuards() + @Post("legal") + createLegalUser(@Body() createDto: CreateLegalUserDto, @UserDec("id") userId: string) { + return this.customersService.createLegalUserData(createDto, userId); + } + //----------------------- profile ----------------------- @ApiOperation({ summary: "Update user profile" }) @AuthGuards() @@ -114,32 +136,43 @@ export class UsersController { @AuthGuards() @PermissionsDec(PermissionEnum.ADMINS, PermissionEnum.CUSTOMERS) @Get() - Users() { + getUsers() { return this.usersService.findAllUsers(); } + //----------------------- customer ----------------------- - @ApiOperation({ summary: "get all customers ==> admin route" }) + @ApiOperation({ summary: "Create customer" }) + @AuthGuards() + @HttpCode(HttpStatus.CREATED) + @Post("customer") + createCustomer(@Body() createDto: CreateCustomerDto) { + return this.customersService.createCustomer(createDto); + } + + @ApiOperation({ summary: "get customers ==> admin route" }) @AuthGuards() @PermissionsDec(PermissionEnum.ADMINS, PermissionEnum.CUSTOMERS) @Get("customers") getCustomers(@Query() queryDto: SearchCustomersDto) { - return this.usersService.getCustomers(queryDto); + return this.customersService.getCustomers(queryDto); } - @ApiOperation({ summary: "get all customers ==> admin route" }) + @ApiOperation({ summary: "get customers by id ==> admin route" }) @AuthGuards() @PermissionsDec(PermissionEnum.ADMINS, PermissionEnum.CUSTOMERS) @Get("customers/:id") - customer(@Param() paramDto: ParamDto) { - return this.usersService.findOneCustomer(paramDto.id); + getCustomerById(@Param() paramDto: ParamDto) { + return this.customersService.getCustomerById(paramDto.id); } + //----------------------- admin ----------------------- + @ApiOperation({ summary: "get all admins ==> admin route" }) @AuthGuards() @PermissionsDec(PermissionEnum.ADMINS, PermissionEnum.CUSTOMERS) @Get("admins") getAdmins(@Query() queryDto: SearchAdminQueryDto) { - return this.usersService.getAdmins(queryDto); + return this.adminsService.getAdmins(queryDto); } @ApiOperation({ summary: "create admin ==> admin route" }) @@ -147,7 +180,7 @@ export class UsersController { @PermissionsDec(PermissionEnum.ADMINS, PermissionEnum.CUSTOMERS) @Post("admins") createAdmin(@Body() createDto: CreateAdminDto) { - return this.usersService.createAdmin(createDto); + return this.adminsService.createAdmin(createDto); } @ApiOperation({ summary: "get all permissions ==> admin route" }) @@ -155,7 +188,7 @@ export class UsersController { @PermissionsDec(PermissionEnum.ADMINS, PermissionEnum.CUSTOMERS) @Get("permissions") getPermissions() { - return this.usersService.getPermissions(); + return this.adminsService.getPermissions(); } @ApiOperation({ summary: "get all roles ==> admin route" }) @@ -163,7 +196,7 @@ export class UsersController { @PermissionsDec(PermissionEnum.ADMINS, PermissionEnum.CUSTOMERS) @Get("roles") getRoles(@Query() queryDto: SearchRolesQueryDto) { - return this.usersService.getRoles(queryDto); + return this.adminsService.getRoles(queryDto); } @ApiOperation({ summary: "create role ==> admin route" }) @@ -171,28 +204,6 @@ export class UsersController { @PermissionsDec(PermissionEnum.ADMINS, PermissionEnum.CUSTOMERS) @Post("roles") createRole(@Body() createDto: CreateRoleDto) { - return this.usersService.createRole(createDto); + return this.adminsService.createRole(createDto); } - - @ApiOperation({ summary: "Create real user data ==> user route" }) - @AuthGuards() - @Post("real") - createRealUser(@Body() createDto: CreateRealUserDto, @UserDec("id") userId: string) { - return this.usersService.createRealUserData(createDto, userId); - } - - @ApiOperation({ summary: "Create legal user data ==> user route" }) - @AuthGuards() - @Post("legal") - createLegalUser(@Body() createDto: CreateLegalUserDto, @UserDec("id") userId: string) { - return this.usersService.createLegalUserData(createDto, userId); - } - - // @ApiOperation({ summary: "Create customer" }) - // @AuthGuards() - // @HttpCode(HttpStatus.CREATED) - // @Post("customer") - // createCustomer(@Body() createDto: CreateCustomerDto) { - // return this.usersService.createCustomer(createDto); - // } } diff --git a/src/modules/users/users.module.ts b/src/modules/users/users.module.ts index e1c9759..281f777 100755 --- a/src/modules/users/users.module.ts +++ b/src/modules/users/users.module.ts @@ -22,6 +22,8 @@ import { RealUser } from "./entities/real-user.entity"; import { RealUserRepository } from "./repositories/real-user.repository"; import { UsersController } from "./users.controller"; import { UtilsModule } from "../utils/utils.module"; +import { AdminsService } from "./providers/admins.service"; +import { CustomersService } from "./providers/customers.service"; @Module({ imports: [ @@ -32,6 +34,8 @@ import { UtilsModule } from "../utils/utils.module"; ], providers: [ UsersService, + AdminsService, + CustomersService, UserRepository, RoleRepository, UserGroupRepository, @@ -43,6 +47,6 @@ import { UtilsModule } from "../utils/utils.module"; AddressService, ], controllers: [UsersController], - exports: [UsersService, UserRepository], + exports: [UsersService, UserRepository, CustomersService, AdminsService], }) export class UsersModule {}