Skip to content
Open
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
3e83779
refactor: improve role comparison logic in RoleGuard
Bikilaketema Mar 27, 2025
7d96024
fix es-lint error
Bikilaketema Mar 27, 2025
8dc7783
add Admin role to seed data
Bikilaketema Mar 27, 2025
e7693d8
refactor: remove unused User import in RoleGuard
Bikilaketema Mar 27, 2025
b3610a9
Merge branch 'bug/fixed-role-guard-problem' into feature/allow-admin-…
Bikilaketema Mar 27, 2025
6bc26f6
feat: enhance admin access by adding RoleGuard and supporting ADMIN r…
Bikilaketema Mar 27, 2025
43b9f7d
add isActive field to User model with default value true
Bikilaketema Mar 27, 2025
5f0af5a
Merge branch 'feature/added-admin-role-to-seed.ts' into feature/add-g…
Bikilaketema Mar 27, 2025
55c221f
Merge branch 'bug/fixed-role-guard-problem' into feature/add-get-and-…
Bikilaketema Mar 27, 2025
7a8a033
Merge branch 'feature/allow-admin-to-manage-mentors-and-channels' int…
Bikilaketema Mar 27, 2025
5366a8f
feat: add AdminProfile module with controller, service, and DTOs for …
Bikilaketema Mar 27, 2025
418a3d5
refactor: clean up code formatting and improve readability in AdminPr…
Bikilaketema Mar 27, 2025
53f3ae4
fix: improve role guard logic to support multiple user accounts
Bikilaketema Mar 28, 2025
4bafee3
feat: enhance admin profile management with account-based user activa…
Bikilaketema Mar 28, 2025
0cf541c
feat: update User entity to include optional fields and enhance role …
Bikilaketema Mar 28, 2025
ce94153
fix: remove debug logging from RoleGuard to clean up code
Bikilaketema Mar 28, 2025
bd8fba2
fix: change User entity id field from optional to required
Bikilaketema Mar 28, 2025
918715b
fix: update User entity to remove optional sub field and adjust AuthG…
Bikilaketema Mar 28, 2025
b5f4937
feat: enhance RoleGuard to throw exceptions for inactive accounts and…
Bikilaketema Mar 28, 2025
90bccfe
fix: remove debug logging from RoleGuard to improve code clarity
Bikilaketema Mar 28, 2025
3805a22
fix: update route parameter for findAll method in AdminProfileController
Bikilaketema Mar 29, 2025
faa2e05
feat: add Role module with controller, service, and DTOs for role man…
Bikilaketema Mar 29, 2025
a7dcb2b
feat: implement AdminProfile module with controller, service, DTOs, a…
Bikilaketema Mar 29, 2025
987fcb8
changed adminProfile to profile
Bikilaketema Apr 1, 2025
4765c49
refactor: rename AdminDto to ProfileDto and update roleName to role
Bikilaketema Apr 1, 2025
6d76a6c
fix es lint
Bikilaketema Apr 1, 2025
14c82bb
refactor: remove unnecessary comment from ProfileDto file
Bikilaketema Apr 1, 2025
ef70806
refactor: update ProfileService to use ProfileDto and add RoleDto for…
Bikilaketema Apr 1, 2025
e0c87ce
refactor: format code in RoleService for consistency
Bikilaketema Apr 1, 2025
7316949
refactor: rename RoleController to RolesController for consistency
Bikilaketema Apr 1, 2025
5e5e9f1
refactor: enhance ProfileService to filter by Admin role in account u…
Bikilaketema Apr 1, 2025
b61149e
removed the profile module and moved all logics to the user in admin …
Bikilaketema Apr 1, 2025
285b592
removed the admin profile
Bikilaketema Apr 1, 2025
04a939d
added the owner to returned admins list
Bikilaketema Apr 1, 2025
ec8f1b5
change the some to filter on active acccounts
Bikilaketema Apr 1, 2025
3324d52
fixed pr-comments
Bikilaketema Apr 2, 2025
9700cfb
fixed es lint error
Bikilaketema Apr 2, 2025
73fa9c8
dis-allwoed the owner from deactivating it's own account
Bikilaketema Apr 2, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ model AccountUser {
userId String
roleId String
accountId String
isActive Boolean @default(true)

deletedAt DateTime?
createdAt DateTime @default(now())
Expand Down
5 changes: 5 additions & 0 deletions prisma/seed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ async function seedBatchOne() {
type: RoleType.MENTOR,
isDefault: true,
},
{
name: 'Admin',
type: RoleType.ADMIN,
isDefault: true,
},
],
skipDuplicates: true, // Prevent duplicate seeding
});
Expand Down
4 changes: 4 additions & 0 deletions src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ 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 { AdminProfileModule } from './modules/admin/adminProfile/admin-profile.module';
import { RoleModule } from './modules/admin/role/role.module';

@Module({
imports: [
Expand All @@ -25,6 +27,8 @@ import { MessageModule } from './modules/message/message.module';
ChatModule,
RabbitmqModule,
MessageModule,
AdminProfileModule,
RoleModule,
],
providers: [PrismaService],
controllers: [],
Expand Down
20 changes: 20 additions & 0 deletions src/modules/admin/adminProfile/admin-profile.controller.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Test, TestingModule } from '@nestjs/testing';
import { AdminProfileController } from './admin-profile.controller';
import { AdminProfileService } from './admin-profile.service';

describe('AdminProfileController', () => {
let controller: AdminProfileController;

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [AdminProfileController],
providers: [AdminProfileService],
}).compile();

controller = module.get<AdminProfileController>(AdminProfileController);
});

it('should be defined', () => {
expect(controller).toBeDefined();
});
});
49 changes: 49 additions & 0 deletions src/modules/admin/adminProfile/admin-profile.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { Controller, Get, Body, Patch, Param, UseGuards } from '@nestjs/common';
import { AuthGuard } from 'src/modules/auth/guard/auth/auth.guard';
import { AdminProfileService } from './admin-profile.service';
import { UpdateAdminProfileDto } from './dto/update-admin-profile.dto';
import { RoleGuard } from 'src/modules/auth/guard/role/role.guard';
import { Roles } from '../../auth/auth.decorator';

@Controller('admin/profile')
@UseGuards(AuthGuard)
export class AdminProfileController {
constructor(private readonly adminProfileService: AdminProfileService) {}

@Get(':accountId/:userId/')
@UseGuards(RoleGuard)
@Roles('OWNER', 'ADMIN')
findOne(
@Param('userId') userId: string,
@Param('accountId') accountId: string,
) {
return this.adminProfileService.findOne(userId, accountId);
}

@Patch(':id')
@UseGuards(RoleGuard)
@Roles('OWNER', 'ADMIN')
update(
@Param('id') id: string,
@Body() updateAdminProfileDto: UpdateAdminProfileDto,
) {
return this.adminProfileService.update(id, updateAdminProfileDto);
}

@Get(':accountId')
@UseGuards(RoleGuard)
@Roles('OWNER')
findAll(@Param('accountId') accountId) {
return this.adminProfileService.findAll(accountId);
}

@Patch(':userId/activate/:accountId')
@UseGuards(RoleGuard)
@Roles('OWNER')
activate(
@Param('accountId') accountId: string,
@Param('userId') userId: string,
) {
return this.adminProfileService.toggleActiveStatus(accountId, userId);
}
}
11 changes: 11 additions & 0 deletions src/modules/admin/adminProfile/admin-profile.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Module } from '@nestjs/common';
import { AdminProfileService } from './admin-profile.service';
import { AdminProfileController } from './admin-profile.controller';
import { PrismaService } from '../../prisma/prisma.service';
import { JwtService } from '@nestjs/jwt';

@Module({
controllers: [AdminProfileController],
providers: [AdminProfileService, PrismaService, JwtService],
})
export class AdminProfileModule {}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this module is under Admin module you dont need to name is as "AdminProfileModule"
rename this module as "ProfileModule"

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have removed all the module and moved the logic to the user

18 changes: 18 additions & 0 deletions src/modules/admin/adminProfile/admin-profile.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { AdminProfileService } from './admin-profile.service';

describe('AdminProfileService', () => {
let service: AdminProfileService;

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [AdminProfileService],
}).compile();

service = module.get<AdminProfileService>(AdminProfileService);
});

it('should be defined', () => {
expect(service).toBeDefined();
});
});
167 changes: 167 additions & 0 deletions src/modules/admin/adminProfile/admin-profile.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
import { Injectable, NotFoundException } from '@nestjs/common';
import { PrismaService } from 'src/modules/prisma/prisma.service';
import { AdminDto } from './dto/admin.dto';
import { UpdateAdminProfileDto } from './dto/update-admin-profile.dto';

@Injectable()
export class AdminProfileService {
constructor(private prisma: PrismaService) {}

async findOne(userId: string, accountId: string): Promise<AdminDto> {
const admin = await this.prisma.user.findFirst({
where: {
id: userId,
deletedAt: null,
},
include: {
AccountUser: {
where: {
accountId: accountId,
},
},
},
});

if (!admin) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • i suggest if we manage such validation using interface before reaching our service class

throw new NotFoundException('Admin not found');
}

const accountUser = admin.AccountUser.find(
(au) => au.accountId === accountId,
);

return new AdminDto({
id: admin.id,
name: admin.name,
email: admin.email,
isActive: accountUser?.isActive,
});
}

async update(
id: string,
updateAdminProfileDto: UpdateAdminProfileDto,
): Promise<AdminDto> {
console.log('Updating admin with ID:', id);

const existingAdmin = await this.prisma.user.findFirst({
where: {
id: id,
deletedAt: null,
},
});

if (!existingAdmin) {
throw new NotFoundException('Admin not found');
}

const updatedAdmin = await this.prisma.user.update({
where: { id: id },
data: {
...updateAdminProfileDto,
},
});

console.log('Updated admin:', updatedAdmin);

return new AdminDto({ ...updatedAdmin });
}

async findAll(accountId: string): Promise<AdminDto[]> {
const admins = await this.prisma.user.findMany({
where: {
deletedAt: null,
OR: [
{
AccountUser: {
some: {
accountId: accountId,
Role: {
name: 'Admin',
},
},
},
},
{
AccountUser: {
some: {
accountId: accountId,
Role: {
name: 'Owner',
},
},
},
},
],
},
include: {
AccountUser: {
include: {
Role: true,
},
},
},
});

console.log('Filtered Admin Data:', JSON.stringify(admins, null, 2));

return admins.map((admin) => {
const accountUser = admin.AccountUser.find(
(au) => au.accountId === accountId,
);

return new AdminDto({
id: admin.id,
name: admin.name,
email: admin.email,
isActive: accountUser?.isActive,
});
});
}

async toggleActiveStatus(
accountId: string,
userId: string,
): Promise<AdminDto> {
const accountUser = await this.prisma.accountUser.findFirst({
where: {
accountId: accountId,
userId: userId,
},
});

const updatedAccountUser = await this.prisma.accountUser.update({
where: {
id: accountUser.id,
},
data: {
isActive: !accountUser.isActive,
},
});

const updatedUser = await this.prisma.user.findFirst({
where: {
id: userId,
deletedAt: null,
},
include: {
AccountUser: {
where: {
accountId: accountId,
},
},
},
});

if (!updatedUser) {
throw new NotFoundException('User not found');
}

return new AdminDto({
id: updatedUser.id,
name: updatedUser.name,
email: updatedUser.email,
isActive: updatedAccountUser.isActive,
});
}
}
34 changes: 34 additions & 0 deletions src/modules/admin/adminProfile/dto/admin.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// src/posts/dto/post.dto.ts

import { Expose } from 'class-transformer';

export class AdminDto {
@Expose()
id: string;

@Expose()
name: string;

@Expose()
email: string;

@Expose()
isActive: boolean;

@Expose()
AccountUser: any;

@Expose()
roleName: string; // Add the roleName field

constructor(partial: Partial<AdminDto>) {
// console.log('partial', partial);
Object.assign(this, {
id: partial.id,
name: partial.name,
email: partial.email,
isActive: partial.isActive,
AccountUser: partial.AccountUser,
});
}
}
15 changes: 15 additions & 0 deletions src/modules/admin/adminProfile/dto/update-admin-profile.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { IsOptional, IsString } from 'class-validator';

export class UpdateAdminProfileDto {
@IsOptional()
@IsString()
name?: string;

@IsOptional()
@IsString()
email?: string;

@IsOptional()
@IsString()
password?: string;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export class AdminProfile {}
5 changes: 3 additions & 2 deletions src/modules/admin/channel/channel.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {}

Expand Down
5 changes: 3 additions & 2 deletions src/modules/admin/mentor/mentor.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {}

Expand Down
1 change: 1 addition & 0 deletions src/modules/admin/role/dto/create-role.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export class CreateRoleDto {}
4 changes: 4 additions & 0 deletions src/modules/admin/role/dto/update-role.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { PartialType } from '@nestjs/mapped-types';
import { CreateRoleDto } from './create-role.dto';

export class UpdateRoleDto extends PartialType(CreateRoleDto) {}
Loading