From cefddfeeda7f362e077d67f56a2328ab6c447ad8 Mon Sep 17 00:00:00 2001 From: realrafi Date: Fri, 24 Jul 2026 20:39:10 +0330 Subject: [PATCH] feat: added user role --- src/auth/auth.module.ts | 3 +- src/common/enums/messages.enum.ts | 4 ++ src/users/DTO/create-user-role.dto.ts | 12 +++++ src/users/entities/user-role.entity.ts | 22 ++++++++ src/users/entities/user.entity.ts | 51 ++++++++++--------- src/users/enums/user-role.enum.ts | 5 ++ .../repositories/user-role.repository.ts | 17 +++++++ .../{ => repositories}/user.repository.ts | 2 +- src/users/users.controller.ts | 11 +++- src/users/users.module.ts | 12 ++--- src/users/users.service.ts | 40 +++++++++++++-- 11 files changed, 144 insertions(+), 35 deletions(-) create mode 100644 src/users/DTO/create-user-role.dto.ts create mode 100644 src/users/entities/user-role.entity.ts create mode 100644 src/users/enums/user-role.enum.ts create mode 100644 src/users/repositories/user-role.repository.ts rename src/users/{ => repositories}/user.repository.ts (88%) diff --git a/src/auth/auth.module.ts b/src/auth/auth.module.ts index a315c23..9cb9bbe 100644 --- a/src/auth/auth.module.ts +++ b/src/auth/auth.module.ts @@ -9,10 +9,11 @@ import { OTP } from './entities/otp.entity'; import { OtpRepository } from './repositories/otp.repository'; import { UsersModule } from 'src/users/users.module'; import { ConfigService } from '@nestjs/config'; +import { UserRole } from 'src/users/entities/user-role.entity'; @Module({ imports: [ - TypeOrmModule.forFeature([User, OTP]), + TypeOrmModule.forFeature([OTP]), JwtModule.register({}), UtilsModule, UsersModule, diff --git a/src/common/enums/messages.enum.ts b/src/common/enums/messages.enum.ts index ebe97c3..0d42008 100644 --- a/src/common/enums/messages.enum.ts +++ b/src/common/enums/messages.enum.ts @@ -10,4 +10,8 @@ export enum OTPMessages { OTP_EXPIRED = 'این کد منسوخ شده است لطفا دوباره درخواست بدهید!', OTP_TOO_MANY_TRIES = 'تعداد تلاش ها بیش از حد مجاز است لطفا دوباره درخواست کد بدهید!', OTP_INVALID = 'کد وارد شده نا معتبر است!' +} + +export enum UserRoleMessages { + USER_ROLE_ALREADY_EXISTS = 'نقش مورد نظر برای کاربر مد نظر وجود دارد!' } \ No newline at end of file diff --git a/src/users/DTO/create-user-role.dto.ts b/src/users/DTO/create-user-role.dto.ts new file mode 100644 index 0000000..5808bf2 --- /dev/null +++ b/src/users/DTO/create-user-role.dto.ts @@ -0,0 +1,12 @@ +import { ApiProperty } from "@nestjs/swagger"; +import { IsEnum } from "class-validator"; +import { RoleType } from "../enums/user-role.enum"; + +export class CreateUserRoleDto { + @ApiProperty({ + enum: RoleType, + example: RoleType.NORMAL_USER + }) + @IsEnum(RoleType) + role: RoleType; +} \ No newline at end of file diff --git a/src/users/entities/user-role.entity.ts b/src/users/entities/user-role.entity.ts new file mode 100644 index 0000000..5c4e725 --- /dev/null +++ b/src/users/entities/user-role.entity.ts @@ -0,0 +1,22 @@ +import { BaseEntity } from "src/common/entities/base.entity"; +import { Column, Entity, JoinColumn, ManyToOne, Unique } from "typeorm"; +import { RoleType } from "../enums/user-role.enum"; +import { User } from "./user.entity"; + +@Entity('user_roles') +@Unique(['user', 'role']) +export class UserRole extends BaseEntity { + @Column({ + type: 'enum', + enum: RoleType, + default: RoleType.NORMAL_USER + }) + role: RoleType; + + @ManyToOne(() => User, (user) => user.roles, { + nullable: false, + onDelete: 'CASCADE' + }) + @JoinColumn({ name: 'userId' }) + user: User; +} \ No newline at end of file diff --git a/src/users/entities/user.entity.ts b/src/users/entities/user.entity.ts index b1830b4..af304de 100644 --- a/src/users/entities/user.entity.ts +++ b/src/users/entities/user.entity.ts @@ -1,31 +1,36 @@ -import { Entity, Column } from "typeorm"; -import { BaseEntity } from "src/common/entities/base.entity"; -import { Exclude } from "class-transformer"; +import { Entity, Column, ManyToMany, JoinTable, OneToMany } from 'typeorm'; +import { BaseEntity } from 'src/common/entities/base.entity'; +import { Exclude } from 'class-transformer'; +import { UserRole } from './user-role.entity'; @Entity('users') export class User extends BaseEntity { - @Column({type: 'varchar', length: 20}) - firstName: string; + @Column({ type: 'varchar', length: 20 }) + firstName: string; - @Column({type: 'varchar', length: 20}) - lastName: string; + @Column({ type: 'varchar', length: 20 }) + lastName: string; - @Column({unique: true, type: 'varchar', length: 100}) - email: string; + @Column({ unique: true, type: 'varchar', length: 100 }) + email: string; - @Column({unique: true, type: 'varchar', length: 11}) - phone: string; + @Column({ unique: true, type: 'varchar', length: 11 }) + phone: string; - @Column({ - }) - @Exclude() - password: string; + @Column({}) + @Exclude() + password: string; - @Column({ - name: 'refresh_token', - nullable: true, - select: false - }) - @Exclude() - refreshToken: string; -} \ No newline at end of file + @Column({ + name: 'refresh_token', + nullable: true, + select: false, + }) + @Exclude() + refreshToken: string; + + @OneToMany(() => UserRole, (userRole) => userRole.user, { + cascade: true + }) + roles: UserRole[]; +} diff --git a/src/users/enums/user-role.enum.ts b/src/users/enums/user-role.enum.ts new file mode 100644 index 0000000..1bbce52 --- /dev/null +++ b/src/users/enums/user-role.enum.ts @@ -0,0 +1,5 @@ +export enum RoleType { + NORMAL_USER = 'normal_user', + ADMIN = 'admin', + EXPERT = 'expert' +} \ No newline at end of file diff --git a/src/users/repositories/user-role.repository.ts b/src/users/repositories/user-role.repository.ts new file mode 100644 index 0000000..f54da95 --- /dev/null +++ b/src/users/repositories/user-role.repository.ts @@ -0,0 +1,17 @@ +import { Repository } from 'typeorm'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Injectable } from '@nestjs/common'; +import { UserRole } from '../entities/user-role.entity'; + +@Injectable() +export class UserRoleRepository extends Repository { + constructor( + @InjectRepository(UserRole) userRoleRepository: Repository, + ) { + super( + userRoleRepository.target, + userRoleRepository.manager, + userRoleRepository.queryRunner, + ); + } +} diff --git a/src/users/user.repository.ts b/src/users/repositories/user.repository.ts similarity index 88% rename from src/users/user.repository.ts rename to src/users/repositories/user.repository.ts index e83ab3d..b4a8e09 100644 --- a/src/users/user.repository.ts +++ b/src/users/repositories/user.repository.ts @@ -1,5 +1,5 @@ import { Repository } from "typeorm"; -import { User } from "./entities/user.entity"; +import { User } from "../entities/user.entity"; import { InjectRepository } from "@nestjs/typeorm"; import { Injectable } from "@nestjs/common"; diff --git a/src/users/users.controller.ts b/src/users/users.controller.ts index 9d0e611..15886ab 100644 --- a/src/users/users.controller.ts +++ b/src/users/users.controller.ts @@ -1,8 +1,9 @@ -import { Body, Controller, Get, Post } from '@nestjs/common'; +import { Body, Controller, Get, Param, Post } from '@nestjs/common'; import { ApiOperation, ApiResponse } from '@nestjs/swagger'; import { CreateUserDto } from './DTO/create-user.dto'; import { User } from './entities/user.entity'; import { UsersService } from './users.service'; +import { CreateUserRoleDto } from './DTO/create-user-role.dto'; @Controller('users') export class UsersController { @@ -18,4 +19,12 @@ export class UsersController { signupUser(@Body() body: CreateUserDto): Promise { return this.userService.createUser(body); } + + @Post(':userId/roles') + createRole( + @Param('userId') userId: string, + @Body() userRoleDto: CreateUserRoleDto + ) { + return this.userService.createUserRole(userId, userRoleDto); + } } diff --git a/src/users/users.module.ts b/src/users/users.module.ts index 1323e5a..b7ca1fa 100644 --- a/src/users/users.module.ts +++ b/src/users/users.module.ts @@ -1,16 +1,16 @@ import { Module } from '@nestjs/common'; import { UsersController } from './users.controller'; import { UsersService } from './users.service'; -import { UserRepository } from './user.repository'; +import { UserRepository } from './repositories/user.repository'; import { TypeOrmModule } from '@nestjs/typeorm'; import { User } from './entities/user.entity'; +import { UserRoleRepository } from './repositories/user-role.repository'; +import { UserRole } from './entities/user-role.entity'; @Module({ - imports: [ - TypeOrmModule.forFeature([User]) - ], + imports: [TypeOrmModule.forFeature([User, UserRole])], controllers: [UsersController], - providers: [UsersService, UserRepository], - exports: [UsersService] + providers: [UsersService, UserRepository, UserRoleRepository], + exports: [UsersService], }) export class UsersModule {} diff --git a/src/users/users.service.ts b/src/users/users.service.ts index a224b6b..92bc62d 100644 --- a/src/users/users.service.ts +++ b/src/users/users.service.ts @@ -3,15 +3,21 @@ import { Injectable, NotFoundException, } from '@nestjs/common'; -import { UserRepository } from './user.repository'; +import { UserRepository } from './repositories/user.repository'; import { User } from './entities/user.entity'; import * as bcrypt from 'bcrypt'; import { CreateUserDto } from './DTO/create-user.dto'; -import { UserMessages } from 'src/common/enums/messages.enum'; +import { UserMessages, UserRoleMessages } from 'src/common/enums/messages.enum'; +import { UserRole } from './entities/user-role.entity'; +import { CreateUserRoleDto } from './DTO/create-user-role.dto'; +import { UserRoleRepository } from './repositories/user-role.repository'; @Injectable() export class UsersService { - constructor(private readonly userRepository: UserRepository) {} + constructor( + private readonly userRepository: UserRepository, + private readonly userRoleRepository: UserRoleRepository, + ) {} async findOneOrFail(id: string): Promise { const user = await this.userRepository.findOne({ where: { id } }); @@ -59,4 +65,32 @@ export class UsersService { return user; } + + async createUserRole( + userId: string, + userRoleDto: CreateUserRoleDto, + ): Promise { + const existingRole = await this.userRoleRepository.findOne({ + where: { + user: { + id: userId, + }, + role: userRoleDto.role, + }, + }); + + if (existingRole) + throw new BadRequestException(UserRoleMessages.USER_ROLE_ALREADY_EXISTS); + + const userRole = this.userRoleRepository.create({ + user: { + id: userId, + }, + role: userRoleDto.role, + }); + + await this.userRoleRepository.save(userRole); + + return userRole; + } }