feat: Add SUPER_ADMIN role with dedicated admin user management and updated access controls for existing admin features.
This commit is contained in:
@@ -17,6 +17,7 @@ enum UserRole {
|
|||||||
USER
|
USER
|
||||||
AGENT
|
AGENT
|
||||||
ADMIN
|
ADMIN
|
||||||
|
SUPER_ADMIN
|
||||||
}
|
}
|
||||||
|
|
||||||
enum UserStatus {
|
enum UserStatus {
|
||||||
|
|||||||
@@ -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(),
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user