update
This commit is contained in:
266
services/roleHelperService.js
Normal file
266
services/roleHelperService.js
Normal file
@@ -0,0 +1,266 @@
|
||||
/**
|
||||
* Role Helper Service - Tiện ích để làm việc với role và permissions
|
||||
*/
|
||||
const { UserProfile, Role, Permission, UserAssignment, School, Class } = require('../models');
|
||||
const { Op } = require('sequelize');
|
||||
|
||||
class RoleHelperService {
|
||||
/**
|
||||
* Load role và permissions cho user (tự động chọn fast/flexible path)
|
||||
*/
|
||||
async getUserRoleAndPermissions(userId) {
|
||||
try {
|
||||
// Load profile
|
||||
const profile = await UserProfile.findOne({
|
||||
where: { user_id: userId },
|
||||
});
|
||||
|
||||
if (!profile) {
|
||||
return { roleInfo: null, permissions: [] };
|
||||
}
|
||||
|
||||
// Case 1: Fast Path - primary_role_info có sẵn
|
||||
if (profile.primary_role_info) {
|
||||
return await this._loadFromPrimaryRole(profile);
|
||||
}
|
||||
|
||||
// Case 2: Flexible Path - check UserAssignment
|
||||
return await this._loadFromAssignments(userId);
|
||||
} catch (error) {
|
||||
console.error('[RoleHelper] Error loading role:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load từ primary_role_info (Fast Path)
|
||||
*/
|
||||
async _loadFromPrimaryRole(profile) {
|
||||
const roleInfo = profile.primary_role_info;
|
||||
let permissions = [];
|
||||
|
||||
if (roleInfo.role_id) {
|
||||
const role = await Role.findByPk(roleInfo.role_id, {
|
||||
include: [{
|
||||
model: Permission,
|
||||
as: 'permissions',
|
||||
through: { attributes: [] },
|
||||
where: { is_active: true },
|
||||
required: false,
|
||||
}],
|
||||
});
|
||||
|
||||
if (role) {
|
||||
permissions = role.permissions.map(p => ({
|
||||
code: p.permission_code,
|
||||
name: p.permission_name,
|
||||
resource: p.resource,
|
||||
action: p.action,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
return { roleInfo, permissions };
|
||||
}
|
||||
|
||||
/**
|
||||
* Load từ UserAssignment (Flexible Path)
|
||||
*/
|
||||
async _loadFromAssignments(userId) {
|
||||
const assignments = await UserAssignment.findAll({
|
||||
where: {
|
||||
user_id: userId,
|
||||
is_active: true,
|
||||
[Op.or]: [
|
||||
{ valid_until: null },
|
||||
{ valid_until: { [Op.gt]: new Date() } },
|
||||
],
|
||||
},
|
||||
include: [
|
||||
{
|
||||
model: Role,
|
||||
as: 'role',
|
||||
include: [{
|
||||
model: Permission,
|
||||
as: 'permissions',
|
||||
through: { attributes: [] },
|
||||
where: { is_active: true },
|
||||
required: false,
|
||||
}],
|
||||
},
|
||||
{
|
||||
model: School,
|
||||
as: 'school',
|
||||
attributes: ['id', 'school_name'],
|
||||
},
|
||||
{
|
||||
model: Class,
|
||||
as: 'class',
|
||||
attributes: ['id', 'class_name'],
|
||||
},
|
||||
],
|
||||
order: [['is_primary', 'DESC']],
|
||||
});
|
||||
|
||||
if (assignments.length === 0) {
|
||||
return { roleInfo: null, permissions: [] };
|
||||
}
|
||||
|
||||
const primaryAssignment = assignments[0];
|
||||
const roleInfo = {
|
||||
role_id: primaryAssignment.role?.id,
|
||||
role_code: primaryAssignment.role?.role_code,
|
||||
role_name: primaryAssignment.role?.role_name,
|
||||
school: primaryAssignment.school ? {
|
||||
id: primaryAssignment.school.id,
|
||||
name: primaryAssignment.school.school_name,
|
||||
} : null,
|
||||
class: primaryAssignment.class ? {
|
||||
id: primaryAssignment.class.id,
|
||||
name: primaryAssignment.class.class_name,
|
||||
} : null,
|
||||
assignments: assignments.map(a => ({
|
||||
school: a.school ? { id: a.school.id, name: a.school.school_name } : null,
|
||||
class: a.class ? { id: a.class.id, name: a.class.class_name } : null,
|
||||
role: {
|
||||
id: a.role.id,
|
||||
code: a.role.role_code,
|
||||
name: a.role.role_name,
|
||||
},
|
||||
})),
|
||||
};
|
||||
|
||||
// Merge permissions từ tất cả roles
|
||||
const permissionMap = new Map();
|
||||
assignments.forEach(a => {
|
||||
a.role?.permissions?.forEach(p => {
|
||||
permissionMap.set(p.permission_code, {
|
||||
code: p.permission_code,
|
||||
name: p.permission_name,
|
||||
resource: p.resource,
|
||||
action: p.action,
|
||||
});
|
||||
});
|
||||
});
|
||||
const permissions = Array.from(permissionMap.values());
|
||||
|
||||
return { roleInfo, permissions };
|
||||
}
|
||||
|
||||
/**
|
||||
* Check xem user có permission cụ thể không
|
||||
*/
|
||||
async hasPermission(userId, permissionCode) {
|
||||
const { permissions } = await this.getUserRoleAndPermissions(userId);
|
||||
return permissions.some(p => p.code === permissionCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check xem user có role cụ thể không
|
||||
*/
|
||||
async hasRole(userId, roleCode) {
|
||||
const { roleInfo } = await this.getUserRoleAndPermissions(userId);
|
||||
|
||||
if (!roleInfo) return false;
|
||||
|
||||
// Check primary role
|
||||
if (roleInfo.role_code === roleCode) return true;
|
||||
|
||||
// Check assignments (nếu có)
|
||||
if (roleInfo.assignments) {
|
||||
return roleInfo.assignments.some(a => a.role.code === roleCode);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update primary_role_info cho student
|
||||
*/
|
||||
async updateStudentRoleInfo(userId, studentDetail) {
|
||||
const profile = await UserProfile.findOne({
|
||||
where: { user_id: userId },
|
||||
});
|
||||
|
||||
if (!profile) {
|
||||
throw new Error('User profile not found');
|
||||
}
|
||||
|
||||
const studentRole = await Role.findOne({
|
||||
where: { role_code: 'student' },
|
||||
});
|
||||
|
||||
if (!studentRole) {
|
||||
throw new Error('Student role not found');
|
||||
}
|
||||
|
||||
const roleInfo = {
|
||||
role_id: studentRole.id,
|
||||
role_code: studentRole.role_code,
|
||||
role_name: studentRole.role_name,
|
||||
school: studentDetail.school ? {
|
||||
id: studentDetail.school.id,
|
||||
name: studentDetail.school.school_name,
|
||||
} : null,
|
||||
class: studentDetail.class ? {
|
||||
id: studentDetail.class.id,
|
||||
name: studentDetail.class.class_name,
|
||||
} : null,
|
||||
grade: studentDetail.grade ? {
|
||||
id: studentDetail.grade.id,
|
||||
name: studentDetail.grade.grade_name,
|
||||
} : null,
|
||||
student_code: studentDetail.student_code,
|
||||
enrollment_date: studentDetail.enrollment_date,
|
||||
status: studentDetail.status,
|
||||
};
|
||||
|
||||
await profile.update({ primary_role_info: roleInfo });
|
||||
|
||||
// TODO: Invalidate Redis cache
|
||||
// await redis.del(`user:${userId}:permissions`);
|
||||
|
||||
return roleInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear primary_role_info (chuyển sang dùng UserAssignment)
|
||||
*/
|
||||
async clearPrimaryRoleInfo(userId) {
|
||||
const profile = await UserProfile.findOne({
|
||||
where: { user_id: userId },
|
||||
});
|
||||
|
||||
if (!profile) {
|
||||
throw new Error('User profile not found');
|
||||
}
|
||||
|
||||
await profile.update({ primary_role_info: null });
|
||||
|
||||
// TODO: Invalidate Redis cache
|
||||
// await redis.del(`user:${userId}:permissions`);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get statistics về fast/flexible path usage
|
||||
*/
|
||||
async getUsageStatistics() {
|
||||
const [results] = await UserProfile.sequelize.query(`
|
||||
SELECT
|
||||
CASE
|
||||
WHEN primary_role_info IS NOT NULL THEN 'Fast Path (primary_role_info)'
|
||||
ELSE 'Flexible Path (UserAssignment)'
|
||||
END as strategy,
|
||||
COUNT(*) as count,
|
||||
ROUND(COUNT(*) * 100.0 / (SELECT COUNT(*) FROM user_profiles), 2) as percentage
|
||||
FROM user_profiles
|
||||
GROUP BY strategy
|
||||
`);
|
||||
|
||||
return results;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new RoleHelperService();
|
||||
Reference in New Issue
Block a user