diff --git a/prisma/migrations/20250218072627_init/migration.sql b/prisma/migrations/20250218072627_init/migration.sql deleted file mode 100644 index bb601f08..00000000 --- a/prisma/migrations/20250218072627_init/migration.sql +++ /dev/null @@ -1,168 +0,0 @@ --- CreateEnum -CREATE TYPE "RoleType" AS ENUM ('OWNER', 'ADMIN', 'USER', 'MENTOR'); - --- CreateEnum -CREATE TYPE "GenderType" AS ENUM ('MALE', 'FEMALE'); - --- CreateEnum -CREATE TYPE "ChannelType" AS ENUM ('NEGARIT', 'TELEGRAM', 'WHATSAPP', 'TWILIO'); - --- CreateEnum -CREATE TYPE "MessageType" AS ENUM ('SENT', 'RECEIVED'); - --- CreateTable -CREATE TABLE "Account" ( - "id" TEXT NOT NULL, - "name" TEXT NOT NULL, - "domain" TEXT, - "deletedAt" TIMESTAMP(3), - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" TIMESTAMP(3) NOT NULL, - - CONSTRAINT "Account_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "User" ( - "id" TEXT NOT NULL, - "name" TEXT NOT NULL, - "email" TEXT NOT NULL, - "password" TEXT NOT NULL, - "imageUrl" TEXT, - "deletedAt" TIMESTAMP(3), - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" TIMESTAMP(3) NOT NULL, - - CONSTRAINT "User_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "AccountUser" ( - "id" TEXT NOT NULL, - "userId" TEXT NOT NULL, - "roleId" TEXT NOT NULL, - "accountId" TEXT NOT NULL, - "deletedAt" TIMESTAMP(3), - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" TIMESTAMP(3) NOT NULL, - - CONSTRAINT "AccountUser_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "Mentor" ( - "id" TEXT NOT NULL, - "accountId" TEXT NOT NULL, - "name" TEXT NOT NULL, - "email" TEXT NOT NULL, - "expertise" TEXT, - "availability" JSONB, - "age" INTEGER, - "gender" "GenderType" NOT NULL DEFAULT 'MALE', - "location" TEXT, - "isActive" BOOLEAN NOT NULL DEFAULT true, - "deletedAt" TIMESTAMP(3), - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" TIMESTAMP(3) NOT NULL, - - CONSTRAINT "Mentor_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "Conversation" ( - "id" TEXT NOT NULL, - "mentorId" TEXT NOT NULL, - "address" TEXT NOT NULL, - "channelId" TEXT NOT NULL, - "isActive" BOOLEAN NOT NULL, - - CONSTRAINT "Conversation_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "Role" ( - "id" TEXT NOT NULL, - "name" TEXT NOT NULL, - "isDefault" BOOLEAN NOT NULL DEFAULT false, - "type" "RoleType" NOT NULL, - "accountId" TEXT, - "deletedAt" TIMESTAMP(3), - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" TIMESTAMP(3) NOT NULL, - - CONSTRAINT "Role_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "Thread" ( - "id" TEXT NOT NULL, - "conversationId" TEXT NOT NULL, - "messageId" TEXT NOT NULL, - - CONSTRAINT "Thread_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "Channel" ( - "id" TEXT NOT NULL, - "accountId" TEXT NOT NULL, - "name" TEXT NOT NULL, - "type" "ChannelType" NOT NULL DEFAULT 'NEGARIT', - "configuration" JSONB, - "isOn" BOOLEAN NOT NULL DEFAULT false, - "deletedAt" TIMESTAMP(3), - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" TIMESTAMP(3) NOT NULL, - - CONSTRAINT "Channel_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "Message" ( - "id" TEXT NOT NULL, - "channelId" TEXT NOT NULL, - "address" TEXT NOT NULL, - "type" "MessageType" NOT NULL, - "body" TEXT NOT NULL, - "deletedAt" TIMESTAMP(3), - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" TIMESTAMP(3) NOT NULL, - - CONSTRAINT "Message_pkey" PRIMARY KEY ("id") -); - --- CreateIndex -CREATE UNIQUE INDEX "User_email_key" ON "User"("email"); - --- AddForeignKey -ALTER TABLE "AccountUser" ADD CONSTRAINT "AccountUser_accountId_fkey" FOREIGN KEY ("accountId") REFERENCES "Account"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "AccountUser" ADD CONSTRAINT "AccountUser_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "AccountUser" ADD CONSTRAINT "AccountUser_roleId_fkey" FOREIGN KEY ("roleId") REFERENCES "Role"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "Mentor" ADD CONSTRAINT "Mentor_accountId_fkey" FOREIGN KEY ("accountId") REFERENCES "Account"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "Conversation" ADD CONSTRAINT "Conversation_mentorId_fkey" FOREIGN KEY ("mentorId") REFERENCES "Mentor"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "Conversation" ADD CONSTRAINT "Conversation_channelId_fkey" FOREIGN KEY ("channelId") REFERENCES "Channel"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "Role" ADD CONSTRAINT "Role_accountId_fkey" FOREIGN KEY ("accountId") REFERENCES "Account"("id") ON DELETE SET NULL ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "Thread" ADD CONSTRAINT "Thread_conversationId_fkey" FOREIGN KEY ("conversationId") REFERENCES "Conversation"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "Thread" ADD CONSTRAINT "Thread_messageId_fkey" FOREIGN KEY ("messageId") REFERENCES "Message"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "Channel" ADD CONSTRAINT "Channel_accountId_fkey" FOREIGN KEY ("accountId") REFERENCES "Account"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "Message" ADD CONSTRAINT "Message_channelId_fkey" FOREIGN KEY ("channelId") REFERENCES "Channel"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 769b997a..82c72379 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -60,6 +60,7 @@ model AccountUser { userId String roleId String accountId String + isActive Boolean @default(true) deletedAt DateTime? createdAt DateTime @default(now()) diff --git a/prisma/seed.ts b/prisma/seed.ts index 603766b1..11bb5227 100644 --- a/prisma/seed.ts +++ b/prisma/seed.ts @@ -18,6 +18,11 @@ async function seedBatchOne() { type: RoleType.MENTOR, isDefault: true, }, + { + name: 'Admin', + type: RoleType.ADMIN, + isDefault: true, + }, ], skipDuplicates: true, // Prevent duplicate seeding }); diff --git a/src/app.module.ts b/src/app.module.ts index f4ed7a4a..fc174e54 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -8,6 +8,7 @@ import { MentorModule } from './modules/mentor/mentor.module'; import { ChatModule } from './modules/chat/chat.module'; import { RabbitmqModule } from './common/rabbitmq/rabbitmq.module'; import { MessageModule } from './modules/message/message.module'; +import { RoleModule } from './modules/admin/role/role.module'; @Module({ imports: [ @@ -25,6 +26,7 @@ import { MessageModule } from './modules/message/message.module'; ChatModule, RabbitmqModule, MessageModule, + RoleModule, ], providers: [PrismaService], controllers: [], diff --git a/src/modules/admin/channel/channel.controller.ts b/src/modules/admin/channel/channel.controller.ts index d8b2ca64..8ce281c3 100644 --- a/src/modules/admin/channel/channel.controller.ts +++ b/src/modules/admin/channel/channel.controller.ts @@ -16,10 +16,11 @@ import { UpdateChannelDto } from './dto/update-channel.dto'; import { GetChannelDto } from './dto/get-channel.dto'; import { Roles } from 'src/modules/auth/auth.decorator'; import { AuthGuard } from 'src/modules/auth/guard/auth/auth.guard'; +import { RoleGuard } from 'src/modules/auth/guard/role/role.guard'; @Controller('admin/channel') -@UseGuards(AuthGuard) -@Roles('OWNER') +@UseGuards(AuthGuard, RoleGuard) +@Roles('OWNER', 'ADMIN') export class ChannelController { constructor(private readonly channelService: ChannelService) {} diff --git a/src/modules/admin/mentor/mentor.controller.ts b/src/modules/admin/mentor/mentor.controller.ts index 71e1c1b5..6d1736d4 100644 --- a/src/modules/admin/mentor/mentor.controller.ts +++ b/src/modules/admin/mentor/mentor.controller.ts @@ -13,13 +13,14 @@ import { import { MentorService } from './mentor.service'; import { Roles } from 'src/modules/auth/auth.decorator'; import { AuthGuard } from 'src/modules/auth/guard/auth/auth.guard'; +import { RoleGuard } from 'src/modules/auth/guard/role/role.guard'; import { GetMentorDto } from './dto/get-mentor.dto'; import { CreateMentorDto } from './dto/create-mentor.dto'; import { UpdateMentorDto } from './dto/update-mentor.dto'; @Controller('admin/mentor') -@UseGuards(AuthGuard) -@Roles('OWNER') +@UseGuards(AuthGuard, RoleGuard) +@Roles('OWNER', 'ADMIN') export class MentorController { constructor(private readonly mentorService: MentorService) {} diff --git a/src/modules/admin/role/dto/create-role.dto.ts b/src/modules/admin/role/dto/create-role.dto.ts new file mode 100644 index 00000000..3044c2b0 --- /dev/null +++ b/src/modules/admin/role/dto/create-role.dto.ts @@ -0,0 +1 @@ +export class CreateRoleDto {} diff --git a/src/modules/admin/role/dto/role.dto.ts b/src/modules/admin/role/dto/role.dto.ts new file mode 100644 index 00000000..f24fe4b3 --- /dev/null +++ b/src/modules/admin/role/dto/role.dto.ts @@ -0,0 +1,20 @@ +import { Expose } from 'class-transformer'; + +export class RoleDto { + @Expose() + id: string; + + @Expose() + name: string; + + @Expose() + accountId: string; + + constructor(partial: Partial) { + Object.assign(this, { + id: partial.id, + name: partial.name, + accountId: partial.accountId, + }); + } +} diff --git a/src/modules/admin/role/dto/update-role.dto.ts b/src/modules/admin/role/dto/update-role.dto.ts new file mode 100644 index 00000000..10e9f33c --- /dev/null +++ b/src/modules/admin/role/dto/update-role.dto.ts @@ -0,0 +1,4 @@ +import { PartialType } from '@nestjs/mapped-types'; +import { CreateRoleDto } from './create-role.dto'; + +export class UpdateRoleDto extends PartialType(CreateRoleDto) {} diff --git a/src/modules/admin/role/entities/role.entity.ts b/src/modules/admin/role/entities/role.entity.ts new file mode 100644 index 00000000..ec816d51 --- /dev/null +++ b/src/modules/admin/role/entities/role.entity.ts @@ -0,0 +1 @@ +export class Role {} diff --git a/src/modules/admin/role/role.controller.spec.ts b/src/modules/admin/role/role.controller.spec.ts new file mode 100644 index 00000000..40763c60 --- /dev/null +++ b/src/modules/admin/role/role.controller.spec.ts @@ -0,0 +1,20 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { RolesController } from './role.controller'; +import { RoleService } from './role.service'; + +describe('RolesController', () => { + let controller: RolesController; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [RolesController], + providers: [RoleService], + }).compile(); + + controller = module.get(RolesController); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); +}); diff --git a/src/modules/admin/role/role.controller.ts b/src/modules/admin/role/role.controller.ts new file mode 100644 index 00000000..041718bc --- /dev/null +++ b/src/modules/admin/role/role.controller.ts @@ -0,0 +1,18 @@ +import { Controller, Get, UseGuards } from '@nestjs/common'; +import { RoleGuard } from '../../auth/guard/role/role.guard'; +import { Roles } from '../../auth/auth.decorator'; +import { RoleService } from './role.service'; +import { AuthGuard } from 'src/modules/auth/guard/auth/auth.guard'; + +@Controller('roles') +@UseGuards(AuthGuard) +export class RolesController { + constructor(private readonly roleService: RoleService) {} + + @Get('') + @UseGuards(RoleGuard) + @Roles('OWNER', 'ADMIN') + findAll() { + return this.roleService.findAll(); + } +} diff --git a/src/modules/admin/role/role.module.ts b/src/modules/admin/role/role.module.ts new file mode 100644 index 00000000..6bcf13e2 --- /dev/null +++ b/src/modules/admin/role/role.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common'; +import { RoleService } from './role.service'; +import { RolesController } from './role.controller'; +import { PrismaService } from 'src/modules/prisma/prisma.service'; +import { JwtService } from '@nestjs/jwt'; + +@Module({ + controllers: [RolesController], + providers: [RoleService, PrismaService, JwtService], +}) +export class RoleModule {} diff --git a/src/modules/admin/role/role.service.spec.ts b/src/modules/admin/role/role.service.spec.ts new file mode 100644 index 00000000..e1e0c008 --- /dev/null +++ b/src/modules/admin/role/role.service.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { RoleService } from './role.service'; + +describe('RoleService', () => { + let service: RoleService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [RoleService], + }).compile(); + + service = module.get(RoleService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/src/modules/admin/role/role.service.ts b/src/modules/admin/role/role.service.ts new file mode 100644 index 00000000..2100ad53 --- /dev/null +++ b/src/modules/admin/role/role.service.ts @@ -0,0 +1,19 @@ +import { Injectable } from '@nestjs/common'; +import { PrismaService } from '../../prisma/prisma.service'; +import { RoleDto } from './dto/role.dto'; + +@Injectable() +export class RoleService { + constructor(private readonly prisma: PrismaService) {} + + async findAll(): Promise { + const roles = await this.prisma.role.findMany({ + select: { + id: true, + name: true, + accountId: true, + }, + }); + return roles.map((role) => new RoleDto(role)); + } +} diff --git a/src/modules/admin/user/dto/create-user.dto.ts b/src/modules/admin/user/dto/create-user.dto.ts index 9850a294..9cd6c737 100644 --- a/src/modules/admin/user/dto/create-user.dto.ts +++ b/src/modules/admin/user/dto/create-user.dto.ts @@ -1,4 +1,4 @@ -import { IsEmail, IsString } from 'class-validator'; +import { IsEmail, IsOptional, IsString } from 'class-validator'; export class CreateUserDto { @IsString() @@ -10,5 +10,6 @@ export class CreateUserDto { @IsEmail() email: string; @IsString() + @IsOptional() password: string; } diff --git a/src/modules/admin/user/dto/get-all-users-query.dto.ts b/src/modules/admin/user/dto/get-all-users-query.dto.ts new file mode 100644 index 00000000..22e66509 --- /dev/null +++ b/src/modules/admin/user/dto/get-all-users-query.dto.ts @@ -0,0 +1,24 @@ +import { IsInt, IsOptional, Min, IsString } from 'class-validator'; +import { Type } from 'class-transformer'; + +export class GetAllUsersQueryDto { + @IsOptional() + @IsString() + accountId?: string; + + @IsOptional() + @IsString() + roleId?: string; + + @IsOptional() + @Type(() => Number) + @IsInt() + @Min(1) + page?: number = 1; + + @IsOptional() + @Type(() => Number) + @IsInt() + @Min(1) + limit?: number = 10; +} diff --git a/src/modules/admin/user/dto/user.dto.ts b/src/modules/admin/user/dto/user.dto.ts index 7ead2a95..3d884d1d 100644 --- a/src/modules/admin/user/dto/user.dto.ts +++ b/src/modules/admin/user/dto/user.dto.ts @@ -13,19 +13,25 @@ export class UserDto { @Expose() imageUrl: string; + @Expose() + role?: string; + + @Expose() + status?: boolean; + @Expose() createdAt: Date; @Expose() updatedAt: Date; - roles?: string[]; - constructor(partial: Partial) { this.id = partial.id; this.name = partial.name; this.email = partial.email; this.imageUrl = partial.imageUrl; + this.role = partial.role; + this.status = partial.status; this.createdAt = partial.createdAt; this.updatedAt = partial.updatedAt; } diff --git a/src/modules/admin/user/entities/user.entity.ts b/src/modules/admin/user/entities/user.entity.ts index b014e0e7..d86a6ea4 100644 --- a/src/modules/admin/user/entities/user.entity.ts +++ b/src/modules/admin/user/entities/user.entity.ts @@ -2,4 +2,5 @@ export class User { id: string; name: string; email: string; + accounts: any; } diff --git a/src/modules/admin/user/user.controller.ts b/src/modules/admin/user/user.controller.ts index 86198c93..86b53c5f 100644 --- a/src/modules/admin/user/user.controller.ts +++ b/src/modules/admin/user/user.controller.ts @@ -7,35 +7,48 @@ import { Patch, Post, UseGuards, + Query, + ValidationPipe, } from '@nestjs/common'; import { UserService } from './user.service'; import { CreateUserDto } from './dto/create-user.dto'; import { UpdateUserDto } from './dto/update-user.dto'; import { AuthGuard } from 'src/modules/auth/guard/auth/auth.guard'; +import { RoleGuard } from 'src/modules/auth/guard/role/role.guard'; import { Roles } from 'src/modules/auth/auth.decorator'; +import { GetAllUsersQueryDto } from './dto/get-all-users-query.dto'; @Controller('admin/user') @UseGuards(AuthGuard) -@Roles('OWNER') export class UserController { constructor(private readonly userService: UserService) {} @Post() + @UseGuards(RoleGuard) + @Roles('OWNER', 'ADMIN') create(@Body() createUserDto: CreateUserDto) { return this.userService.create(createUserDto); } - @Get(':accountId/all') - async findAll(@Param('accountId') accountId: string) { - return this.userService.findAllUsers(accountId); + @Get('all') + @UseGuards(RoleGuard) + @Roles('OWNER') + async findAll( + @Query(new ValidationPipe({ transform: true })) query: GetAllUsersQueryDto, + ) { + return this.userService.findAllUsers(query); } @Get(':accountId/user/:id') + @UseGuards(RoleGuard) + @Roles('OWNER', 'ADMIN') findOne(@Param('accountId') accountId: string, @Param('id') id: string) { return this.userService.findOne(accountId, id); } @Patch(':accountId/user/:id') + @UseGuards(RoleGuard) + @Roles('OWNER', 'ADMIN') update( @Param('accountId') accountId: string, @Param('id') id: string, @@ -45,7 +58,19 @@ export class UserController { } @Delete(':accountId/user/:id') + @UseGuards(RoleGuard) + @Roles('OWNER') remove(@Param('accountId') accountId: string, @Param('id') id: string) { return this.userService.remove(accountId, id); } + + @Patch(':userId/activate/:accountId') + @UseGuards(RoleGuard) + @Roles('OWNER') + activate( + @Param('accountId') accountId: string, + @Param('userId') userId: string, + ) { + return this.userService.toggleActiveStatus(userId, accountId); + } } diff --git a/src/modules/admin/user/user.service.ts b/src/modules/admin/user/user.service.ts index ad5f4a15..5f102608 100644 --- a/src/modules/admin/user/user.service.ts +++ b/src/modules/admin/user/user.service.ts @@ -1,18 +1,23 @@ -import { Inject, Injectable, ForbiddenException } from '@nestjs/common'; +import { + Inject, + ForbiddenException, + HttpException, + HttpStatus, +} from '@nestjs/common'; import { CreateUserDto } from './dto/create-user.dto'; import { UpdateUserDto } from './dto/update-user.dto'; import { PrismaService } from 'src/modules/prisma/prisma.service'; import { UserDto } from './dto/user.dto'; import { REQUEST } from '@nestjs/core'; -import * as bcrypt from 'bcryptjs'; -import { AuthService } from 'src/modules/auth/auth.service'; - -@Injectable() +import { paginate } from 'src/common/helpers/pagination'; +import { User, AccountUser, Role } from '@prisma/client'; // Assuming User is from Prisma model@Injectable() +import { GetAllUsersQueryDto } from './dto/get-all-users-query.dto'; export class UserService { constructor( @Inject(REQUEST) private readonly request: any, private prisma: PrismaService, ) {} + async create(createUserDto: CreateUserDto) { const account = await this.prisma.account.findUnique({ where: { id: createUserDto.accountId }, @@ -24,24 +29,54 @@ export class UserService { await this.validateAccountAccess(createUserDto.accountId); + const role = await this.prisma.role.findFirst({ + where: { + id: createUserDto.roleId, + }, + }); + + if (role?.name === 'Owner') { + throw new HttpException( + 'You cannot create a user with owner role!', + HttpStatus.FORBIDDEN, + ); + } + const existingUser = await this.prisma.user.findUnique({ where: { email: createUserDto.email }, }); if (existingUser) { - throw new Error('Email already in use'); - } + const existingRelation = await this.prisma.accountUser.findFirst({ + where: { + userId: existingUser.id, + accountId: createUserDto.accountId, + }, + }); - const hashedPassword = await bcrypt.hash( - createUserDto.password, - AuthService.saltRounds, - ); + if (existingRelation) { + throw new HttpException( + 'The user already exists in your organization!', + HttpStatus.CONFLICT, + ); + } + + await this.prisma.accountUser.create({ + data: { + userId: existingUser.id, + accountId: createUserDto.accountId, + roleId: createUserDto.roleId, + }, + }); + + return { message: 'User added to the organization successfully!' }; // Stop here + } const userData = await this.prisma.user.create({ data: { name: createUserDto.name, email: createUserDto.email, - password: hashedPassword, + password: '', AccountUser: { create: { accountId: createUserDto.accountId, @@ -81,18 +116,69 @@ export class UserService { return new UserDto(userData); } - async findAllUsers(accountId: string) { + async findAllUsers(query: GetAllUsersQueryDto) { + const { accountId, roleId, page, limit } = query; + await this.validateAccountAccess(accountId); - return this.prisma.user.findMany({ - where: { - AccountUser: { - some: { - accountId: accountId, - }, + const whereCondition = { + AccountUser: { + some: roleId + ? { + accountId: accountId, + deletedAt: null, + OR: [{ Role: { name: 'Owner' } }, { roleId: roleId }], + } + : { + accountId: accountId, + deletedAt: null, + }, + }, + }; + + const includeCondition = { + AccountUser: { + include: { + Role: true, }, }, - }); + }; + + const { data, meta } = await paginate( + this.prisma, + this.prisma.user, + whereCondition, + page, + limit, + includeCondition, + ); + + const users = data.map( + (user: User & { AccountUser: (AccountUser & { Role: Role })[] }) => { + const role = + user.AccountUser.length > 0 && user.AccountUser[0].Role + ? user.AccountUser[0].Role.name + : ''; + + const isActive = user.AccountUser[0].isActive; + + return new UserDto({ + id: user.id, + name: user.name, + email: user.email, + imageUrl: user.imageUrl, + role: role, + status: isActive, + createdAt: user.createdAt, + updatedAt: user.updatedAt, + }); + }, + ); + + return { + data: users, + meta: meta, + }; } async updateUser( @@ -130,10 +216,11 @@ export class UserService { const updatedUser = await this.prisma.user.update({ where: { id }, data: { + deletedAt: new Date(), AccountUser: { updateMany: { where: { accountId }, - data: { deletedAt: Date() }, + data: { deletedAt: new Date() }, }, }, }, @@ -157,4 +244,37 @@ export class UserService { return account; } + + async toggleActiveStatus(userId: string, accountId: string) { + const accountUser = await this.prisma.accountUser.findFirst({ + where: { + accountId: accountId, + userId: userId, + }, + include: { + Role: true, + }, + }); + + if (accountUser.Role.name === 'Owner') { + throw new HttpException( + 'You can not deactivate your own account!', + HttpStatus.CONFLICT, + ); + } + + const updatedStatus = await this.prisma.accountUser.update({ + where: { + id: accountUser.id, + }, + data: { + isActive: !accountUser.isActive, + }, + }); + + return { + message: updatedStatus.isActive ? 'Activated' : 'Deactivated', + isActive: updatedStatus.isActive, + }; + } } diff --git a/src/modules/auth/auth.service.ts b/src/modules/auth/auth.service.ts index 4c8f208c..6192aceb 100644 --- a/src/modules/auth/auth.service.ts +++ b/src/modules/auth/auth.service.ts @@ -129,7 +129,7 @@ export class AuthService { async getUserAccounts(userId: string) { const accounts = await this.prisma.account.findMany({ - where: { AccountUser: { some: { userId } } }, + where: { AccountUser: { some: { userId, isActive: true } } }, select: { id: true, name: true, diff --git a/src/modules/auth/guard/auth/auth.guard.ts b/src/modules/auth/guard/auth/auth.guard.ts index bb905c49..7f917f91 100644 --- a/src/modules/auth/guard/auth/auth.guard.ts +++ b/src/modules/auth/guard/auth/auth.guard.ts @@ -31,7 +31,11 @@ export class AuthGuard implements CanActivate { const user = await this.jwtService.verifyAsync(token, options); - request.user = user; + request.user = { + ...user, + id: user.sub, + }; + return true; } catch (error) { console.error(`AuthGuard Error: ${error.message}`); diff --git a/src/modules/auth/guard/role/role.guard.ts b/src/modules/auth/guard/role/role.guard.ts index da5533fb..6bc5cb7b 100644 --- a/src/modules/auth/guard/role/role.guard.ts +++ b/src/modules/auth/guard/role/role.guard.ts @@ -2,7 +2,6 @@ import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; import { Observable } from 'rxjs'; import { ROLE_KEY } from '../../auth.decorator'; -import { User } from 'src/modules/admin/user/entities/user.entity'; @Injectable() export class RoleGuard implements CanActivate { @@ -14,9 +13,14 @@ export class RoleGuard implements CanActivate { context.getHandler(), context.getClass(), ]); + console.log(requiredRoles); - const user: User = context.switchToHttp().getRequest().user; + const user = context.switchToHttp().getRequest().user; - return requiredRoles.some((role) => user.name === role); + return user.accounts?.some((account) => + requiredRoles.some( + (role) => account.role?.name.toLowerCase() === role.toLowerCase(), + ), + ); } }