From 763b6b49d90765716609e412ca3f1219f8e82fc9 Mon Sep 17 00:00:00 2001 From: mahyargdz Date: Mon, 17 Feb 2025 13:54:34 +0330 Subject: [PATCH] refactor: the whole role and permission flow --- database/seeders/admin.seeder.ts | 2 +- database/seeders/role.seeder.ts | 12 +- src/app.module.ts | 2 +- src/common/decorators/auth-guard.decorator.ts | 4 +- src/common/decorators/roles.decorator.ts | 6 +- src/common/enums/message.enum.ts | 19 ++ src/modules/ads/ads.controller.ts | 18 +- .../announcements/announcement.controller.ts | 29 +-- src/modules/auth/guards/role.guard.ts | 40 +-- src/modules/auth/interfaces/IToken-payload.ts | 4 +- src/modules/auth/providers/auth.service.ts | 28 ++- src/modules/auth/strategies/jwt.strategy.ts | 3 +- .../contact-us/contact-us.controller.ts | 8 +- .../criticisms/criticisms.controller.ts | 10 +- .../danak-services.controller.ts | 28 +-- .../providers/danak-services.service.ts | 6 +- src/modules/discounts/discounts.controller.ts | 6 - src/modules/invoices/invoices.controller.ts | 14 +- .../invoices/providers/invoices.service.ts | 5 +- src/modules/learnings/learning.controller.ts | 12 +- .../notifications/notifications.controller.ts | 5 - src/modules/payments/payments.controller.ts | 26 +- .../payments/providers/payments.service.ts | 5 +- .../subscriptions/subscriptions.controller.ts | 16 +- .../tickets/providers/tickets.service.ts | 15 +- src/modules/tickets/tickets.controller.ts | 34 ++- src/modules/users/DTO/create-admin.dto.ts | 29 +++ src/modules/users/DTO/create-role.dto.ts | 17 ++ .../users/DTO/search-admins-query.dto.ts | 17 ++ src/modules/users/DTO/search-admins.dto.ts | 12 - src/modules/users/DTO/search-roles.dto.ts | 11 + src/modules/users/entities/role.entity.ts | 15 +- .../users/entities/user-financial.entity.ts | 27 ++ src/modules/users/entities/user.entity.ts | 8 +- src/modules/users/enums/permission.enum.ts | 2 + src/modules/users/enums/role.enum.ts | 4 +- src/modules/users/providers/users.service.ts | 238 +++++++++--------- .../repositories/permissions.repository.ts | 12 + src/modules/users/users.controller.ts | 97 +++---- src/modules/users/users.module.ts | 9 +- .../utils/providers/checkRole.utils.ts | 7 - src/modules/wallets/wallets.controller.ts | 4 - 42 files changed, 479 insertions(+), 387 deletions(-) create mode 100644 src/modules/users/DTO/create-admin.dto.ts create mode 100644 src/modules/users/DTO/create-role.dto.ts create mode 100644 src/modules/users/DTO/search-admins-query.dto.ts delete mode 100644 src/modules/users/DTO/search-admins.dto.ts create mode 100644 src/modules/users/DTO/search-roles.dto.ts create mode 100644 src/modules/users/entities/user-financial.entity.ts create mode 100644 src/modules/users/repositories/permissions.repository.ts diff --git a/database/seeders/admin.seeder.ts b/database/seeders/admin.seeder.ts index 0da370f..b56313b 100644 --- a/database/seeders/admin.seeder.ts +++ b/database/seeders/admin.seeder.ts @@ -21,7 +21,7 @@ export const seedAdmin = async (dataSource: DataSource, logger: Logger) => { const roleRepo = dataSource.getRepository(Role); const userRepo = dataSource.getRepository(User); - const adminRole = await roleRepo.findOneBy({ name: RoleEnum.ADMIN }); + const adminRole = await roleRepo.findOneBy({ name: RoleEnum.SUPER_ADMIN }); if (!adminRole) throw new Error("Role not found"); const hashedPassword = await hash(defaultAdmin.password!, 10); diff --git a/database/seeders/role.seeder.ts b/database/seeders/role.seeder.ts index 2a47dc0..f731d12 100644 --- a/database/seeders/role.seeder.ts +++ b/database/seeders/role.seeder.ts @@ -12,17 +12,9 @@ const permissions = Object.values(PermissionEnum).map((perm) => ({ name: perm }) // Define roles with their default permissions const rolesWithPermissions: { name: RoleEnum; permissions: PermissionEnum[] }[] = [ { - name: RoleEnum.ADMIN, + name: RoleEnum.SUPER_ADMIN, permissions: Object.values(PermissionEnum), }, - { - name: RoleEnum.AGENT, - permissions: [PermissionEnum.CUSTOMERS, PermissionEnum.TICKETS, PermissionEnum.TRANSACTIONS], - }, - { - name: RoleEnum.DEVELOPER, - permissions: [PermissionEnum.SERVICES, PermissionEnum.LOGS, PermissionEnum.LEARNINGS], - }, { name: RoleEnum.USER, permissions: [], @@ -54,7 +46,7 @@ export const seedPermissionsAndRoles = async (dataSource: DataSource, logger: Lo let role = await roleRepo.findOne({ where: { name: roleData.name }, relations: ["permissions"] }); if (!role) { - role = roleRepo.create({ name: roleData.name, permissions: [] }); + role = roleRepo.create({ name: roleData.name, permissions: [], isAdmin: roleData.name === RoleEnum.SUPER_ADMIN }); } role.permissions = allPermissions.filter((perm) => roleData.permissions.includes(perm.name as PermissionEnum)); diff --git a/src/app.module.ts b/src/app.module.ts index 94b561e..8242ad9 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -41,7 +41,7 @@ import { WalletsModule } from "./modules/wallets/wallets.module"; ConfigModule.forRoot({ cache: true, isGlobal: true }), CacheModule.registerAsync(cacheConfig()), TypeOrmModule.forRootAsync(databaseConfigs()), - HttpModule.register({ global: true, timeout: 5000, headers: { "Content-Type": "application/json" } }), + HttpModule.register({ global: true, timeout: 10000, headers: { "Content-Type": "application/json" } }), FastifyMulterModule, AuthModule, UsersModule, diff --git a/src/common/decorators/auth-guard.decorator.ts b/src/common/decorators/auth-guard.decorator.ts index 2941ba0..a738282 100644 --- a/src/common/decorators/auth-guard.decorator.ts +++ b/src/common/decorators/auth-guard.decorator.ts @@ -3,8 +3,8 @@ import { ApiBearerAuth } from "@nestjs/swagger"; import { JwtAuthGuard } from "../../modules/auth/guards/auth.guard"; import { PermissionsGuard } from "../../modules/auth/guards/permission.guard"; -import { RoleGuard } from "../../modules/auth/guards/role.guard"; +// import { RoleGuard } from "../../modules/auth/guards/role.guard"; export function AuthGuards() { - return applyDecorators(UseGuards(JwtAuthGuard, RoleGuard, PermissionsGuard), ApiBearerAuth("authorization")); + return applyDecorators(UseGuards(JwtAuthGuard, PermissionsGuard), ApiBearerAuth("authorization")); } diff --git a/src/common/decorators/roles.decorator.ts b/src/common/decorators/roles.decorator.ts index 3d51da8..3e1f224 100644 --- a/src/common/decorators/roles.decorator.ts +++ b/src/common/decorators/roles.decorator.ts @@ -1,4 +1,4 @@ -import { SetMetadata } from "@nestjs/common"; +// import { SetMetadata } from "@nestjs/common"; -export const ROLES_KEY = "roles"; -export const Roles = (...roles: string[]) => SetMetadata(ROLES_KEY, roles); +// export const ROLES_KEY = "roles"; +// export const Roles = (...roles: string[]) => SetMetadata(ROLES_KEY, roles); diff --git a/src/common/enums/message.enum.ts b/src/common/enums/message.enum.ts index 1d895d1..4dc9d4b 100644 --- a/src/common/enums/message.enum.ts +++ b/src/common/enums/message.enum.ts @@ -65,6 +65,7 @@ export const enum UserMessage { USER_GROUP_NOT_FOUND = "گروه کاربری یافت نشد", USER_GROUP_CREATED = "گروه کاربری با موفقیت ایجاد شد", USER_ID_SHOULD_BE_A_UUID = "شناسه کاربر باید یک UUID باشد", + NATIONAL_CODE_EXIST = "کد ملی قبلا ثبت شده است", } export const enum CommonMessage { @@ -384,6 +385,23 @@ export const enum FinancialMessage { FINANCIAL_INFO_NOT_FOUND = "دیتا اطلاعات مالی یافت نشد", SEARCH_QUERY_MUST_BE_A_STRING = "رشته جستجو باید یک رشته باشد", } + +export const enum AdminMessage { + ROLE_REQUIRED = "نقش مورد نیاز است", + ROLE_ID_SHOULD_BE_UUID = "شناسه نقش باید یک UUID معتبر باشد", + PERMISSIONS_REQUIRED = "دسترسی‌ها مورد نیاز است", + PERMISSIONS_ID_SHOULD_BE_UUID = "شناسه دسترسی باید یک UUID معتبر باشد", + PROFILE_PIC_URL = "آدرس تصویر پروفایل باید یک URL معتبر باشد", + PROFILE_PIC_REQUIRED = "آدرس تصویر پروفایل مورد نیاز است", + PERMISSIONS_NOT_FOUND = "دسترسی‌های مورد نظر یافت نشد", + ADMIN_CREATED = "مدیر با موفقیت ایجاد شد", + PASSWORD_NOT_MATCH = "رمز عبور و تکرار آن باید یکسان باشد", + ROLE_NAME_REQUIRED = "نام نقش مورد نیاز است", + ROLE_NAME_STRING = "نام نقش باید یک رشته باشد", + ROLE_NAME_LENGTH = "نام نقش باید بین ۳ تا ۱۵۰ کاراکتر باشد", + ROLE_EXIST = "نقش با این نام قبلا ثبت شده است", + ROLE_CREATED = "نقش با موفقیت ایجاد شد", +} export const enum AdsMessage { TITLE_REQUIRED = "عنوان الزامی است", TITLE_STRING = "عنوان باید یک رشته باشد", @@ -413,6 +431,7 @@ export const enum AdsMessage { ADS_DELETED = "تبلیغ با موفقیت حذف شد", ADS_ACTIVATED = "تبلیغ با موفقیت فعال شد", ADS_DEACTIVATED = "تبلیغ با موفقیت غیرفعال شد", + ROLE_NOT_FOUND = "نقشی با این شناسه یافت نشد", } export const enum ProvinceMessage { diff --git a/src/modules/ads/ads.controller.ts b/src/modules/ads/ads.controller.ts index 52c4545..16e1cc6 100644 --- a/src/modules/ads/ads.controller.ts +++ b/src/modules/ads/ads.controller.ts @@ -6,9 +6,9 @@ import { CreateAdsDto } from "./DTO/create-ads.dto"; import { UpdateAdsDto } from "./DTO/update-ads.dto"; import { AdsService } from "./providers/ads.service"; import { AuthGuards } from "../../common/decorators/auth-guard.decorator"; -import { Roles } from "../../common/decorators/roles.decorator"; +import { PermissionsDec } from "../../common/decorators/permission.decorator"; import { ParamDto } from "../../common/DTO/param.dto"; -import { RoleEnum } from "../users/enums/role.enum"; +import { PermissionEnum } from "../users/enums/permission.enum"; @Controller("advertise") @ApiTags("Advertisements") @@ -17,56 +17,54 @@ export class AdsController { constructor(private readonly adsService: AdsService) {} @ApiOperation({ summary: "Create new ads" }) - @Roles(RoleEnum.ADMIN) @Post() + @PermissionsDec(PermissionEnum.ADVERTISEMENTS) createAds(@Body() createDto: CreateAdsDto) { return this.adsService.createAds(createDto); } @ApiOperation({ summary: "Get all ads" }) - @Roles(RoleEnum.ADMIN) + @PermissionsDec(PermissionEnum.ADVERTISEMENTS) @Get("list") getAdsList(@Query() queryDto: AdsSearchQueryDto) { return this.adsService.getAdsList(queryDto); } @ApiOperation({ summary: "Get ads by id" }) - @Roles(RoleEnum.ADMIN) + @PermissionsDec(PermissionEnum.ADVERTISEMENTS) @Get(":id") getAdsById(@Param() paramDto: ParamDto) { return this.adsService.getAdsById(paramDto.id); } @ApiOperation({ summary: "Update ads by id" }) - @Roles(RoleEnum.ADMIN) + @PermissionsDec(PermissionEnum.ADVERTISEMENTS) @Patch(":id") updateAdsById(@Param() paramDto: ParamDto, @Body() updateDto: UpdateAdsDto) { return this.adsService.updateAdsById(paramDto.id, updateDto); } @ApiOperation({ summary: "Delete ads by id" }) - @Roles(RoleEnum.ADMIN) + @PermissionsDec(PermissionEnum.ADVERTISEMENTS) @Delete(":id") deleteAdsById(@Param() paramDto: ParamDto) { return this.adsService.deleteAdsById(paramDto.id); } @ApiOperation({ summary: "Toggle ads status" }) - @Roles(RoleEnum.ADMIN) + @PermissionsDec(PermissionEnum.ADVERTISEMENTS) @Patch("toggle-status/:id") toggleAdsStatus(@Param() paramDto: ParamDto) { return this.adsService.toggleAdsStatus(paramDto.id); } @ApiOperation({ summary: "Get ads by location ==> both user and admin" }) - @Roles(RoleEnum.ADMIN, RoleEnum.USER) @Get("by-location") getAdsByLocation(@Query() queryDto: GetAdsByLocationQueryDto) { return this.adsService.getAdsByLocation(queryDto); } @ApiOperation({ summary: "Get all ads grouped by location ==> both user and admin" }) - @Roles(RoleEnum.ADMIN, RoleEnum.USER) @Get("grouped-by-location") getAdsGroupedByLocation() { return this.adsService.getAdsGroupedByLocation(); diff --git a/src/modules/announcements/announcement.controller.ts b/src/modules/announcements/announcement.controller.ts index 3c67ca2..beb2731 100644 --- a/src/modules/announcements/announcement.controller.ts +++ b/src/modules/announcements/announcement.controller.ts @@ -4,12 +4,12 @@ import { ApiProperty, ApiTags } from "@nestjs/swagger"; import { AnnouncementService } from "./announcement.service"; import { CreateAnnouncementDto } from "./DTO/create-announcement.dto"; import { AuthGuards } from "../../common/decorators/auth-guard.decorator"; -import { Roles } from "../../common/decorators/roles.decorator"; +import { PermissionsDec } from "../../common/decorators/permission.decorator"; import { UserDec } from "../../common/decorators/user.decorator"; import { ParamDto } from "../../common/DTO/param.dto"; import { SearchCriticismQueryDto } from "../criticisms/DTO/search-criticism-query.dto"; import { User } from "../users/entities/user.entity"; -import { RoleEnum } from "../users/enums/role.enum"; +import { PermissionEnum } from "../users/enums/permission.enum"; @ApiTags("Announcements") @Controller("announcements") @@ -17,27 +17,21 @@ export class AnnouncementController { constructor(private readonly announcementService: AnnouncementService) {} @AuthGuards() - @Roles(RoleEnum.ADMIN) - @ApiProperty({ - description: "Create a new announcement ===> login as admin", - }) + @ApiProperty({ description: "Create a new announcement ===> login as admin" }) @Post() async create(@Body() createAnnouncementDto: CreateAnnouncementDto) { return await this.announcementService.createAnnouncement(createAnnouncementDto); } @AuthGuards() - @Roles(RoleEnum.ADMIN, RoleEnum.USER) - @ApiProperty({ - description: "Get all announcements ===> login as admin", - }) + @ApiProperty({ description: "Get all announcements ===> login as admin" }) + @PermissionsDec(PermissionEnum.ANNOUNCEMENTS) @Get() async getAllAnnouncements(@Query() queryDto: SearchCriticismQueryDto, @UserDec() user: User) { return await this.announcementService.getAllAnnouncements(queryDto, user.id); } @AuthGuards() - @Roles(RoleEnum.ADMIN, RoleEnum.USER) @ApiProperty({ description: "Get all announcements ===> login as user", }) @@ -47,28 +41,21 @@ export class AnnouncementController { } @AuthGuards() - @Roles(RoleEnum.ADMIN, RoleEnum.USER) - @ApiProperty({ - description: "Get one announcements with id", - }) + @ApiProperty({ description: "Get one announcements with id" }) @Get(":id") getAnnouncement(@Param() paramDto: ParamDto, @UserDec() user: User) { return this.announcementService.getOneAnnouncement(paramDto.id, user.id); } @AuthGuards() - @ApiProperty({ - description: "Get all public announcements", - }) + @ApiProperty({ description: "Get all public announcements" }) @Get("public") async getPublicAnnouncements() { return await this.announcementService.getPublicAnnouncements(); } @AuthGuards() - @ApiProperty({ - description: "Get all public announcements", - }) + @ApiProperty({ description: "Get all public announcements" }) @Get("/service/:serviceId") async getAnnouncementsByService(@Query() queryDto: SearchCriticismQueryDto, @Param("serviceId") serviceId: string) { return await this.announcementService.getAnnouncementByService(queryDto, serviceId); diff --git a/src/modules/auth/guards/role.guard.ts b/src/modules/auth/guards/role.guard.ts index 692d3fd..e38d587 100644 --- a/src/modules/auth/guards/role.guard.ts +++ b/src/modules/auth/guards/role.guard.ts @@ -1,27 +1,27 @@ -import { CanActivate, ExecutionContext, ForbiddenException, Injectable } from "@nestjs/common"; -import { Reflector } from "@nestjs/core"; -import { FastifyRequest } from "fastify"; +// import { CanActivate, ExecutionContext, ForbiddenException, Injectable } from "@nestjs/common"; +// import { Reflector } from "@nestjs/core"; +// import { FastifyRequest } from "fastify"; -import { ROLES_KEY } from "../../../common/decorators/roles.decorator"; -import { AuthMessage } from "../../../common/enums/message.enum"; -import { RoleEnum } from "../../users/enums/role.enum"; +// import { ROLES_KEY } from "../../../common/decorators/roles.decorator"; +// import { AuthMessage } from "../../../common/enums/message.enum"; +// import { RoleEnum } from "../../users/enums/role.enum"; -@Injectable() -export class RoleGuard implements CanActivate { - constructor(private reflector: Reflector) {} +// @Injectable() +// export class RoleGuard implements CanActivate { +// constructor(private reflector: Reflector) {} - canActivate(context: ExecutionContext): boolean { - const requiredRole = this.reflector.getAllAndOverride(ROLES_KEY, [context.getHandler(), context.getClass()]); - if (!requiredRole) return true; +// canActivate(context: ExecutionContext): boolean { +// const requiredRole = this.reflector.getAllAndOverride(ROLES_KEY, [context.getHandler(), context.getClass()]); +// if (!requiredRole) return true; - const req = context.switchToHttp().getRequest(); - const user = req.user; +// const req = context.switchToHttp().getRequest(); +// const user = req.user; - if (!user) throw new ForbiddenException(AuthMessage.UNAUTHORIZED_ACCESS); +// if (!user) throw new ForbiddenException(AuthMessage.UNAUTHORIZED_ACCESS); - const hasRequiredRole = requiredRole.some((role) => user.roles.includes(role)); - if (!hasRequiredRole) throw new ForbiddenException(AuthMessage.UNAUTHORIZED_ACCESS); +// const hasRequiredRole = requiredRole.some((role) => user.roles.includes(role)); +// if (!hasRequiredRole) throw new ForbiddenException(AuthMessage.UNAUTHORIZED_ACCESS); - return true; - } -} +// return true; +// } +// } diff --git a/src/modules/auth/interfaces/IToken-payload.ts b/src/modules/auth/interfaces/IToken-payload.ts index f694a0e..222c2b5 100644 --- a/src/modules/auth/interfaces/IToken-payload.ts +++ b/src/modules/auth/interfaces/IToken-payload.ts @@ -1,8 +1,8 @@ import { PermissionEnum } from "../../users/enums/permission.enum"; -import { RoleEnum } from "../../users/enums/role.enum"; +// import { RoleEnum } from "../../users/enums/role.enum"; export interface ITokenPayload { id: string; - roles: RoleEnum[]; + isAdmin: boolean; permissions: PermissionEnum[]; } diff --git a/src/modules/auth/providers/auth.service.ts b/src/modules/auth/providers/auth.service.ts index 5294ee2..6b30236 100644 --- a/src/modules/auth/providers/auth.service.ts +++ b/src/modules/auth/providers/auth.service.ts @@ -4,10 +4,9 @@ import { DataSource } from "typeorm"; import { TokensService } from "./tokens.service"; import { AuthMessage, UserMessage } from "../../../common/enums/message.enum"; import { NotificationsService } from "../../notifications/providers/notifications.service"; +import { Role } from "../../users/entities/role.entity"; import { User } from "../../users/entities/user.entity"; -import { RoleEnum } from "../../users/enums/role.enum"; import { UsersService } from "../../users/providers/users.service"; -import { checkUserRole } from "../../utils/providers/checkRole.utils"; import { OTPService } from "../../utils/providers/otp.service"; import { PasswordService } from "../../utils/providers/password.service"; import { SmsService } from "../../utils/providers/sms.service"; @@ -109,8 +108,8 @@ export class AuthService { const user = await this.checkUserLoginCredentialWithEmail(email, password); - const hasAccess = checkUserRole(user.roles, RoleEnum.ADMIN); - if (!hasAccess) throw new BadRequestException(AuthMessage.NOT_ADMIN); + const isAdmin = this.checkUserIsAdmin(user.roles); + if (!isAdmin) throw new BadRequestException(AuthMessage.NOT_ADMIN); const tokens = this.generateAccessAndRefreshToken(user); @@ -139,8 +138,9 @@ export class AuthService { //check the if the method call is from admin or not - const hasAccess = checkUserRole(user.roles, RoleEnum.ADMIN); - if (isAdmin && !hasAccess) throw new BadRequestException(AuthMessage.NOT_ADMIN); + const isUserAdmin = this.checkUserIsAdmin(user.roles); + + if (isAdmin && !isUserAdmin) throw new BadRequestException(AuthMessage.NOT_ADMIN); const existCode = await this.otpService.checkExistOtp(phone, "LOGIN"); if (existCode) { @@ -186,9 +186,9 @@ export class AuthService { const user = await this.checkUserLoginCredentialWithPhone(phone, code); - const hasAccess = checkUserRole(user.roles, RoleEnum.ADMIN); + const isUserAdmin = this.checkUserIsAdmin(user.roles); - if (!hasAccess) throw new BadRequestException(AuthMessage.NOT_ADMIN); + if (!isUserAdmin) throw new BadRequestException(AuthMessage.NOT_ADMIN); const tokens = this.generateAccessAndRefreshToken(user); @@ -245,8 +245,18 @@ export class AuthService { private generateAccessAndRefreshToken(user: User) { return this.tokensService.generateAccessAndRefreshToken({ id: user.id, - roles: user.roles.map((r) => r.name), + isAdmin: user.roles.some((r) => r.isAdmin), permissions: user.roles.flatMap((r) => (r?.permissions?.length ? r.permissions.map((p) => p.name) : [])), }); } + //****************** */ + //****************** */ + // private checkUserPerm(user: User, perm: PermissionEnum) { + // return user.roles.some((role) => role?.permissions?.some((p) => p.name === perm)); + // } + //****************** */ + //****************** */ + private checkUserIsAdmin(roles: Role[]) { + return roles.some((role) => role.isAdmin); + } } diff --git a/src/modules/auth/strategies/jwt.strategy.ts b/src/modules/auth/strategies/jwt.strategy.ts index d6b6e6b..db69562 100644 --- a/src/modules/auth/strategies/jwt.strategy.ts +++ b/src/modules/auth/strategies/jwt.strategy.ts @@ -17,6 +17,7 @@ export class JwtStrategy extends PassportStrategy(Strategy, JWT_STRATEGY_NAME) { } async validate(payload: ITokenPayload) { - return { id: payload.id, roles: payload.roles, permissions: payload.permissions }; + return { id: payload.id, permissions: payload.permissions }; + // return { id: payload.id, roles: payload.roles, permissions: payload.permissions }; } } diff --git a/src/modules/contact-us/contact-us.controller.ts b/src/modules/contact-us/contact-us.controller.ts index 6de1e5f..b9788b0 100644 --- a/src/modules/contact-us/contact-us.controller.ts +++ b/src/modules/contact-us/contact-us.controller.ts @@ -5,9 +5,9 @@ import { CreateContactUsDto } from "./DTO/create-contact-us.dto"; import { SearchContactUsQueryDto } from "./DTO/search-contact-us-query.dto"; import { ContactUsService } from "./providers/contact-us.service"; import { AuthGuards } from "../../common/decorators/auth-guard.decorator"; -import { Roles } from "../../common/decorators/roles.decorator"; +import { PermissionsDec } from "../../common/decorators/permission.decorator"; import { ParamDto } from "../../common/DTO/param.dto"; -import { RoleEnum } from "../users/enums/role.enum"; +import { PermissionEnum } from "../users/enums/permission.enum"; @Controller("contact-us") @ApiTags("ContactUs") @@ -23,7 +23,7 @@ export class ContactUsController { //******************** */ @AuthGuards() - @Roles(RoleEnum.ADMIN) + @PermissionsDec(PermissionEnum.CONTACTS_US) @ApiOperation({ summary: "Get all contact us ===> login as admin" }) @Get() getAllContactUs(@Query() queryDto: SearchContactUsQueryDto) { @@ -33,7 +33,7 @@ export class ContactUsController { //******************** */ @AuthGuards() - @Roles(RoleEnum.ADMIN) + @PermissionsDec(PermissionEnum.CONTACTS_US) @ApiOperation({ summary: "Get one contact us with id ===> login as admin" }) @Get(":id") getOneContactUs(@Param() paramDto: ParamDto) { diff --git a/src/modules/criticisms/criticisms.controller.ts b/src/modules/criticisms/criticisms.controller.ts index 61991bf..59cd319 100644 --- a/src/modules/criticisms/criticisms.controller.ts +++ b/src/modules/criticisms/criticisms.controller.ts @@ -5,11 +5,11 @@ import { CreateCriticismDto } from "./DTO/create-criticism.dto"; import { SearchCriticismQueryDto } from "./DTO/search-criticism-query.dto"; import { CriticismsService } from "./providers/criticisms.service"; import { AuthGuards } from "../../common/decorators/auth-guard.decorator"; -import { Roles } from "../../common/decorators/roles.decorator"; +import { PermissionsDec } from "../../common/decorators/permission.decorator"; import { UserDec } from "../../common/decorators/user.decorator"; import { ParamDto } from "../../common/DTO/param.dto"; import { User } from "../users/entities/user.entity"; -import { RoleEnum } from "../users/enums/role.enum"; +import { PermissionEnum } from "../users/enums/permission.enum"; @Controller("criticisms") @ApiTags("Criticism") @@ -24,7 +24,7 @@ export class CriticismController { } @AuthGuards() - @Roles(RoleEnum.ADMIN) + @PermissionsDec(PermissionEnum.CRITICISMS) @ApiOperation({ summary: "Get all Criticism ===> login as admin" }) @Get() findAll(@Query() queryDto: SearchCriticismQueryDto) { @@ -32,7 +32,7 @@ export class CriticismController { } @AuthGuards() - @Roles(RoleEnum.ADMIN) + @PermissionsDec(PermissionEnum.CRITICISMS) @ApiOperation({ summary: "Get one Criticism with id ===> login as admin" }) @Get(":id") fineOne(@Param() paramDto: ParamDto) { @@ -40,7 +40,7 @@ export class CriticismController { } @AuthGuards() - @Roles(RoleEnum.ADMIN) + @PermissionsDec(PermissionEnum.CRITICISMS) @ApiOperation({ summary: "Delete one Criticism with id ===> login as admin" }) @Delete(":id") delete(@Param() paramDto: ParamDto) { diff --git a/src/modules/danak-services/danak-services.controller.ts b/src/modules/danak-services/danak-services.controller.ts index 7557df6..5c7868d 100644 --- a/src/modules/danak-services/danak-services.controller.ts +++ b/src/modules/danak-services/danak-services.controller.ts @@ -8,10 +8,10 @@ import { DanakServicesSearchQueryDto } from "./DTO/danak-services-search-query.d import { DanakServicesService } from "./providers/danak-services.service"; import { AuthGuards } from "../../common/decorators/auth-guard.decorator"; import { Pagination } from "../../common/decorators/pagination.decorator"; -import { Roles } from "../../common/decorators/roles.decorator"; +import { PermissionsDec } from "../../common/decorators/permission.decorator"; import { UserDec } from "../../common/decorators/user.decorator"; import { ParamDto } from "../../common/DTO/param.dto"; -import { RoleEnum } from "../users/enums/role.enum"; +import { PermissionEnum } from "../users/enums/permission.enum"; @Controller("danak-services") @ApiTags("Danak-Services") @@ -20,7 +20,7 @@ export class DanakServicesController { //------------------------ service categories ------------------------ @AuthGuards() - @Roles(RoleEnum.ADMIN) + @PermissionsDec(PermissionEnum.SERVICES) @ApiOperation({ summary: "Create a new service category => admin route" }) @Post("categories") createCategory(@Body() createDto: CreateCategoryDto) { @@ -28,7 +28,7 @@ export class DanakServicesController { } @AuthGuards() - @Roles(RoleEnum.ADMIN) + @PermissionsDec(PermissionEnum.SERVICES) @ApiOperation({ summary: "Get all service categories => admin route" }) @Get("categories") getCategories(@Query() queryDto: CategorySearchQueryDto) { @@ -36,7 +36,7 @@ export class DanakServicesController { } @AuthGuards() - @Roles(RoleEnum.ADMIN) + @PermissionsDec(PermissionEnum.SERVICES) @ApiOperation({ summary: "Get all service categories => admin route" }) @Pagination() @Get("categories/list") @@ -45,7 +45,6 @@ export class DanakServicesController { } @AuthGuards() - @Roles(RoleEnum.USER, RoleEnum.ADMIN) @ApiOperation({ summary: "Get all service categories user side" }) @Get("categories/public") getCategoriesUserSide() { @@ -53,7 +52,6 @@ export class DanakServicesController { } @AuthGuards() - @Roles(RoleEnum.USER, RoleEnum.ADMIN) @ApiOperation({ summary: "get category services with category id" }) @Get("categories/:id/services") getCategoryServices(@Param() paramDto: ParamDto) { @@ -61,7 +59,7 @@ export class DanakServicesController { } @AuthGuards() - @Roles(RoleEnum.ADMIN) + @PermissionsDec(PermissionEnum.SERVICES) @ApiOperation({ summary: "toggle status of categories => admin route" }) @HttpCode(HttpStatus.OK) @Post("categories/toggle-status/:id") @@ -70,7 +68,7 @@ export class DanakServicesController { } //-------------------- service management -------------------------- @AuthGuards() - @Roles(RoleEnum.ADMIN) + @PermissionsDec(PermissionEnum.SERVICES) @ApiOperation({ summary: "create new danak services => admin route" }) @Post() createService(@Body() createDto: CreateServiceDto) { @@ -78,15 +76,15 @@ export class DanakServicesController { } @AuthGuards() + @PermissionsDec(PermissionEnum.SERVICES) @ApiOperation({ summary: "get all danak services ==> admin route" }) - @Roles(RoleEnum.ADMIN) @Get() getServices(@Query() queryDto: DanakServicesSearchQueryDto) { return this.danakServicesService.getServicesList(queryDto); } @AuthGuards() - @Roles(RoleEnum.ADMIN) + @PermissionsDec(PermissionEnum.SERVICES) @ApiOperation({ summary: "toggle status of danak service => admin route" }) @HttpCode(HttpStatus.OK) @Post("toggle-status/:id") @@ -96,7 +94,6 @@ export class DanakServicesController { @ApiOperation({ summary: "get services for user side" }) @AuthGuards() - @Roles(RoleEnum.USER) @Get("suggested") getDanakSuggestServices() { return this.danakServicesService.getDanakSuggestServices(); @@ -104,14 +101,13 @@ export class DanakServicesController { @ApiOperation({ summary: "get danak service by id" }) @AuthGuards() - @Roles(RoleEnum.USER, RoleEnum.ADMIN) @Get(":id") - getDanakServiceById(@Param() paramDto: ParamDto, @UserDec("roles") userRole: RoleEnum[]) { - return this.danakServicesService.getDanakServiceByIdWithSubs(paramDto.id, userRole); + getDanakServiceById(@Param() paramDto: ParamDto, @UserDec("isAdmin") isAdmin: boolean) { + return this.danakServicesService.getDanakServiceByIdWithSubs(paramDto.id, isAdmin); } // @AuthGuards() - // @Roles(RoleEnum.USER) + // // @ApiOperation({ summary: "get all user purchased danak services" }) // @Get("user-services") // getUserServices(@UserDec() user: User) { diff --git a/src/modules/danak-services/providers/danak-services.service.ts b/src/modules/danak-services/providers/danak-services.service.ts index be78fd5..8a1f809 100644 --- a/src/modules/danak-services/providers/danak-services.service.ts +++ b/src/modules/danak-services/providers/danak-services.service.ts @@ -3,7 +3,6 @@ import { FindOptionsWhere, In, IsNull } from "typeorm"; import { ParamDto } from "../../../common/DTO/param.dto"; import { CategoryMessage, CommonMessage, ServiceMessage } from "../../../common/enums/message.enum"; -import { RoleEnum } from "../../users/enums/role.enum"; import { PaginationUtils } from "../../utils/providers/pagination.utils"; import { CategoryListSearchQueryDto, CategorySearchQueryDto } from "../DTO/category-search-query.dto"; import { CreateCategoryDto } from "../DTO/create-category.dto"; @@ -173,10 +172,9 @@ export class DanakServicesService { } /******************************************** */ - async getDanakServiceByIdWithSubs(serviceId: string, roles: RoleEnum[]) { - const hasAccess = roles.includes(RoleEnum.ADMIN); + async getDanakServiceByIdWithSubs(serviceId: string, isAdmin: boolean) { const danakService = await this.danakServicesRepository.findOne({ - where: { id: serviceId, ...(hasAccess && { isActive: true, subscriptionPlans: { isActive: true } }) }, + where: { id: serviceId, ...(isAdmin && { isActive: true, subscriptionPlans: { isActive: true } }) }, relations: { images: true, subscriptionPlans: true }, }); if (!danakService) throw new BadRequestException(ServiceMessage.SERVICE_NOT_FOUND_BY_ID); diff --git a/src/modules/discounts/discounts.controller.ts b/src/modules/discounts/discounts.controller.ts index fbf6e7b..9fdeee6 100644 --- a/src/modules/discounts/discounts.controller.ts +++ b/src/modules/discounts/discounts.controller.ts @@ -5,10 +5,8 @@ import { CreateDiscountDto } from "./DTO/create-discount.dto"; import { SearchDiscountsDto } from "./DTO/discount-search-query.dto"; import { DiscountService } from "./providers/discounts.service"; import { AuthGuards } from "../../common/decorators/auth-guard.decorator"; -import { Roles } from "../../common/decorators/roles.decorator"; import { UserDec } from "../../common/decorators/user.decorator"; import { ParamDto } from "../../common/DTO/param.dto"; -import { RoleEnum } from "../users/enums/role.enum"; @ApiTags("discounts") @AuthGuards() @@ -19,7 +17,6 @@ export class DiscountController { //************************************ */ @ApiOperation({ summary: "Create a new discount" }) - @Roles(RoleEnum.ADMIN) @Post() async create(@Body() createDiscountDto: CreateDiscountDto) { return this.discountService.create(createDiscountDto); @@ -28,7 +25,6 @@ export class DiscountController { //************************************ */ @ApiOperation({ summary: "Retrieve all discounts" }) - @Roles(RoleEnum.ADMIN) @Get() async findAll(@Query() queryDto: SearchDiscountsDto) { return this.discountService.findAll(queryDto); @@ -37,7 +33,6 @@ export class DiscountController { //************************************ */ @ApiOperation({ summary: "Retrieve a discount by ID" }) - @Roles(RoleEnum.ADMIN) @Get(":id") async findOne(@Param() paramDto: ParamDto) { return this.discountService.findOne(paramDto.id); @@ -54,7 +49,6 @@ export class DiscountController { //************************************ */ @ApiOperation({ summary: "Toggle active/deactive discount" }) - @Roles(RoleEnum.ADMIN) @Patch(":id/toggle") async toggleActive(@Param() paramDto: ParamDto) { return this.discountService.toggleActive(paramDto.id); diff --git a/src/modules/invoices/invoices.controller.ts b/src/modules/invoices/invoices.controller.ts index 2a2a600..3e76be0 100644 --- a/src/modules/invoices/invoices.controller.ts +++ b/src/modules/invoices/invoices.controller.ts @@ -7,18 +7,18 @@ import { InvoicesSearchQueryDto, UserInvoicesSearchQueryDto } from "./DTO/invoic import { InvoicesService } from "./providers/invoices.service"; import { AuthGuards } from "../../common/decorators/auth-guard.decorator"; import { Pagination } from "../../common/decorators/pagination.decorator"; -import { Roles } from "../../common/decorators/roles.decorator"; +import { PermissionsDec } from "../../common/decorators/permission.decorator"; import { UserDec } from "../../common/decorators/user.decorator"; import { ParamDto } from "../../common/DTO/param.dto"; -import { RoleEnum } from "../users/enums/role.enum"; +import { PermissionEnum } from "../users/enums/permission.enum"; @Controller("invoices") export class InvoicesController { constructor(private readonly invoiceService: InvoicesService) {} @ApiOperation({ summary: "create an invoice ==> admin route" }) + @PermissionsDec(PermissionEnum.INVOICES) @AuthGuards() - @Roles(RoleEnum.ADMIN) @Post() createInvoice(@Body() createDto: CreateInvoiceDto) { return this.invoiceService.createInvoiceAdmin(createDto); @@ -26,8 +26,8 @@ export class InvoicesController { @ApiOperation({ summary: "get all invoices ==> admin route" }) @AuthGuards() + @PermissionsDec(PermissionEnum.INVOICES) @Pagination() - @Roles(RoleEnum.ADMIN) @Get() getInvoices(@Query() queryDto: InvoicesSearchQueryDto) { return this.invoiceService.getInvoices(queryDto); @@ -35,7 +35,6 @@ export class InvoicesController { @ApiOperation({ summary: "get all user invoices" }) @AuthGuards() - @Roles(RoleEnum.USER) @Pagination() @Get("user") getUserInvoices(@Query() queryDto: UserInvoicesSearchQueryDto, @UserDec("id") userId: string) { @@ -44,10 +43,9 @@ export class InvoicesController { @ApiOperation({ summary: "get single invoice by Id " }) @AuthGuards() - @Roles(RoleEnum.ADMIN, RoleEnum.USER) @Get(":id") - getInvoiceById(@Param() paramDto: ParamDto, @UserDec("roles") roles: RoleEnum[], @UserDec("id") userId: string) { - return this.invoiceService.getInvoiceById(paramDto.id, roles, userId); + getInvoiceById(@Param() paramDto: ParamDto, @UserDec("isAdmin") isAdmin: boolean, @UserDec("id") userId: string) { + return this.invoiceService.getInvoiceById(paramDto.id, isAdmin, userId); } @Post(":id/apply-discount") diff --git a/src/modules/invoices/providers/invoices.service.ts b/src/modules/invoices/providers/invoices.service.ts index 841be16..6b476c9 100644 --- a/src/modules/invoices/providers/invoices.service.ts +++ b/src/modules/invoices/providers/invoices.service.ts @@ -6,7 +6,6 @@ import { QueryRunner } from "typeorm"; import { InvoiceMessage } from "../../../common/enums/message.enum"; import { DiscountRepository } from "../../discounts/repositories/discount.repository"; import { SubscriptionPlan } from "../../subscriptions/entities/subscription.entity"; -import { RoleEnum } from "../../users/enums/role.enum"; import { PaginationUtils } from "../../utils/providers/pagination.utils"; import { ApplyDiscountDto } from "../DTO/apply-discount-invoice.dto"; import { CreateInvoiceDto } from "../DTO/create-invoice.dto"; @@ -126,10 +125,10 @@ export class InvoicesService { } //*********************************** */ - async getInvoiceById(invoiceId: string, roles: RoleEnum[], userId: string) { + async getInvoiceById(invoiceId: string, isAdmin: boolean, userId: string) { let invoice: Invoice | null; - if (roles.includes(RoleEnum.ADMIN)) { + if (isAdmin) { invoice = await this.invoiceRepository.findOne({ where: { id: invoiceId }, relations: { items: { subscriptionPlan: true }, user: true }, diff --git a/src/modules/learnings/learning.controller.ts b/src/modules/learnings/learning.controller.ts index 3db3248..10649c7 100644 --- a/src/modules/learnings/learning.controller.ts +++ b/src/modules/learnings/learning.controller.ts @@ -5,9 +5,9 @@ import { CreateLearningCategoryDto } from "./DTO/create-learning-category.dto"; import { CreateLearningDto } from "./DTO/create-learning.dto"; import { LearningService } from "./providers/learning.service"; import { AuthGuards } from "../../common/decorators/auth-guard.decorator"; -import { Roles } from "../../common/decorators/roles.decorator"; +import { PermissionsDec } from "../../common/decorators/permission.decorator"; import { ParamDto } from "../../common/DTO/param.dto"; -import { RoleEnum } from "../users/enums/role.enum"; +import { PermissionEnum } from "../users/enums/permission.enum"; @ApiTags("Learning") @AuthGuards() @@ -18,7 +18,7 @@ export class LearningController { //************************ */ @ApiOperation({ summary: "Create learning" }) - @Roles(RoleEnum.ADMIN) + @PermissionsDec(PermissionEnum.LEARNINGS) @Post() create(@Body() createDto: CreateLearningDto) { return this.learningService.createLearning(createDto); @@ -27,7 +27,7 @@ export class LearningController { //************************ */ @ApiOperation({ summary: "Create learning category" }) - @Roles(RoleEnum.ADMIN) + @PermissionsDec(PermissionEnum.LEARNINGS) @Post("category") createCategory(@Body() createDto: CreateLearningCategoryDto) { return this.learningService.createLearningCategory(createDto); @@ -36,7 +36,6 @@ export class LearningController { //************************ */ @ApiOperation({ summary: "find all learnings" }) - @Roles(RoleEnum.ADMIN, RoleEnum.USER) @Get() findAllLearnings() { return this.learningService.findAllLearnings(); @@ -45,7 +44,6 @@ export class LearningController { //************************ */ @ApiOperation({ summary: "find one learning by id" }) - @Roles(RoleEnum.ADMIN, RoleEnum.USER) @Get(":id") findOneLearning(@Param() paramDto: ParamDto) { return this.learningService.findOneLearning(paramDto.id); @@ -54,7 +52,6 @@ export class LearningController { //************************ */ @ApiOperation({ summary: "find all learning category" }) - @Roles(RoleEnum.ADMIN, RoleEnum.USER) @Get("category") findAllCategories() { return this.learningService.findAllCategories(); @@ -63,7 +60,6 @@ export class LearningController { //************************ */ @ApiOperation({ summary: "find one learning category by id" }) - @Roles(RoleEnum.ADMIN, RoleEnum.USER) @Get("category/:id") findOneCategory(@Param() paramDto: ParamDto) { return this.learningService.findOneCategory(paramDto.id); diff --git a/src/modules/notifications/notifications.controller.ts b/src/modules/notifications/notifications.controller.ts index db87f79..f1de3fa 100644 --- a/src/modules/notifications/notifications.controller.ts +++ b/src/modules/notifications/notifications.controller.ts @@ -5,10 +5,8 @@ import { SearchNotificationQueryDto } from "./DTO/search-notification-query.dto" import { NotificationsService } from "./providers/notifications.service"; import { AuthGuards } from "../../common/decorators/auth-guard.decorator"; import { Pagination } from "../../common/decorators/pagination.decorator"; -import { Roles } from "../../common/decorators/roles.decorator"; import { UserDec } from "../../common/decorators/user.decorator"; import { ParamDto } from "../../common/DTO/param.dto"; -import { RoleEnum } from "../users/enums/role.enum"; @Controller("notifications") @ApiTags("Notifications") @@ -20,10 +18,8 @@ export class NotificationController { @ApiOperation({ summary: "all notifications by user" }) @Pagination() @AuthGuards() - @Roles(RoleEnum.USER) @Get() getAllNotifications(@UserDec("id") userId: string, @Query() queryDto: SearchNotificationQueryDto) { - console.log(userId); return this.notificationService.getAllNotifications(queryDto, userId); } @@ -31,7 +27,6 @@ export class NotificationController { @ApiOperation({ summary: "mark user notification as read" }) @AuthGuards() - @Roles(RoleEnum.USER) @Patch(":id/read") markAsRead(@Param() paramDto: ParamDto, @UserDec("id") userId: string) { return this.notificationService.markAsRead(paramDto.id, userId); diff --git a/src/modules/payments/payments.controller.ts b/src/modules/payments/payments.controller.ts index 8ad63bc..e390647 100644 --- a/src/modules/payments/payments.controller.ts +++ b/src/modules/payments/payments.controller.ts @@ -11,11 +11,11 @@ import { VerifyParamDto, VerifyQueryDto } from "./DTO/verify-payment.dto"; import { PaymentsService } from "./providers/payments.service"; import { AuthGuards } from "../../common/decorators/auth-guard.decorator"; import { Pagination } from "../../common/decorators/pagination.decorator"; -import { Roles } from "../../common/decorators/roles.decorator"; +import { PermissionsDec } from "../../common/decorators/permission.decorator"; import { UserDec } from "../../common/decorators/user.decorator"; import { PaginationDto } from "../../common/DTO/pagination.dto"; import { ParamDto } from "../../common/DTO/param.dto"; -import { RoleEnum } from "../users/enums/role.enum"; +import { PermissionEnum } from "../users/enums/permission.enum"; @Controller("payments") @ApiTags("Payments") @@ -31,7 +31,6 @@ export class PaymentsController { @ApiOperation({ summary: "Charge wallet ==> user route" }) @AuthGuards() - @Roles(RoleEnum.USER) @Post("deposit/gateway") chargeWalletWithGateway(@Body() chargeDto: GatewayDepositDto, @UserDec("id") userId: string) { return this.paymentsService.chargeWalletWithGateway(chargeDto, userId); @@ -39,50 +38,49 @@ export class PaymentsController { @ApiOperation({ summary: "charge wallet with transfer ==> user route" }) @AuthGuards() - @Roles(RoleEnum.USER) @Post("deposit/transfer") chargeWalletWithTransfer(@Body() depositDto: TransferDepositDto, @UserDec("id") userId: string) { return this.paymentsService.chargeWalletWithTransfer(depositDto, userId); } @ApiOperation({ summary: "get deposit request of users ==> admin route" }) + @PermissionsDec(PermissionEnum.PAYMENTS) @AuthGuards() @Pagination() - @Roles(RoleEnum.ADMIN) @Get("deposit/transfer") getDepositRequests(@Query() queryDto: PaymentTransactionQueryDto) { return this.paymentsService.getDepositRequests(queryDto); } @ApiOperation({ summary: "get deposit gateway payment ==> admin route" }) + @PermissionsDec(PermissionEnum.PAYMENTS) @AuthGuards() @Pagination() - @Roles(RoleEnum.ADMIN) @Get("deposit/gateway") getDepositGatewayPayment(@Query() queryDto: PaginationDto) { return this.paymentsService.getDepositGatewayPayment(queryDto); } @ApiOperation({ summary: "approve deposit request ==> admin route" }) + @PermissionsDec(PermissionEnum.PAYMENTS) @AuthGuards() - @Roles(RoleEnum.ADMIN) @Post("deposit/transfer/approve/:id") approverDepositRequest(@Param() paramDto: ParamDto) { return this.paymentsService.approverDepositRequest(paramDto.id); } @ApiOperation({ summary: "reject deposit request ==> admin route" }) + @PermissionsDec(PermissionEnum.PAYMENTS) @AuthGuards() - @Roles(RoleEnum.ADMIN) @Post("deposit/transfer/reject/:id") rejectDepositRequest(@Param() paramDto: ParamDto, @Body() rejectDto: RejectDepositRequestDto) { return this.paymentsService.rejectDepositRequest(paramDto.id, rejectDto); } @ApiOperation({ summary: "get transaction for admin" }) + @PermissionsDec(PermissionEnum.PAYMENTS) @AuthGuards() @Pagination() - @Roles(RoleEnum.ADMIN) @Get("transactions") getTransaction(@Query() queryDto: SearchTransactionQueryDto) { return this.paymentsService.getTransactions(queryDto); @@ -96,7 +94,7 @@ export class PaymentsController { ///------------------- bank account ----------------------------- @AuthGuards() - @Roles(RoleEnum.ADMIN) + @PermissionsDec(PermissionEnum.PAYMENTS, PermissionEnum.BANK_ACCOUNTS) @ApiOperation({ summary: "add bank account for transfer and deposit ==> admin route" }) @Post("bank-account") addBankAccount(@Body() createDto: CreateBankAccountDto) { @@ -104,7 +102,6 @@ export class PaymentsController { } @AuthGuards() - @Roles(RoleEnum.ADMIN, RoleEnum.USER) @ApiOperation({ summary: "get bank account by id" }) @Get("bank-account/:id") getBankAccountById(@Param() paramDto: ParamDto) { @@ -112,19 +109,18 @@ export class PaymentsController { } @AuthGuards() - @Roles(RoleEnum.ADMIN) @ApiOperation({ summary: "update bank account with its id" }) + @PermissionsDec(PermissionEnum.PAYMENTS, PermissionEnum.BANK_ACCOUNTS) @Patch("bank-account/:id") updateBankAccount(@Body() updateDto: UpdateBankAccountDto, @Param() paramDto: ParamDto) { return this.paymentsService.updateBankAccount(updateDto, paramDto.id); } @AuthGuards() - @Roles(RoleEnum.ADMIN, RoleEnum.USER) @ApiOperation({ summary: "get bank account" }) @Get("bank-account") - getBankAccounts(@UserDec("roles") roles: RoleEnum[]) { - return this.paymentsService.getBankAccounts(roles); + getBankAccounts(@UserDec("isAdmin") isAdmin: boolean) { + return this.paymentsService.getBankAccounts(isAdmin); } ///------------------- Deposit transfer ----------------------------- diff --git a/src/modules/payments/providers/payments.service.ts b/src/modules/payments/providers/payments.service.ts index 241fb3c..8f41a6c 100644 --- a/src/modules/payments/providers/payments.service.ts +++ b/src/modules/payments/providers/payments.service.ts @@ -8,7 +8,6 @@ import { DataSource, Not, QueryRunner } from "typeorm"; import { PaginationDto } from "../../../common/DTO/pagination.dto"; import { CommonMessage, PaymentMessage, UserMessage, WalletMessage } from "../../../common/enums/message.enum"; import { User } from "../../users/entities/user.entity"; -import { RoleEnum } from "../../users/enums/role.enum"; import { PaginationUtils } from "../../utils/providers/pagination.utils"; import { Wallet } from "../../wallets/entities/wallet.entity"; import { WalletsService } from "../../wallets/providers/wallets.service"; @@ -262,10 +261,10 @@ export class PaymentsService { await this.bankAccountsRepository.save({ ...bankAccount, ...updateDto }); } //*********************************** */ - async getBankAccounts(roles: RoleEnum[]) { + async getBankAccounts(isAdmin: boolean) { let bankAccounts: BankAccount[]; - if (roles.includes(RoleEnum.USER)) { + if (isAdmin) { bankAccounts = await this.bankAccountsRepository.find({ where: { isActive: true } }); } else { bankAccounts = await this.bankAccountsRepository.find(); diff --git a/src/modules/subscriptions/subscriptions.controller.ts b/src/modules/subscriptions/subscriptions.controller.ts index 664d05c..afbcf00 100644 --- a/src/modules/subscriptions/subscriptions.controller.ts +++ b/src/modules/subscriptions/subscriptions.controller.ts @@ -8,10 +8,10 @@ import { SubscribeServiceDto } from "./DTO/subscribe-service.dto"; import { UpdateSubscriptionPlanDto } from "./DTO/update-subscription.dto"; import { SubscriptionsService } from "./providers/subscriptions.service"; import { AuthGuards } from "../../common/decorators/auth-guard.decorator"; -import { Roles } from "../../common/decorators/roles.decorator"; +import { PermissionsDec } from "../../common/decorators/permission.decorator"; import { UserDec } from "../../common/decorators/user.decorator"; import { ParamDto } from "../../common/DTO/param.dto"; -import { RoleEnum } from "../users/enums/role.enum"; +import { PermissionEnum } from "../users/enums/permission.enum"; @Controller("subscriptions") export class SubscriptionsController { @@ -19,7 +19,7 @@ export class SubscriptionsController { @ApiOperation({ summary: "Create a subscription plan" }) @AuthGuards() - @Roles(RoleEnum.ADMIN) + @PermissionsDec(PermissionEnum.SERVICES) @Post() createSubscription(@Body() createDto: AddSubscriptionsToServiceDto) { return this.subscriptionService.createSubscriptionsPlan(createDto); @@ -27,7 +27,7 @@ export class SubscriptionsController { @ApiOperation({ summary: "get all subscription plans" }) @AuthGuards() - @Roles(RoleEnum.ADMIN) + @PermissionsDec(PermissionEnum.SERVICES) @Get("service/:serviceId") getServiceSubscriptions(@Param() paramDto: ServiceIdParamDto, @Query() queryDto: ServiceSubsQueryDto) { return this.subscriptionService.getServiceSubscriptions(paramDto.serviceId, queryDto); @@ -35,7 +35,7 @@ export class SubscriptionsController { @ApiOperation({ summary: "toggle status of service subs" }) @AuthGuards() - @Roles(RoleEnum.ADMIN) + @PermissionsDec(PermissionEnum.SERVICES) @Post("toggle-status/:id") toggleStatusOfServiceSub(@Param() paramDto: ParamDto) { return this.subscriptionService.toggleSubStatus(paramDto.id); @@ -43,23 +43,23 @@ export class SubscriptionsController { @ApiOperation({ summary: "get a subscription plan by id" }) @AuthGuards() - @Roles(RoleEnum.ADMIN) @Get(":id") + @PermissionsDec(PermissionEnum.SERVICES) getSubscription(@Param() paramDto: ParamDto) { return this.subscriptionService.getSubscriptionPlanById(paramDto.id); } @ApiOperation({ summary: "update a subscription plan by id" }) @AuthGuards() - @Roles(RoleEnum.ADMIN) @Patch(":id") + //TODO:fix this if needed + @PermissionsDec(PermissionEnum.SERVICES) updateSubscription(@Param() paramDto: ParamDto, @Body() updateDto: UpdateSubscriptionPlanDto) { return this.subscriptionService.updateSubscriptionPlan(paramDto.id, updateDto); } @ApiOperation({ summary: "subscribe to a service ==> user route" }) @AuthGuards() - @Roles(RoleEnum.USER) @Post(":serviceId/subscribe") subscribe(@Param() paramDto: ServiceIdParamDto, @Body() subscribeDto: SubscribeServiceDto, @UserDec("id") userId: string) { return this.subscriptionService.subscribeToPlan(paramDto.serviceId, subscribeDto, userId); diff --git a/src/modules/tickets/providers/tickets.service.ts b/src/modules/tickets/providers/tickets.service.ts index a3a7518..ec3a35b 100644 --- a/src/modules/tickets/providers/tickets.service.ts +++ b/src/modules/tickets/providers/tickets.service.ts @@ -4,7 +4,6 @@ import { DataSource, Not } from "typeorm"; import { ParamDto } from "../../../common/DTO/param.dto"; import { CommonMessage, TicketMessageEnum, UserMessage } from "../../../common/enums/message.enum"; import { User } from "../../users/entities/user.entity"; -import { RoleEnum } from "../../users/enums/role.enum"; import { UsersService } from "../../users/providers/users.service"; import { PaginationUtils } from "../../utils/providers/pagination.utils"; import { CreateTicketCategoryDto } from "../DTO/create-ticket-category.dto"; @@ -181,7 +180,7 @@ export class TicketsService { //******************************** */ - async createTicketMessage(ticketId: string, createDto: CreateTicketMessageDto, userId: string, roles: RoleEnum[]) { + async createTicketMessage(ticketId: string, createDto: CreateTicketMessageDto, userId: string, isAdmin: boolean) { const queryRunner = this.dataSource.createQueryRunner(); await queryRunner.connect(); await queryRunner.startTransaction(); @@ -189,7 +188,7 @@ export class TicketsService { try { let ticket: null | Ticket = null; - if (roles.includes(RoleEnum.ADMIN)) { + if (isAdmin) { ticket = await queryRunner.manager.findOneBy(Ticket, { id: ticketId }); } else { ticket = await queryRunner.manager.findOneBy(Ticket, { id: ticketId, user: { id: userId } }); @@ -204,7 +203,7 @@ export class TicketsService { const ticketMessage = queryRunner.manager.create(TicketMessage, { ...createDto, author: user, ticket }); - if (roles.includes(RoleEnum.ADMIN) && ticket.status === TicketStatus.PENDING) { + if (isAdmin && ticket.status === TicketStatus.PENDING) { ticket.status = TicketStatus.ANSWERED; } @@ -234,11 +233,11 @@ export class TicketsService { //******************************** */ - async getTicketMessages(ticketId: string, userId: string, roles: RoleEnum[]) { + async getTicketMessages(ticketId: string, userId: string, isAdmin: boolean) { let ticket: null | Ticket = null; // - if (roles.includes(RoleEnum.ADMIN)) { + if (isAdmin) { ticket = await this.ticketsRepository.findTicketById(ticketId); } else { ticket = await this.ticketsRepository.findTicketById(ticketId, userId); @@ -254,10 +253,10 @@ export class TicketsService { //******************************** */ - async closeTicketByUser(ticketId: string, userId: string, roles: RoleEnum[]) { + async closeTicketByUser(ticketId: string, userId: string, isAdmin: boolean) { let ticket: null | Ticket = null; - if (roles.includes(RoleEnum.ADMIN)) { + if (isAdmin) { ticket = await this.ticketsRepository.findTicketById(ticketId); } else { ticket = await this.ticketsRepository.findTicketById(ticketId, userId); diff --git a/src/modules/tickets/tickets.controller.ts b/src/modules/tickets/tickets.controller.ts index ed4c902..2f83a6c 100644 --- a/src/modules/tickets/tickets.controller.ts +++ b/src/modules/tickets/tickets.controller.ts @@ -11,12 +11,9 @@ import { TicketsService } from "./providers/tickets.service"; import { AuthGuards } from "../../common/decorators/auth-guard.decorator"; import { Pagination } from "../../common/decorators/pagination.decorator"; import { PermissionsDec } from "../../common/decorators/permission.decorator"; -import { Roles } from "../../common/decorators/roles.decorator"; import { UserDec } from "../../common/decorators/user.decorator"; import { ParamDto } from "../../common/DTO/param.dto"; -import { User } from "../users/entities/user.entity"; import { PermissionEnum } from "../users/enums/permission.enum"; -import { RoleEnum } from "../users/enums/role.enum"; @Controller("tickets") @ApiTags("Tickets") @@ -27,7 +24,6 @@ export class TicketsController { @ApiOperation({ summary: "Create ticket category => admin route" }) @AuthGuards() - @Roles(RoleEnum.ADMIN) @PermissionsDec(PermissionEnum.TICKETS) @Post("category") createTicketCategory(@Body() createDto: CreateTicketCategoryDto) { @@ -36,7 +32,6 @@ export class TicketsController { @ApiOperation({ summary: "Update ticket category => admin route" }) @AuthGuards() - @Roles(RoleEnum.ADMIN) @PermissionsDec(PermissionEnum.TICKETS) @Patch("category/:id") updateCategory(@Param() paramDto: ParamDto, @Body() updateCategoryDto: UpdateTicketCategoryDto) { @@ -45,7 +40,6 @@ export class TicketsController { @ApiOperation({ summary: "Get ticket categories " }) @AuthGuards() - @Roles(RoleEnum.ADMIN, RoleEnum.USER) @Get("categories") @PermissionsDec(PermissionEnum.TICKETS) getTicketCategories() { @@ -55,7 +49,7 @@ export class TicketsController { @ApiOperation({ summary: "Get ticket categories list => admin route" }) @Pagination() @AuthGuards() - @Roles(RoleEnum.ADMIN) + @PermissionsDec(PermissionEnum.TICKETS) @Get("categories-list") getCategoriesList(@Query() queryDto: SearchTicketCategoryDto) { return this.ticketsService.getCategoriesList(queryDto); @@ -63,8 +57,8 @@ export class TicketsController { @ApiOperation({ summary: "toggle status of categories => admin route" }) @AuthGuards() - @Roles(RoleEnum.ADMIN) @HttpCode(HttpStatus.OK) + @PermissionsDec(PermissionEnum.TICKETS) @Post("category/toggle-status/:id") toggleCategoryStatus(@Param() paramDto: ParamDto) { return this.ticketsService.toggleCategoryStatus(paramDto); @@ -74,7 +68,6 @@ export class TicketsController { @ApiOperation({ summary: "create ticket ==> user route" }) @AuthGuards() - @Roles(RoleEnum.USER) @Post() createTicket(@Body() createDto: CreateTicketDto, @UserDec("id") userId: string) { return this.ticketsService.createTicket(createDto, userId); @@ -82,7 +75,6 @@ export class TicketsController { @ApiOperation({ summary: "Get all tickets of user ==> user route" }) @AuthGuards() - @Roles(RoleEnum.USER) @Pagination() @Get() getTickets(@Query() queryDto: SearchTicketQueryDto, @UserDec("id") userId: string) { @@ -91,7 +83,7 @@ export class TicketsController { @ApiOperation({ summary: "Get all tickets ==> admin route" }) @AuthGuards() - @Roles(RoleEnum.ADMIN) + @PermissionsDec(PermissionEnum.TICKETS) @Pagination() @Get("admin") getAllTickets(@Query() queryDto: SearchTicketQueryDto) { @@ -100,26 +92,28 @@ export class TicketsController { @ApiOperation({ summary: "create ticket messages" }) @AuthGuards() - @Roles(RoleEnum.USER, RoleEnum.ADMIN) @Post(":id/messages") - createTicketMessage(@Param() paramDto: ParamDto, @Body() createDto: CreateTicketMessageDto, @UserDec() user: User) { - return this.ticketsService.createTicketMessage(paramDto.id, createDto, user.id, user.roles as unknown as RoleEnum[]); + createTicketMessage( + @Param() paramDto: ParamDto, + @Body() createDto: CreateTicketMessageDto, + @UserDec("id") userId: string, + @UserDec("isAdmin") isAdmin: boolean, + ) { + return this.ticketsService.createTicketMessage(paramDto.id, createDto, userId, isAdmin); } @ApiOperation({ summary: "Get all ticket messages of user" }) @AuthGuards() - @Roles(RoleEnum.USER, RoleEnum.ADMIN) @Get(":id/messages") - getTicketMessages(@Param() paramDto: ParamDto, @UserDec() user: User) { - return this.ticketsService.getTicketMessages(paramDto.id, user.id, user.roles as unknown as RoleEnum[]); + getTicketMessages(@Param() paramDto: ParamDto, @UserDec("id") userId: string, @UserDec("isAdmin") isAdmin: boolean) { + return this.ticketsService.getTicketMessages(paramDto.id, userId, isAdmin); } @ApiOperation({ summary: "close ticket by user or admin" }) @AuthGuards() - @Roles(RoleEnum.USER, RoleEnum.ADMIN) @HttpCode(HttpStatus.OK) @Post(":id/close") - closedTicketByUser(@Param() paramDto: ParamDto, @UserDec() user: User) { - return this.ticketsService.closeTicketByUser(paramDto.id, user.id, user.roles as unknown as RoleEnum[]); + closedTicketByUser(@Param() paramDto: ParamDto, @UserDec("id") userId: string, @UserDec("isAdmin") isAdmin: boolean) { + return this.ticketsService.closeTicketByUser(paramDto.id, userId, isAdmin); } } diff --git a/src/modules/users/DTO/create-admin.dto.ts b/src/modules/users/DTO/create-admin.dto.ts new file mode 100644 index 0000000..7d39a5b --- /dev/null +++ b/src/modules/users/DTO/create-admin.dto.ts @@ -0,0 +1,29 @@ +import { ApiProperty, ApiPropertyOptional, PickType } from "@nestjs/swagger"; +import { IsEmail, IsNotEmpty, IsOptional, IsString, IsUUID, IsUrl, MinLength } from "class-validator"; + +import { AdminMessage, AuthMessage } from "../../../common/enums/message.enum"; +import { CompleteRegistrationDto } from "../../auth/DTO/complete-register.dto"; + +export class CreateAdminDto extends PickType(CompleteRegistrationDto, ["firstName", "lastName", "phone", "password"] as const) { + @IsNotEmpty({ message: AuthMessage.PASSWORD_NOT_EMPTY }) + @IsString({ message: AuthMessage.PASSWORD_FORMAT_INVALID }) + @ApiProperty({ description: "repeat password", example: "12345678" }) + @MinLength(8, { message: AuthMessage.PASSWORD_LENGTH }) + repeatPassword: string; + + @IsNotEmpty({ message: AuthMessage.EMAIL_NOT_EMPTY }) + @IsEmail({}, { message: AuthMessage.INVALID_EMAIL_FORMAT }) + @ApiProperty({ description: "Email", example: "ma@gmail.com" }) + email: string; + + @IsNotEmpty({ message: AdminMessage.ROLE_REQUIRED }) + @IsUUID("4", { message: AdminMessage.ROLE_ID_SHOULD_BE_UUID }) + @ApiProperty({ description: "Role id", example: "123e4567-e89b-12d3-a456-426614174000" }) + roleId: string; + + @IsOptional() + @IsNotEmpty({ message: AdminMessage.PROFILE_PIC_REQUIRED }) + @IsUrl({ protocols: ["http", "https"], require_protocol: true }, { message: AdminMessage.PROFILE_PIC_URL }) + @ApiPropertyOptional({ description: "Profile picture", example: "https://www.google.com" }) + profilePic: string; +} diff --git a/src/modules/users/DTO/create-role.dto.ts b/src/modules/users/DTO/create-role.dto.ts new file mode 100644 index 0000000..45fe4da --- /dev/null +++ b/src/modules/users/DTO/create-role.dto.ts @@ -0,0 +1,17 @@ +import { ApiProperty } from "@nestjs/swagger"; +import { IsNotEmpty, IsString, IsUUID, Length } from "class-validator"; + +import { AdminMessage } from "../../../common/enums/message.enum"; + +export class CreateRoleDto { + @IsNotEmpty({ message: AdminMessage.ROLE_NAME_REQUIRED }) + @IsString({ message: AdminMessage.ROLE_NAME_STRING }) + @Length(3, 150, { message: AdminMessage.ROLE_NAME_LENGTH }) + @ApiProperty({ description: "Role name", example: "super-admin" }) + name: string; + + @IsNotEmpty({ message: AdminMessage.PERMISSIONS_REQUIRED }) + @IsUUID("4", { each: true, message: AdminMessage.PERMISSIONS_ID_SHOULD_BE_UUID }) + @ApiProperty({ description: "Permissions id", example: ["123e4567-e89b-12d3-a456-426614174000"] }) + permissions: string[]; +} diff --git a/src/modules/users/DTO/search-admins-query.dto.ts b/src/modules/users/DTO/search-admins-query.dto.ts new file mode 100644 index 0000000..2087404 --- /dev/null +++ b/src/modules/users/DTO/search-admins-query.dto.ts @@ -0,0 +1,17 @@ +import { ApiPropertyOptional } from "@nestjs/swagger"; +import { IsEnum, IsOptional, IsString } from "class-validator"; + +import { PaginationDto } from "../../../common/DTO/pagination.dto"; +import { PermissionEnum } from "../enums/permission.enum"; + +export class SearchAdminQueryDto extends PaginationDto { + @IsOptional() + @IsString() + @ApiPropertyOptional({ description: "search query", example: "mahyar" }) + q?: string; + + @IsOptional() + @IsEnum(PermissionEnum) + @ApiPropertyOptional({ description: "permission", example: PermissionEnum.SERVICES }) + permission?: PermissionEnum; +} diff --git a/src/modules/users/DTO/search-admins.dto.ts b/src/modules/users/DTO/search-admins.dto.ts deleted file mode 100644 index d81bcc1..0000000 --- a/src/modules/users/DTO/search-admins.dto.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { ApiPropertyOptional } from "@nestjs/swagger"; -import { IsOptional, IsString } from "class-validator"; - -import { PaginationDto } from "../../../common/DTO/pagination.dto"; -import { FinancialMessage } from "../../../common/enums/message.enum"; - -export class SearchAdminsDto extends PaginationDto { - @IsOptional() - @IsString({ message: FinancialMessage.SEARCH_QUERY_MUST_BE_A_STRING }) - @ApiPropertyOptional({ description: "search query", example: "search query" }) - q: string; -} diff --git a/src/modules/users/DTO/search-roles.dto.ts b/src/modules/users/DTO/search-roles.dto.ts new file mode 100644 index 0000000..3b6184a --- /dev/null +++ b/src/modules/users/DTO/search-roles.dto.ts @@ -0,0 +1,11 @@ +import { ApiPropertyOptional } from "@nestjs/swagger"; +import { IsOptional, IsString } from "class-validator"; + +import { PaginationDto } from "../../../common/DTO/pagination.dto"; + +export class SearchRolesQueryDto extends PaginationDto { + @IsOptional() + @IsString() + @ApiPropertyOptional({ description: "Search query", example: "admin" }) + q?: string; +} diff --git a/src/modules/users/entities/role.entity.ts b/src/modules/users/entities/role.entity.ts index 3054f43..7a99357 100644 --- a/src/modules/users/entities/role.entity.ts +++ b/src/modules/users/entities/role.entity.ts @@ -1,15 +1,24 @@ import { Column, Entity, JoinTable, ManyToMany } from "typeorm"; import { Permission } from "./permission.entity"; +import { User } from "./user.entity"; import { BaseEntity } from "../../../common/entities/base.entity"; -import { RoleEnum } from "../enums/role.enum"; @Entity() export class Role extends BaseEntity { - @Column({ type: "enum", enum: RoleEnum, default: RoleEnum.USER, nullable: false, unique: true }) - name: RoleEnum; + // @Column({ type: "enum", enum: RoleEnum, default: RoleEnum.USER, nullable: false, unique: true }) + // name: RoleEnum; + + @Column({ type: "varchar", length: 150, nullable: false, unique: true }) + name: string; + + @Column({ type: "boolean", default: false }) + isAdmin: boolean; @ManyToMany(() => Permission, (permission) => permission.roles) @JoinTable({ name: "role_permission_relation" }) permissions: Permission[]; + + @ManyToMany(() => User, (user) => user.roles) + users: User[]; } diff --git a/src/modules/users/entities/user-financial.entity.ts b/src/modules/users/entities/user-financial.entity.ts new file mode 100644 index 0000000..08dd44e --- /dev/null +++ b/src/modules/users/entities/user-financial.entity.ts @@ -0,0 +1,27 @@ +import { Column, Entity, ManyToOne, Unique } from "typeorm"; + +import { User } from "./user.entity"; +import { BaseEntity } from "../../../common/entities/base.entity"; +import { FinancialType } from "../enums/financial-type.enum"; + +@Unique(["user", "type"]) +@Entity() +export class UserFinancial extends BaseEntity { + @Column({ type: "enum", enum: FinancialType, default: FinancialType.REAL }) + type: FinancialType; + + @Column({ nullable: true }) + economicCode: string; + + @Column({ nullable: true }) + registrationId: string; + + @Column({ nullable: true }) + nationalId: string; + + @Column({ nullable: true }) + number: string; + + @ManyToOne(() => User) + user: User; +} diff --git a/src/modules/users/entities/user.entity.ts b/src/modules/users/entities/user.entity.ts index d6ccc43..f58e9f4 100644 --- a/src/modules/users/entities/user.entity.ts +++ b/src/modules/users/entities/user.entity.ts @@ -43,14 +43,16 @@ export class User extends BaseEntity { @Column({ type: "varchar", length: 12, nullable: true }) birthDate: string; - @Column({ type: "varchar", length: 100, unique: true, nullable: false }) - nationalCode: string; + @Column({ type: "varchar", length: 100, unique: true, nullable: true }) + nationalCode: string | null; + @Column({ type: "varchar", length: 100, nullable: true }) + profilePic: string; //----------------------------------------- // @ManyToOne(() => Role, { eager: true, onDelete: "RESTRICT", nullable: false }) // role: Role; - @ManyToMany(() => Role) + @ManyToMany(() => Role, (role) => role.users) @JoinTable({ name: "user_role_relation" }) roles: Role[]; diff --git a/src/modules/users/enums/permission.enum.ts b/src/modules/users/enums/permission.enum.ts index a43909c..d61095a 100644 --- a/src/modules/users/enums/permission.enum.ts +++ b/src/modules/users/enums/permission.enum.ts @@ -16,4 +16,6 @@ export enum PermissionEnum { LEARNINGS = "learnings", LOGS = "logs", SETTINGS = "settings", + BANK_ACCOUNTS = "bank_accounts", + PAYMENTS = "payments", } diff --git a/src/modules/users/enums/role.enum.ts b/src/modules/users/enums/role.enum.ts index 5101513..d5813e8 100644 --- a/src/modules/users/enums/role.enum.ts +++ b/src/modules/users/enums/role.enum.ts @@ -1,6 +1,4 @@ export enum RoleEnum { - ADMIN = "admin", + SUPER_ADMIN = "super_admin", USER = "user", - DEVELOPER = "developer", - AGENT = "agent", } diff --git a/src/modules/users/providers/users.service.ts b/src/modules/users/providers/users.service.ts index 7255f2b..0f65b91 100644 --- a/src/modules/users/providers/users.service.ts +++ b/src/modules/users/providers/users.service.ts @@ -2,49 +2,40 @@ import { BadRequestException, Injectable } from "@nestjs/common"; import slugify from "slugify"; import { In, Not, QueryRunner } from "typeorm"; -import { CommonMessage, UserMessage } from "../../../common/enums/message.enum"; +// import { CreateFinancialDto } from "../DTO/create-legal-user.dto"; +import { AdminMessage, AdsMessage, CommonMessage, UserMessage } from "../../../common/enums/message.enum"; import { CompleteRegistrationDto } from "../../auth/DTO/complete-register.dto"; import { UserSettingsService } from "../../settings/providers/user-settings.service"; import { PaginationUtils } from "../../utils/providers/pagination.utils"; +import { PasswordService } from "../../utils/providers/password.service"; import { WalletsService } from "../../wallets/providers/wallets.service"; import { CheckValidityDTO } from "../DTO/check-validity.dto"; -// import { CreateFinancialDto } from "../DTO/create-legal-user.dto"; -import { SearchAdminsDto } from "../DTO/search-admins.dto"; +import { CreateAdminDto } from "../DTO/create-admin.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 { Role } from "../entities/role.entity"; import { User } from "../entities/user.entity"; import { RoleEnum } from "../enums/role.enum"; import { ValidityType } from "../enums/validity-type.enum"; -// import { LegalUserRepository } from "../repositories/legal-user.repository"; -// import { RealUserRepository } from "../repositories/real-user.repository"; +import { PermissionsRepository } from "../repositories/permissions.repository"; +import { RoleRepository } from "../repositories/roles.repository"; import { UserGroupRepository } from "../repositories/user-group.repository"; import { UserRepository } from "../repositories/users.repository"; @Injectable() export class UsersService { - private readonly userSelect = { - id: true, - phone: true, - email: true, - userName: true, - firstName: true, - lastName: true, - nationalCode: true, - birthDate: true, - role: { - id: true, - name: true, - }, - }; constructor( private readonly userRepository: UserRepository, private readonly userGroupRepository: UserGroupRepository, private readonly userSettingsService: UserSettingsService, private readonly walletsService: WalletsService, - // private readonly realUserRepository: RealUserRepository, - // private readonly legalUserRepository: LegalUserRepository, + private readonly rolesRepository: RoleRepository, + private readonly permissionsRepository: PermissionsRepository, + private readonly passwordService: PasswordService, ) {} /************************************************************ */ @@ -65,7 +56,7 @@ export class UsersService { /************************************************************ */ async getMe(userId: string) { - const user = await this.userRepository.findOne({ where: { id: userId }, select: this.userSelect }); + const user = await this.userRepository.findOne({ where: { id: userId }, relations: { roles: true } }); if (!user) throw new BadRequestException(UserMessage.USER_NOT_FOUND); return { user }; } @@ -74,6 +65,7 @@ export class UsersService { async updateProfile(userId: string, updateProfileDto: UpdateProfileDto) { if (updateProfileDto.userName) { + // updateProfileDto.userName = slugify(updateProfileDto.userName, { lower: true, trim: true }); const existUserName = await this.userRepository.findOneBy({ userName: updateProfileDto.userName, id: Not(userId) }); if (existUserName) throw new BadRequestException(UserMessage.USERNAME_EXIST); @@ -81,12 +73,8 @@ export class UsersService { const user = await this.userRepository.findOneBy({ id: userId }); if (!user) throw new BadRequestException(UserMessage.USER_NOT_FOUND); - // const { userName } = updateProfileDto; - // if (userName) user.userName = userName; - // if (firstName) user.firstName = firstName; - // if (lastName) user.lastName = lastName; - await this.userRepository.save({ ...user, ...updateProfileDto }); + // return { message: CommonMessage.UPDATE_SUCCESS, }; @@ -110,6 +98,9 @@ export class UsersService { const role = await queryRunner.manager.findOneBy(Role, { name: RoleEnum.USER }); if (!role) throw new BadRequestException(UserMessage.ROLE_NOT_FOUND); + const existUser = await queryRunner.manager.findOneBy(User, { nationalCode: registerDto.nationalCode }); + if (existUser) throw new BadRequestException(UserMessage.NATIONAL_CODE_EXIST); + const user = queryRunner.manager.create(User, { ...registerDto, password: hashedPassword, @@ -122,12 +113,47 @@ export class UsersService { await this.walletsService.createUserWallet(user.id, queryRunner); return user; - // const role = await this.roleRepository.findOneBy({ name: RoleEnum.USER }); - // if (!role) throw new BadRequestException(UserMessage.ROLE_NOT_FOUND); + } - // const user = this.userRepository.create({ ...registerDto, password: hashedPassword, role }); - // await this.userSettingsService.createUserSettings(user.id); - // return await this.userRepository.save(user); + /************************************************************ */ + + async createAdmin(createDto: CreateAdminDto) { + const existEmail = await this.userRepository.findOneBy({ email: createDto.email }); + if (existEmail) throw new BadRequestException(UserMessage.EMAIL_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, + }; } /************************************************************ */ @@ -209,30 +235,6 @@ export class UsersService { /************************************************************ */ - async findAllAdmins(queryDto: SearchAdminsDto) { - const { limit, skip } = PaginationUtils(queryDto); - - const queryBuilder = this.userRepository - .createQueryBuilder("user") - .leftJoin("user.role", "role") - .where("role.name = :roleName", { roleName: RoleEnum.ADMIN }) - .leftJoin("user.groups", "groups") - .addSelect(["groups.id", "groups.name"]); - - 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 [admins, count] = await queryBuilder.skip(skip).take(limit).getManyAndCount(); - - return { admins, count }; - } - - /************************************************************ */ - async findAllCustomers(queryDto: SearchCustomersDto) { const { limit, skip } = PaginationUtils(queryDto); @@ -273,73 +275,77 @@ export class UsersService { return { customer }; } - /************************************************************ */ - // async createRealUserData(createDto: CreateFinancialDto, user: User) { - // const userExist = await this.userRepository.findOneBy({ - // id: user.role.name === RoleEnum.USER ? user.id : createDto.userId, - // }); + async getAdmins(queryDto: SearchAdminQueryDto) { + const { limit, skip } = PaginationUtils(queryDto); + const queryBuilder = this.userRepository.createQueryBuilder("user"); - // if (!userExist) throw new BadRequestException(UserMessage.USER_NOT_FOUND); + 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"]); - // const existingFinancial = await this.userFinancialRepository.findOne({ - // where: { user: { id: userExist.id }, type: createDto.type }, - // }); - // console.log(existingFinancial); + if (queryDto.q) { + queryBuilder.andWhere("user.firstName ILIKE :q OR user.lastName ILIKE :q OR user.userName ILIKE :q", { + q: `%${queryDto.q}%`, + }); + } - // if (existingFinancial) throw new BadRequestException(FinancialMessage.FINANCIAL_INFO_ALREADY_EXISTS); + if (queryDto.permission) { + queryBuilder.andWhere("permissions.name = :permName", { permName: queryDto.permission }); + } - // const userFinancial = this.userFinancialRepository.create({ - // ...createDto, - // user: userExist, - // }); + queryBuilder.take(limit).skip(skip).orderBy("user.createdAt", "DESC"); - // await this.userFinancialRepository.save(userFinancial); + const [users, count] = await queryBuilder.getManyAndCount(); - // return { - // message: CommonMessage.CREATED, - // userFinancial, - // }; - // } - - // /************************************************************ */ - - // async updateUserFinancial(updateDto: CreateFinancialDto, user: User, userFinancialId: string) { - // const userExist = await this.userRepository.findOneBy({ - // id: user.role.name === RoleEnum.USER ? user.id : updateDto.userId, - // }); - - // if (!userExist) throw new BadRequestException(UserMessage.USER_NOT_FOUND); - - // const existingFinancial = await this.userFinancialRepository.findOne({ - // where: { id: userFinancialId, user: { id: userExist.id }, type: updateDto.type }, - // }); - - // if (!existingFinancial) throw new BadRequestException(FinancialMessage.FINANCIAL_INFO_NOT_FOUND); - - // await this.userFinancialRepository.save({ - // ...existingFinancial, - // updateDto, - // }); - - // return { - // message: CommonMessage.UPDATE_SUCCESS, - // userFinancial: existingFinancial, - // }; - // } - - /************************************************************ */ - - async getUserFinancial(userId: string) { - const user = await this.userRepository.findOne({ - where: { - id: userId, - }, - relations: ["userFinancials"], - }); - if (!user) throw new BadRequestException(UserMessage.USER_NOT_FOUND); - - return { user }; + 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/repositories/permissions.repository.ts b/src/modules/users/repositories/permissions.repository.ts new file mode 100644 index 0000000..93cdff8 --- /dev/null +++ b/src/modules/users/repositories/permissions.repository.ts @@ -0,0 +1,12 @@ +import { Injectable } from "@nestjs/common"; +import { InjectRepository } from "@nestjs/typeorm"; +import { Repository } from "typeorm"; + +import { Permission } from "../entities/permission.entity"; + +@Injectable() +export class PermissionsRepository extends Repository { + constructor(@InjectRepository(Permission) permissionsRepository: Repository) { + super(permissionsRepository.target, permissionsRepository.manager, permissionsRepository.queryRunner); + } +} diff --git a/src/modules/users/users.controller.ts b/src/modules/users/users.controller.ts index 0bb6fae..1a515c5 100644 --- a/src/modules/users/users.controller.ts +++ b/src/modules/users/users.controller.ts @@ -2,15 +2,18 @@ import { Body, Controller, Get, HttpCode, HttpStatus, Patch, Post, Query } from import { ApiOperation, ApiTags } from "@nestjs/swagger"; import { CheckValidityDTO } from "./DTO/check-validity.dto"; -import { SearchAdminsDto } from "./DTO/search-admins.dto"; +import { CreateAdminDto } from "./DTO/create-admin.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 { User } from "./entities/user.entity"; -import { RoleEnum } from "./enums/role.enum"; +import { PermissionEnum } from "./enums/permission.enum"; import { UsersService } from "./providers/users.service"; import { AuthGuards } from "../../common/decorators/auth-guard.decorator"; -import { Roles } from "../../common/decorators/roles.decorator"; +import { PermissionsDec } from "../../common/decorators/permission.decorator"; import { UserDec } from "../../common/decorators/user.decorator"; @Controller("users") @@ -49,9 +52,8 @@ export class UsersController { /************************************************************ */ @AuthGuards() - @Roles(RoleEnum.ADMIN) - @ApiOperation({ summary: "Create user group" }) - @HttpCode(HttpStatus.CREATED) + @ApiOperation({ summary: "Create user group ==> admin route" }) + @PermissionsDec(PermissionEnum.ADMINS, PermissionEnum.CUSTOMERS) @Post("user-group") createUserGroup(@Body() createDto: CreateUserGroupDto) { return this.usersService.createUserGroup(createDto); @@ -59,9 +61,9 @@ export class UsersController { /************************************************************ */ - @ApiOperation({ summary: "get all user group" }) + @ApiOperation({ summary: "get all user group ==> admin route" }) + @PermissionsDec(PermissionEnum.ADMINS, PermissionEnum.CUSTOMERS) @AuthGuards() - @Roles(RoleEnum.ADMIN) @Get("user-group") UserGroups() { return this.usersService.getUserGroups(); @@ -69,50 +71,59 @@ export class UsersController { /************************************************************ */ - @ApiOperation({ summary: "get all admins" }) + @ApiOperation({ summary: "get all users ==> admin route" }) + @PermissionsDec(PermissionEnum.ADMINS, PermissionEnum.CUSTOMERS) @AuthGuards() - @Roles(RoleEnum.ADMIN) - @Get("admins") - Users(@Query() queryDto: SearchAdminsDto) { - return this.usersService.findAllAdmins(queryDto); + @Get() + Users() { + return this.usersService.findAllUsers(); } - /************************************************************ */ - - @ApiOperation({ summary: "get all customers" }) + @ApiOperation({ summary: "get all customers ==> admin route" }) + @PermissionsDec(PermissionEnum.ADMINS, PermissionEnum.CUSTOMERS) @AuthGuards() - @Roles(RoleEnum.ADMIN) @Get("customers") customers(@Query() queryDto: SearchCustomersDto) { return this.usersService.findAllCustomers(queryDto); } - /************************************************************ */ - - // @AuthGuards() - // @ApiOperation({ summary: "Create user financial" }) - // @HttpCode(HttpStatus.CREATED) - // @Post("user-financial") - // createUserFinancial(@Body() createDto: CreateFinancialDto, @UserDec() user: User) { - // return this.usersService.createUserFinancial(createDto, user); - // } - - /************************************************************ */ - - // @AuthGuards() - // @ApiOperation({ summary: "update user financial" }) - // @HttpCode(HttpStatus.CREATED) - // @Patch("user-financial/:id/update") - // updateUserFinancial(@Body() updateDto: CreateFinancialDto, @UserDec() user: User, @Param() paramDto: ParamDto) { - // return this.usersService.updateUserFinancial(updateDto, user, paramDto.id); - // } - - /************************************************************ */ - - @ApiOperation({ summary: "get all user financials" }) + @ApiOperation({ summary: "get all admins ==> admin route" }) + @PermissionsDec(PermissionEnum.ADMINS, PermissionEnum.CUSTOMERS) @AuthGuards() - @Get("user-financials") - userFinancials(@UserDec("id") userId: string) { - return this.usersService.getUserFinancial(userId); + @Get("admins") + getAdmins(@Query() queryDto: SearchAdminQueryDto) { + return this.usersService.getAdmins(queryDto); + } + + @ApiOperation({ summary: "create admin ==> admin route" }) + @AuthGuards() + @PermissionsDec(PermissionEnum.ADMINS, PermissionEnum.CUSTOMERS) + @Post("admins") + createAdmin(@Body() createDto: CreateAdminDto) { + return this.usersService.createAdmin(createDto); + } + + @ApiOperation({ summary: "get all permissions ==> admin route" }) + @AuthGuards() + @PermissionsDec(PermissionEnum.ADMINS, PermissionEnum.CUSTOMERS) + @Get("permissions") + getPermissions() { + return this.usersService.getPermissions(); + } + + @ApiOperation({ summary: "get all roles ==> admin route" }) + @AuthGuards() + @PermissionsDec(PermissionEnum.ADMINS, PermissionEnum.CUSTOMERS) + @Get("roles") + getRoles(@Query() queryDto: SearchRolesQueryDto) { + return this.usersService.getRoles(queryDto); + } + + @ApiOperation({ summary: "create role ==> admin route" }) + @AuthGuards() + @PermissionsDec(PermissionEnum.ADMINS, PermissionEnum.CUSTOMERS) + @Post("roles") + createRole(@Body() createDto: CreateRoleDto) { + return this.usersService.createRole(createDto); } } diff --git a/src/modules/users/users.module.ts b/src/modules/users/users.module.ts index 70b4d2f..1fcfe69 100644 --- a/src/modules/users/users.module.ts +++ b/src/modules/users/users.module.ts @@ -8,19 +8,21 @@ import { UserGroup } from "./entities/user-group.entity"; import { User } from "./entities/user.entity"; import { UsersService } from "./providers/users.service"; import { LegalUserRepository } from "./repositories/legal-user.repository"; +import { PermissionsRepository } from "./repositories/permissions.repository"; import { RoleRepository } from "./repositories/roles.repository"; import { UserGroupRepository } from "./repositories/user-group.repository"; import { UserRepository } from "./repositories/users.repository"; -import { UsersController } from "./users.controller"; import { UserSetting } from "../settings/entities/user-setting.entity"; import { UserSettingsService } from "../settings/providers/user-settings.service"; import { UserSettingsRepository } from "../settings/repositories/user-settings.repository"; import { WalletsModule } from "../wallets/wallets.module"; 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"; @Module({ - imports: [TypeOrmModule.forFeature([User, Role, UserGroup, UserSetting, RealUser, LegalUser, Permission]), WalletsModule], + imports: [TypeOrmModule.forFeature([User, Role, UserGroup, UserSetting, RealUser, LegalUser, Permission]), WalletsModule, UtilsModule], providers: [ UsersService, UserRepository, @@ -28,10 +30,11 @@ import { RealUserRepository } from "./repositories/real-user.repository"; UserGroupRepository, UserSettingsRepository, UserSettingsService, + PermissionsRepository, RealUserRepository, LegalUserRepository, ], controllers: [UsersController], - exports: [UsersService, TypeOrmModule, UserRepository], + exports: [UsersService, UserRepository], }) export class UsersModule {} diff --git a/src/modules/utils/providers/checkRole.utils.ts b/src/modules/utils/providers/checkRole.utils.ts index b7030d2..e69de29 100644 --- a/src/modules/utils/providers/checkRole.utils.ts +++ b/src/modules/utils/providers/checkRole.utils.ts @@ -1,7 +0,0 @@ -import { Role } from "../../users/entities/role.entity"; -import { RoleEnum } from "../../users/enums/role.enum"; - -export function checkUserRole(userRoles: Role[], requiredRole: RoleEnum) { - if (!userRoles.some((role) => role.name === requiredRole)) return false; - return true; -} diff --git a/src/modules/wallets/wallets.controller.ts b/src/modules/wallets/wallets.controller.ts index 0552610..3c24f77 100644 --- a/src/modules/wallets/wallets.controller.ts +++ b/src/modules/wallets/wallets.controller.ts @@ -4,10 +4,8 @@ import { ApiOperation, ApiTags } from "@nestjs/swagger"; import { WalletsService } from "./providers/wallets.service"; import { AuthGuards } from "../../common/decorators/auth-guard.decorator"; import { Pagination } from "../../common/decorators/pagination.decorator"; -import { Roles } from "../../common/decorators/roles.decorator"; import { UserDec } from "../../common/decorators/user.decorator"; import { PaginationDto } from "../../common/DTO/pagination.dto"; -import { RoleEnum } from "../users/enums/role.enum"; @Controller("wallets") @ApiTags("wallets") @@ -16,7 +14,6 @@ export class WalletsController { @ApiOperation({ summary: "Get balance ==> user route" }) @AuthGuards() - @Roles(RoleEnum.USER) @Get("balance") getBalance(@UserDec("id") userId: string) { return this.walletsService.getBalance(userId); @@ -24,7 +21,6 @@ export class WalletsController { @ApiOperation({ summary: "get wallet transactions" }) @AuthGuards() - @Roles(RoleEnum.USER) @Pagination() @Get("transactions") getTransactions(@UserDec("id") userId: string, @Query() paginationDto: PaginationDto) {