267 lines
7.0 KiB
JavaScript
267 lines
7.0 KiB
JavaScript
/**
|
|
* 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();
|