feat: Add SUPER_ADMIN role with dedicated admin user management and updated access controls for existing admin features.

This commit is contained in:
pradeepkumar
2026-03-20 17:05:40 +05:30
parent 8e9d623f24
commit 31439c3b06
6 changed files with 104 additions and 4 deletions

View File

@@ -17,6 +17,7 @@ enum UserRole {
USER USER
AGENT AGENT
ADMIN ADMIN
SUPER_ADMIN
} }
enum UserStatus { enum UserStatus {

View File

@@ -1336,7 +1336,7 @@ async function main() {
data: { data: {
email: adminEmail, email: adminEmail,
password: hashedPassword, password: hashedPassword,
role: UserRole.ADMIN, role: UserRole.SUPER_ADMIN,
status: UserStatus.ACTIVE, status: UserStatus.ACTIVE,
emailVerified: true, emailVerified: true,
emailVerifiedAt: new Date(), emailVerifiedAt: new Date(),

View File

@@ -33,7 +33,11 @@ export class RolesGuard implements CanActivate {
throw new ForbiddenException('Access denied'); throw new ForbiddenException('Access denied');
} }
const hasRole = requiredRoles.some((role) => user.role === role); // SUPER_ADMIN has all ADMIN privileges
const hasRole = requiredRoles.some((role) =>
user.role === role ||
(role === UserRole.ADMIN && user.role === UserRole.SUPER_ADMIN)
);
if (!hasRole) { if (!hasRole) {
throw new ForbiddenException( throw new ForbiddenException(

View File

@@ -85,7 +85,7 @@ export class MessagesGateway implements OnGatewayConnection, OnGatewayDisconnect
this.logger.log(`Client connected successfully: ${client.id} (User: ${userId}, Role: ${payload.role})`); this.logger.log(`Client connected successfully: ${client.id} (User: ${userId}, Role: ${payload.role})`);
// Auto-join admin users to admin_room for support chat notifications // Auto-join admin users to admin_room for support chat notifications
if (payload.role === 'ADMIN') { if (payload.role === 'ADMIN' || payload.role === 'SUPER_ADMIN') {
client.join('admin_room'); client.join('admin_room');
this.logger.log(`Admin ${userId} joined admin_room`); this.logger.log(`Admin ${userId} joined admin_room`);
} }

View File

@@ -329,4 +329,34 @@ export class UsersController {
) { ) {
return this.usersService.updateVerification(id, data, admin.id); return this.usersService.updateVerification(id, data, admin.id);
} }
// ==========================================
// ADMIN MANAGEMENT (SUPER_ADMIN only)
// ==========================================
@Get('admin/admins')
@Roles(UserRole.SUPER_ADMIN)
@ApiOperation({ summary: 'Super Admin: Get all admin users' })
async getAdminUsers() {
return this.usersService.getAdminUsers();
}
@Put('admin/admins')
@Roles(UserRole.SUPER_ADMIN)
@ApiOperation({ summary: 'Super Admin: Create a new admin user' })
async createAdminUser(
@Body() dto: { email: string; password: string },
) {
return this.usersService.createAdminUser(dto.email, dto.password);
}
@Delete('admin/admins/:id')
@Roles(UserRole.SUPER_ADMIN)
@ApiOperation({ summary: 'Super Admin: Delete an admin user' })
async deleteAdminUser(
@Param('id') id: string,
@CurrentUser('id') requesterId: string,
) {
return this.usersService.deleteAdminUser(id, requesterId);
}
} }

View File

@@ -1,6 +1,7 @@
import { Injectable, BadRequestException, NotFoundException } from '@nestjs/common'; import { Injectable, BadRequestException, NotFoundException, ForbiddenException } from '@nestjs/common';
import { UserStatus, UserRole, Prisma, VerificationStatus } from '@prisma/client'; import { UserStatus, UserRole, Prisma, VerificationStatus } from '@prisma/client';
import { PrismaService } from '../prisma/prisma.service'; import { PrismaService } from '../prisma/prisma.service';
import * as argon2 from 'argon2';
type UserWithProfiles = Prisma.UserGetPayload<{ type UserWithProfiles = Prisma.UserGetPayload<{
include: { userProfile: true; agentProfile: { include: { agentType: true } } }; include: { userProfile: true; agentProfile: { include: { agentType: true } } };
@@ -519,4 +520,68 @@ export class UsersService {
return { message: 'Account deleted successfully' }; return { message: 'Account deleted successfully' };
} }
// =============================================
// ADMIN MANAGEMENT (SUPER_ADMIN only)
// =============================================
async getAdminUsers() {
return this.prisma.user.findMany({
where: { role: { in: ['ADMIN', 'SUPER_ADMIN'] } },
select: {
id: true,
email: true,
role: true,
status: true,
createdAt: true,
lastLoginAt: true,
},
orderBy: { createdAt: 'desc' },
});
}
async createAdminUser(email: string, password: string) {
const existing = await this.prisma.user.findUnique({ where: { email } });
if (existing) {
throw new BadRequestException('A user with this email already exists');
}
const hashedPassword = await argon2.hash(password);
return this.prisma.user.create({
data: {
email,
password: hashedPassword,
role: 'ADMIN',
status: 'ACTIVE',
emailVerified: true,
emailVerifiedAt: new Date(),
authProvider: 'LOCAL',
},
select: {
id: true,
email: true,
role: true,
status: true,
createdAt: true,
},
});
}
async deleteAdminUser(id: string, requesterId: string) {
if (id === requesterId) {
throw new BadRequestException('You cannot delete your own account');
}
const user = await this.prisma.user.findUnique({ where: { id } });
if (!user) throw new NotFoundException('User not found');
if (user.role === 'SUPER_ADMIN') {
throw new ForbiddenException('Cannot delete a super admin');
}
if (user.role !== 'ADMIN') {
throw new BadRequestException('This user is not an admin');
}
await this.prisma.user.delete({ where: { id } });
return null;
}
} }