Admin panel: real data - backend API, users/audit/roles/stats, frontend wired
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -88,6 +88,16 @@ async function main() {
|
||||
});
|
||||
}
|
||||
|
||||
// Admin permission for GM
|
||||
await prisma.positionPermission.create({
|
||||
data: {
|
||||
positionId: gmPosition.id,
|
||||
module: 'admin',
|
||||
resource: '*',
|
||||
actions: ['*'],
|
||||
},
|
||||
});
|
||||
|
||||
// Create Permissions for Sales Manager
|
||||
await prisma.positionPermission.createMany({
|
||||
data: [
|
||||
|
||||
41
backend/scripts/add-admin-permission.ts
Normal file
41
backend/scripts/add-admin-permission.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* Add admin permission for GM position.
|
||||
* Run this for existing databases where seed was run before admin module existed:
|
||||
* npx ts-node scripts/add-admin-permission.ts
|
||||
*/
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
async function main() {
|
||||
const gmPosition = await prisma.position.findFirst({ where: { code: 'GM' } });
|
||||
if (!gmPosition) {
|
||||
console.log('GM position not found. Run full seed first.');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const existing = await prisma.positionPermission.findFirst({
|
||||
where: { positionId: gmPosition.id, module: 'admin' },
|
||||
});
|
||||
if (existing) {
|
||||
console.log('Admin permission already exists for GM.');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
await prisma.positionPermission.create({
|
||||
data: {
|
||||
positionId: gmPosition.id,
|
||||
module: 'admin',
|
||||
resource: '*',
|
||||
actions: ['*'],
|
||||
},
|
||||
});
|
||||
console.log('Admin permission added for GM position.');
|
||||
}
|
||||
|
||||
main()
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
})
|
||||
.finally(() => prisma.$disconnect());
|
||||
150
backend/src/modules/admin/admin.controller.ts
Normal file
150
backend/src/modules/admin/admin.controller.ts
Normal file
@@ -0,0 +1,150 @@
|
||||
import { Response, NextFunction } from 'express';
|
||||
import { adminService } from './admin.service';
|
||||
import { AuthRequest } from '../../shared/middleware/auth';
|
||||
import { ResponseFormatter } from '../../shared/utils/responseFormatter';
|
||||
|
||||
class AdminController {
|
||||
// ========== USERS ==========
|
||||
|
||||
async findAllUsers(req: AuthRequest, res: Response, next: NextFunction) {
|
||||
try {
|
||||
const filters = {
|
||||
search: req.query.search as string | undefined,
|
||||
isActive: req.query.status === 'active' ? true : req.query.status === 'inactive' ? false : undefined,
|
||||
positionId: req.query.positionId as string | undefined,
|
||||
page: parseInt(req.query.page as string) || 1,
|
||||
pageSize: parseInt(req.query.pageSize as string) || 20,
|
||||
};
|
||||
const result = await adminService.findAllUsers(filters);
|
||||
res.json(ResponseFormatter.paginated(result.users, result.total, result.page, result.pageSize));
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
async findUserById(req: AuthRequest, res: Response, next: NextFunction) {
|
||||
try {
|
||||
const user = await adminService.findUserById(req.params.id);
|
||||
res.json(ResponseFormatter.success(user));
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
async createUser(req: AuthRequest, res: Response, next: NextFunction) {
|
||||
try {
|
||||
const userId = req.user!.id;
|
||||
const user = await adminService.createUser(
|
||||
{
|
||||
email: req.body.email,
|
||||
username: req.body.username,
|
||||
password: req.body.password,
|
||||
employeeId: req.body.employeeId,
|
||||
isActive: req.body.isActive ?? true,
|
||||
},
|
||||
userId
|
||||
);
|
||||
res.status(201).json(ResponseFormatter.success(user));
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
async updateUser(req: AuthRequest, res: Response, next: NextFunction) {
|
||||
try {
|
||||
const userId = req.user!.id;
|
||||
const user = await adminService.updateUser(
|
||||
req.params.id,
|
||||
{
|
||||
email: req.body.email,
|
||||
username: req.body.username,
|
||||
password: req.body.password,
|
||||
employeeId: req.body.employeeId,
|
||||
isActive: req.body.isActive,
|
||||
},
|
||||
userId
|
||||
);
|
||||
res.json(ResponseFormatter.success(user));
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
async toggleUserActive(req: AuthRequest, res: Response, next: NextFunction) {
|
||||
try {
|
||||
const userId = req.user!.id;
|
||||
const user = await adminService.toggleUserActive(req.params.id, userId);
|
||||
res.json(ResponseFormatter.success(user));
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
async deleteUser(req: AuthRequest, res: Response, next: NextFunction) {
|
||||
try {
|
||||
const userId = req.user!.id;
|
||||
await adminService.deleteUser(req.params.id, userId);
|
||||
res.json(ResponseFormatter.success(null, 'User deactivated successfully'));
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
// ========== AUDIT LOGS ==========
|
||||
|
||||
async getAuditLogs(req: AuthRequest, res: Response, next: NextFunction) {
|
||||
try {
|
||||
const filters = {
|
||||
entityType: req.query.entityType as string | undefined,
|
||||
action: req.query.action as string | undefined,
|
||||
userId: req.query.userId as string | undefined,
|
||||
startDate: req.query.startDate as string | undefined,
|
||||
endDate: req.query.endDate as string | undefined,
|
||||
page: parseInt(req.query.page as string) || 1,
|
||||
pageSize: parseInt(req.query.pageSize as string) || 20,
|
||||
};
|
||||
const result = await adminService.getAuditLogs(filters);
|
||||
res.json(
|
||||
ResponseFormatter.paginated(result.logs, result.total, result.page, result.pageSize)
|
||||
);
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
// ========== STATS ==========
|
||||
|
||||
async getStats(req: AuthRequest, res: Response, next: NextFunction) {
|
||||
try {
|
||||
const stats = await adminService.getStats();
|
||||
res.json(ResponseFormatter.success(stats));
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
// ========== POSITIONS (ROLES) ==========
|
||||
|
||||
async getPositions(req: AuthRequest, res: Response, next: NextFunction) {
|
||||
try {
|
||||
const positions = await adminService.getPositions();
|
||||
res.json(ResponseFormatter.success(positions));
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
async updatePositionPermissions(req: AuthRequest, res: Response, next: NextFunction) {
|
||||
try {
|
||||
const position = await adminService.updatePositionPermissions(
|
||||
req.params.id,
|
||||
req.body.permissions
|
||||
);
|
||||
res.json(ResponseFormatter.success(position));
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const adminController = new AdminController();
|
||||
103
backend/src/modules/admin/admin.routes.ts
Normal file
103
backend/src/modules/admin/admin.routes.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
import { Router } from 'express';
|
||||
import { body, param } from 'express-validator';
|
||||
import { adminController } from './admin.controller';
|
||||
import { authenticate, authorize } from '../../shared/middleware/auth';
|
||||
import { validate } from '../../shared/middleware/validation';
|
||||
|
||||
const router = Router();
|
||||
router.use(authenticate);
|
||||
|
||||
// ========== USERS ==========
|
||||
|
||||
router.get(
|
||||
'/users',
|
||||
authorize('admin', 'users', 'read'),
|
||||
adminController.findAllUsers
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/users/:id',
|
||||
authorize('admin', 'users', 'read'),
|
||||
param('id').isUUID(),
|
||||
validate,
|
||||
adminController.findUserById
|
||||
);
|
||||
|
||||
router.post(
|
||||
'/users',
|
||||
authorize('admin', 'users', 'create'),
|
||||
[
|
||||
body('email').isEmail(),
|
||||
body('username').notEmpty().trim(),
|
||||
body('password').isLength({ min: 8 }),
|
||||
body('employeeId').isUUID(),
|
||||
],
|
||||
validate,
|
||||
adminController.createUser
|
||||
);
|
||||
|
||||
router.put(
|
||||
'/users/:id',
|
||||
authorize('admin', 'users', 'update'),
|
||||
[
|
||||
param('id').isUUID(),
|
||||
body('email').optional().isEmail(),
|
||||
body('username').optional().notEmpty().trim(),
|
||||
body('password').optional().isLength({ min: 8 }),
|
||||
],
|
||||
validate,
|
||||
adminController.updateUser
|
||||
);
|
||||
|
||||
router.patch(
|
||||
'/users/:id/toggle-active',
|
||||
authorize('admin', 'users', 'update'),
|
||||
param('id').isUUID(),
|
||||
validate,
|
||||
adminController.toggleUserActive
|
||||
);
|
||||
|
||||
router.delete(
|
||||
'/users/:id',
|
||||
authorize('admin', 'users', 'delete'),
|
||||
param('id').isUUID(),
|
||||
validate,
|
||||
adminController.deleteUser
|
||||
);
|
||||
|
||||
// ========== AUDIT LOGS ==========
|
||||
|
||||
router.get(
|
||||
'/audit-logs',
|
||||
authorize('admin', 'audit-logs', 'read'),
|
||||
adminController.getAuditLogs
|
||||
);
|
||||
|
||||
// ========== STATS ==========
|
||||
|
||||
router.get(
|
||||
'/stats',
|
||||
authorize('admin', 'stats', 'read'),
|
||||
adminController.getStats
|
||||
);
|
||||
|
||||
// ========== POSITIONS (ROLES) ==========
|
||||
|
||||
router.get(
|
||||
'/positions',
|
||||
authorize('admin', 'roles', 'read'),
|
||||
adminController.getPositions
|
||||
);
|
||||
|
||||
router.put(
|
||||
'/positions/:id/permissions',
|
||||
authorize('admin', 'roles', 'update'),
|
||||
[
|
||||
param('id').isUUID(),
|
||||
body('permissions').isArray(),
|
||||
],
|
||||
validate,
|
||||
adminController.updatePositionPermissions
|
||||
);
|
||||
|
||||
export default router;
|
||||
434
backend/src/modules/admin/admin.service.ts
Normal file
434
backend/src/modules/admin/admin.service.ts
Normal file
@@ -0,0 +1,434 @@
|
||||
import bcrypt from 'bcryptjs';
|
||||
import prisma from '../../config/database';
|
||||
import { AppError } from '../../shared/middleware/errorHandler';
|
||||
import { AuditLogger } from '../../shared/utils/auditLogger';
|
||||
import { config } from '../../config';
|
||||
import { Prisma } from '@prisma/client';
|
||||
|
||||
export interface UserFilters {
|
||||
search?: string;
|
||||
isActive?: boolean;
|
||||
positionId?: string;
|
||||
page?: number;
|
||||
pageSize?: number;
|
||||
}
|
||||
|
||||
export interface CreateUserData {
|
||||
email: string;
|
||||
username: string;
|
||||
password: string;
|
||||
employeeId: string;
|
||||
isActive?: boolean;
|
||||
}
|
||||
|
||||
export interface UpdateUserData {
|
||||
email?: string;
|
||||
username?: string;
|
||||
password?: string;
|
||||
employeeId?: string;
|
||||
isActive?: boolean;
|
||||
}
|
||||
|
||||
export interface AuditLogFilters {
|
||||
entityType?: string;
|
||||
action?: string;
|
||||
userId?: string;
|
||||
startDate?: string;
|
||||
endDate?: string;
|
||||
page?: number;
|
||||
pageSize?: number;
|
||||
}
|
||||
|
||||
class AdminService {
|
||||
// ========== USERS ==========
|
||||
|
||||
async findAllUsers(filters: UserFilters) {
|
||||
const page = filters.page || 1;
|
||||
const pageSize = Math.min(filters.pageSize || 20, 100);
|
||||
const skip = (page - 1) * pageSize;
|
||||
|
||||
const where: Prisma.UserWhereInput = {};
|
||||
|
||||
if (filters.isActive !== undefined) {
|
||||
where.isActive = filters.isActive;
|
||||
}
|
||||
|
||||
if (filters.positionId) {
|
||||
where.employee = {
|
||||
positionId: filters.positionId,
|
||||
};
|
||||
}
|
||||
|
||||
if (filters.search?.trim()) {
|
||||
const search = filters.search.trim().toLowerCase();
|
||||
where.OR = [
|
||||
{ username: { contains: search, mode: 'insensitive' } },
|
||||
{ email: { contains: search, mode: 'insensitive' } },
|
||||
{
|
||||
employee: {
|
||||
OR: [
|
||||
{ firstName: { contains: search, mode: 'insensitive' } },
|
||||
{ lastName: { contains: search, mode: 'insensitive' } },
|
||||
],
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
const [users, total] = await Promise.all([
|
||||
prisma.user.findMany({
|
||||
where,
|
||||
skip,
|
||||
take: pageSize,
|
||||
include: {
|
||||
employee: {
|
||||
include: {
|
||||
position: { select: { id: true, title: true, titleAr: true } },
|
||||
department: { select: { name: true, nameAr: true } },
|
||||
},
|
||||
},
|
||||
},
|
||||
orderBy: { createdAt: 'desc' },
|
||||
}),
|
||||
prisma.user.count({ where }),
|
||||
]);
|
||||
|
||||
const sanitized = users.map((u) => {
|
||||
const { password: _, ...rest } = u;
|
||||
return rest;
|
||||
});
|
||||
|
||||
return {
|
||||
users: sanitized,
|
||||
total,
|
||||
page,
|
||||
pageSize,
|
||||
totalPages: Math.ceil(total / pageSize),
|
||||
};
|
||||
}
|
||||
|
||||
async findUserById(id: string) {
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { id },
|
||||
include: {
|
||||
employee: {
|
||||
include: {
|
||||
position: { include: { permissions: true } },
|
||||
department: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
throw new AppError(404, 'المستخدم غير موجود - User not found');
|
||||
}
|
||||
|
||||
const { password: _, ...rest } = user;
|
||||
return rest;
|
||||
}
|
||||
|
||||
async createUser(data: CreateUserData, createdById: string) {
|
||||
const employee = await prisma.employee.findUnique({
|
||||
where: { id: data.employeeId },
|
||||
});
|
||||
|
||||
if (!employee) {
|
||||
throw new AppError(400, 'الموظف غير موجود - Employee not found');
|
||||
}
|
||||
|
||||
if (employee.status !== 'ACTIVE') {
|
||||
throw new AppError(400, 'الموظف غير نشط - Employee must be ACTIVE');
|
||||
}
|
||||
|
||||
const existingUser = await prisma.user.findFirst({
|
||||
where: { employeeId: data.employeeId },
|
||||
});
|
||||
if (existingUser) {
|
||||
throw new AppError(400, 'هذا الموظف مرتبط بحساب مستخدم بالفعل - Employee already has a user account');
|
||||
}
|
||||
|
||||
const emailExists = await prisma.user.findUnique({
|
||||
where: { email: data.email },
|
||||
});
|
||||
if (emailExists) {
|
||||
throw new AppError(400, 'البريد الإلكتروني مستخدم بالفعل - Email already in use');
|
||||
}
|
||||
|
||||
const usernameExists = await prisma.user.findUnique({
|
||||
where: { username: data.username },
|
||||
});
|
||||
if (usernameExists) {
|
||||
throw new AppError(400, 'اسم المستخدم مستخدم بالفعل - Username already in use');
|
||||
}
|
||||
|
||||
const hashedPassword = await bcrypt.hash(data.password, config.security?.bcryptRounds || 10);
|
||||
|
||||
const user = await prisma.user.create({
|
||||
data: {
|
||||
email: data.email,
|
||||
username: data.username,
|
||||
password: hashedPassword,
|
||||
employeeId: data.employeeId,
|
||||
isActive: data.isActive ?? true,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
email: true,
|
||||
username: true,
|
||||
employeeId: true,
|
||||
isActive: true,
|
||||
lastLogin: true,
|
||||
createdAt: true,
|
||||
employee: {
|
||||
include: {
|
||||
position: { select: { title: true, titleAr: true } },
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await AuditLogger.log({
|
||||
entityType: 'USER',
|
||||
entityId: user.id,
|
||||
action: 'CREATE',
|
||||
userId: createdById,
|
||||
changes: { created: { email: user.email, username: user.username } },
|
||||
});
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
async updateUser(id: string, data: UpdateUserData, updatedById: string) {
|
||||
const existing = await prisma.user.findUnique({ where: { id } });
|
||||
if (!existing) {
|
||||
throw new AppError(404, 'المستخدم غير موجود - User not found');
|
||||
}
|
||||
|
||||
if (data.email && data.email !== existing.email) {
|
||||
const emailExists = await prisma.user.findUnique({ where: { email: data.email } });
|
||||
if (emailExists) {
|
||||
throw new AppError(400, 'البريد الإلكتروني مستخدم بالفعل - Email already in use');
|
||||
}
|
||||
}
|
||||
|
||||
if (data.username && data.username !== existing.username) {
|
||||
const usernameExists = await prisma.user.findUnique({ where: { username: data.username } });
|
||||
if (usernameExists) {
|
||||
throw new AppError(400, 'اسم المستخدم مستخدم بالفعل - Username already in use');
|
||||
}
|
||||
}
|
||||
|
||||
const updateData: Prisma.UserUpdateInput = {
|
||||
...(data.email && { email: data.email }),
|
||||
...(data.username && { username: data.username }),
|
||||
...(data.isActive !== undefined && { isActive: data.isActive }),
|
||||
...(data.employeeId !== undefined && { employeeId: data.employeeId || null }),
|
||||
};
|
||||
|
||||
if (data.password && data.password.length >= 8) {
|
||||
updateData.password = await bcrypt.hash(data.password, config.security?.bcryptRounds || 10);
|
||||
}
|
||||
|
||||
const user = await prisma.user.update({
|
||||
where: { id },
|
||||
data: updateData,
|
||||
include: {
|
||||
employee: {
|
||||
include: {
|
||||
position: { select: { title: true, titleAr: true } },
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const { password: _, ...sanitized } = user;
|
||||
|
||||
await AuditLogger.log({
|
||||
entityType: 'USER',
|
||||
entityId: id,
|
||||
action: 'UPDATE',
|
||||
userId: updatedById,
|
||||
changes: { before: existing, after: sanitized },
|
||||
});
|
||||
|
||||
return sanitized;
|
||||
}
|
||||
|
||||
async toggleUserActive(id: string, userId: string) {
|
||||
const user = await prisma.user.findUnique({ where: { id } });
|
||||
if (!user) {
|
||||
throw new AppError(404, 'المستخدم غير موجود - User not found');
|
||||
}
|
||||
|
||||
const updated = await prisma.user.update({
|
||||
where: { id },
|
||||
data: { isActive: !user.isActive },
|
||||
include: {
|
||||
employee: {
|
||||
include: {
|
||||
position: { select: { title: true, titleAr: true } },
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const { password: _, ...sanitized } = updated;
|
||||
|
||||
await AuditLogger.log({
|
||||
entityType: 'USER',
|
||||
entityId: id,
|
||||
action: user.isActive ? 'DEACTIVATE' : 'ACTIVATE',
|
||||
userId,
|
||||
changes: { isActive: updated.isActive },
|
||||
});
|
||||
|
||||
return sanitized;
|
||||
}
|
||||
|
||||
async deleteUser(id: string, deletedById: string) {
|
||||
const user = await prisma.user.findUnique({ where: { id } });
|
||||
if (!user) {
|
||||
throw new AppError(404, 'المستخدم غير موجود - User not found');
|
||||
}
|
||||
|
||||
await prisma.user.update({
|
||||
where: { id },
|
||||
data: { isActive: false },
|
||||
});
|
||||
|
||||
await AuditLogger.log({
|
||||
entityType: 'USER',
|
||||
entityId: id,
|
||||
action: 'DELETE',
|
||||
userId: deletedById,
|
||||
changes: { softDeleted: true, email: user.email },
|
||||
});
|
||||
|
||||
return { success: true, message: 'User deactivated successfully' };
|
||||
}
|
||||
|
||||
// ========== AUDIT LOGS ==========
|
||||
|
||||
async getAuditLogs(filters: AuditLogFilters) {
|
||||
const page = filters.page || 1;
|
||||
const pageSize = Math.min(filters.pageSize || 20, 100);
|
||||
const skip = (page - 1) * pageSize;
|
||||
|
||||
const where: Prisma.AuditLogWhereInput = {};
|
||||
|
||||
if (filters.entityType) where.entityType = filters.entityType;
|
||||
if (filters.action) where.action = filters.action;
|
||||
if (filters.userId) where.userId = filters.userId;
|
||||
|
||||
if (filters.startDate || filters.endDate) {
|
||||
where.createdAt = {};
|
||||
if (filters.startDate) where.createdAt.gte = new Date(filters.startDate);
|
||||
if (filters.endDate) where.createdAt.lte = new Date(filters.endDate);
|
||||
}
|
||||
|
||||
const [logs, total] = await Promise.all([
|
||||
prisma.auditLog.findMany({
|
||||
where,
|
||||
skip,
|
||||
take: pageSize,
|
||||
include: {
|
||||
user: {
|
||||
select: { id: true, username: true, email: true },
|
||||
},
|
||||
},
|
||||
orderBy: { createdAt: 'desc' },
|
||||
}),
|
||||
prisma.auditLog.count({ where }),
|
||||
]);
|
||||
|
||||
return {
|
||||
logs,
|
||||
total,
|
||||
page,
|
||||
pageSize,
|
||||
totalPages: Math.ceil(total / pageSize),
|
||||
};
|
||||
}
|
||||
|
||||
// ========== STATS ==========
|
||||
|
||||
async getStats() {
|
||||
const today = new Date();
|
||||
today.setHours(0, 0, 0, 0);
|
||||
|
||||
const [totalUsers, activeUsers, inactiveUsers, loginsToday] = await Promise.all([
|
||||
prisma.user.count(),
|
||||
prisma.user.count({ where: { isActive: true } }),
|
||||
prisma.user.count({ where: { isActive: false } }),
|
||||
prisma.user.count({
|
||||
where: { lastLogin: { gte: today } },
|
||||
}),
|
||||
]);
|
||||
|
||||
return {
|
||||
totalUsers,
|
||||
activeUsers,
|
||||
inactiveUsers,
|
||||
loginsToday,
|
||||
};
|
||||
}
|
||||
|
||||
// ========== POSITIONS (ROLES) ==========
|
||||
|
||||
async getPositions() {
|
||||
const positions = await prisma.position.findMany({
|
||||
where: { isActive: true },
|
||||
include: {
|
||||
department: { select: { name: true, nameAr: true } },
|
||||
permissions: true,
|
||||
_count: {
|
||||
select: {
|
||||
employees: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
orderBy: { level: 'asc' },
|
||||
});
|
||||
|
||||
const withUserCount = await Promise.all(
|
||||
positions.map(async (p) => {
|
||||
const usersCount = await prisma.user.count({
|
||||
where: {
|
||||
employee: { positionId: p.id },
|
||||
isActive: true,
|
||||
},
|
||||
});
|
||||
return { ...p, usersCount };
|
||||
})
|
||||
);
|
||||
|
||||
return withUserCount;
|
||||
}
|
||||
|
||||
async updatePositionPermissions(positionId: string, permissions: Array<{ module: string; resource: string; actions: string[] }>) {
|
||||
const position = await prisma.position.findUnique({ where: { id: positionId } });
|
||||
if (!position) {
|
||||
throw new AppError(404, 'الدور غير موجود - Position not found');
|
||||
}
|
||||
|
||||
await prisma.positionPermission.deleteMany({
|
||||
where: { positionId },
|
||||
});
|
||||
|
||||
if (permissions.length > 0) {
|
||||
await prisma.positionPermission.createMany({
|
||||
data: permissions.map((p) => ({
|
||||
positionId,
|
||||
module: p.module,
|
||||
resource: p.resource,
|
||||
actions: p.actions,
|
||||
})),
|
||||
});
|
||||
}
|
||||
|
||||
return this.getPositions().then((pos) => pos.find((p) => p.id === positionId) || position);
|
||||
}
|
||||
}
|
||||
|
||||
export const adminService = new AdminService();
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Router } from 'express';
|
||||
import adminRoutes from '../modules/admin/admin.routes';
|
||||
import authRoutes from '../modules/auth/auth.routes';
|
||||
import contactsRoutes from '../modules/contacts/contacts.routes';
|
||||
import crmRoutes from '../modules/crm/crm.routes';
|
||||
@@ -10,6 +11,7 @@ import marketingRoutes from '../modules/marketing/marketing.routes';
|
||||
const router = Router();
|
||||
|
||||
// Module routes
|
||||
router.use('/admin', adminRoutes);
|
||||
router.use('/auth', authRoutes);
|
||||
router.use('/contacts', contactsRoutes);
|
||||
router.use('/crm', crmRoutes);
|
||||
|
||||
Reference in New Issue
Block a user