From fe1ebaa1cfe0d2d1d55f9cc61873bb823380ca36 Mon Sep 17 00:00:00 2001 From: "DESKTOP-1R2VSQH\\Lenovo ThinkPad E490" Date: Thu, 7 May 2026 13:27:27 +0700 Subject: [PATCH] refactor(PermissionController): getPermission --- src/controllers/PermissionController.ts | 164 ++++++++++++++++-- src/controllers/PosMasterActController.ts | 42 +++++ .../ProfileActpositionController.ts | 37 ++++ 3 files changed, 230 insertions(+), 13 deletions(-) diff --git a/src/controllers/PermissionController.ts b/src/controllers/PermissionController.ts index 026a3ecf..feb8d7a3 100644 --- a/src/controllers/PermissionController.ts +++ b/src/controllers/PermissionController.ts @@ -57,17 +57,26 @@ export class PermissionController extends Controller { } } - let reply = await getAsync("role_" + profile.id); + // Query ตำแหน่งรักษาการโดยใช้ service ที่มีอยู่ + const orgRevision = await this.orgRevisionRepository.findOne({ + select: ["id"], + where: { + orgRevisionIsDraft: false, + orgRevisionIsCurrent: true, + }, + }); + + const actingData = await actingPositionService.getActingPositionsWithPrivilege( + profile.id, + orgRevision?.id + ); + + // ใช้ cache key ที่รวมสถานะ acting + const cacheKey = `role_${profile.id}_${actingData.isAct ? 'acting' : 'normal'}`; + let reply = await getAsync(cacheKey); if (reply != null) { reply = JSON.parse(reply); } else { - const orgRevision = await this.orgRevisionRepository.findOne({ - select: ["id"], - where: { - orgRevisionIsDraft: false, - orgRevisionIsCurrent: true, - }, - }); let posMaster: any = await this.posMasterRepository.findOne({ select: ["authRoleId"], where: { @@ -111,11 +120,140 @@ export class PermissionController extends Controller { where: { authRoleId: getDetail.id }, }); - reply = { - ...getDetail, - roles: roleAttrData, - }; - redisClient.setex("role_" + profile.id, 86400, JSON.stringify(reply)); + // ถ้า User มีตำแหน่งรักษาการ ให้รวมสิทธิ์ + if (actingData.isAct && actingData.posMasterActs.length > 0) { + // ดึง authRoleId ของทุกตำแหน่งรักษาการ + const actingAuthRoleIds = await this.posMasterActRepo + .createQueryBuilder("posMasterAct") + .leftJoin("posMasterAct.posMaster", "posMaster") + .select("posMaster.authRoleId", "authRoleId") + .leftJoin("posMasterAct.posMasterChild", "posMasterChild") + .leftJoin("posMasterChild.current_holder", "profile") + .where("profile.id = :profileId", { profileId: profile.id }) + .andWhere("posMaster.orgRevisionId = :orgRevisionId", { orgRevisionId: orgRevision?.id }) + .getRawMany(); + + // ดึง AuthRoleAttr ทั้งหมดของ acting roles + const actingRoleIds = actingAuthRoleIds.map(x => x.authRoleId).filter(id => id != null); + const actingRoleAttrs = await this.authRoleAttrRepo.find({ + select: [ + "authSysId", + "parentNode", + "attrOwnership", + "attrIsCreate", + "attrIsList", + "attrIsGet", + "attrIsUpdate", + "attrIsDelete", + "attrPrivilege", + ], + where: { authRoleId: In(actingRoleIds) as any }, + }); + + // สร้าง map ของ authSysId -> สิทธิ์ที่ดีที่สุดจาก acting + const actingPermissionMap = new Map(); + + // ลำดับความสำคัญของ privilege (มากไปน้อย) + const privilegePriority: Record = { + "OWNER": 7, + "PARENT": 6, + "ROOT": 5, + "BROTHER": 4, + "CHILD": 3, + "NORMAL": 2, + "SPECIFIC": 1, + "null": 0, + }; + + // ฟังก์ชันเปรียบเทียบ privilege + const getHigherPrivilege = (priv1: string | null, priv2: string | null): string | null => { + const p1 = priv1 ?? "null"; + const p2 = priv2 ?? "null"; + const priority1 = privilegePriority[p1] ?? 0; + const priority2 = privilegePriority[p2] ?? 0; + return priority1 >= priority2 ? priv1 : priv2; + }; + + // ฟังก์ชันเปรียบเทียบ ownership (OWNER > STAFF > null) + const getHigherOwnership = (own1: string | null, own2: string | null): string | null => { + // OWNER สูงสุด + if (own1 === "OWNER" || own2 === "OWNER") return "OWNER"; + // STAFF รองลงมา + if (own1 === "STAFF" || own2 === "STAFF") return "STAFF"; + return null; + }; + + for (const attr of actingRoleAttrs) { + const key = attr.authSysId; + if (!actingPermissionMap.has(key)) { + actingPermissionMap.set(key, attr); + } else { + // รวมสิทธิ์: ใช้ OR logic สำหรับ CRUD + // สำหรับ attrOwnership และ attrPrivilege ใช้ค่าที่ใหญ่ที่สุด + const existing = actingPermissionMap.get(key); + actingPermissionMap.set(key, { + ...attr, + attrIsCreate: existing.attrIsCreate || attr.attrIsCreate, + attrIsList: existing.attrIsList || attr.attrIsList, + attrIsGet: existing.attrIsGet || attr.attrIsGet, + attrIsUpdate: existing.attrIsUpdate || attr.attrIsUpdate, + attrIsDelete: existing.attrIsDelete || attr.attrIsDelete, + attrPrivilege: getHigherPrivilege(attr.attrPrivilege, existing.attrPrivilege), + parentNode: attr.parentNode, // ใช้ parentNode ของ acting role + attrOwnership: getHigherOwnership(attr.attrOwnership, existing.attrOwnership), + }); + } + } + + // รวมกับสิทธิ์พื้นฐานของ User + // สำหรับระบบที่อยู่ใน acting: ใช้สิทธิ์จาก acting + // สำหรับระบบที่ไม่อยู่ใน acting: ใช้สิทธิ์พื้นฐาน + const mergedRoleAttrs = roleAttrData.map((baseAttr) => { + const actingAttr = actingPermissionMap.get(baseAttr.authSysId); + if (actingAttr) { + // ระบบนี้มีสิทธิ์จาก acting - ใช้ค่าจาก acting role + return { + ...baseAttr, + parentNode: actingAttr.parentNode, + attrOwnership: actingAttr.attrOwnership, + attrIsCreate: actingAttr.attrIsCreate, + attrIsList: actingAttr.attrIsList, + attrIsGet: actingAttr.attrIsGet, + attrIsUpdate: actingAttr.attrIsUpdate, + attrIsDelete: actingAttr.attrIsDelete, + attrPrivilege: actingAttr.attrPrivilege, + // เพิ่ม metadata เพื่อระบุว่ามาจาก acting + _isActing: true, + }; + } + // เก็บสิทธิ์พื้นฐานสำหรับระบบที่ไม่ได้รักษาการ + return baseAttr; + }); + + // เพิ่มระบบที่มีเฉพาะใน acting roles + for (const [authSysId, actingAttr] of actingPermissionMap) { + if (!roleAttrData.find(a => a.authSysId === authSysId)) { + mergedRoleAttrs.push({ + ...actingAttr, + _isActing: true, + }); + } + } + + reply = { + ...getDetail, + roles: mergedRoleAttrs, + isActing: true, // Flag ระบุสถานะ acting + }; + } else { + // ไม่มี acting - ใช้ response เดิม + reply = { + ...getDetail, + roles: roleAttrData, + isActing: false, + }; + } + redisClient.setex(cacheKey, 86400, JSON.stringify(reply)); } return new HttpSuccess(reply); } diff --git a/src/controllers/PosMasterActController.ts b/src/controllers/PosMasterActController.ts index dd4acd1b..ac37e65c 100644 --- a/src/controllers/PosMasterActController.ts +++ b/src/controllers/PosMasterActController.ts @@ -25,6 +25,9 @@ import { ProfileActposition } from "../entities/ProfileActposition"; import { RequestWithUser } from "../middlewares/user"; import { escape } from "querystring"; +const REDIS_HOST = process.env.REDIS_HOST; +const REDIS_PORT = process.env.REDIS_PORT; + @Route("api/v1/org/pos/act") @Tags("PosMasterAct") @Security("bearerAuth") @@ -37,6 +40,23 @@ export class PosMasterActController extends Controller { private posMasterActRepository = AppDataSource.getRepository(PosMasterAct); private posMasterRepository = AppDataSource.getRepository(PosMaster); private actpositionRepository = AppDataSource.getRepository(ProfileActposition); + private redis = require("redis"); + + /** + * Helper function สำหรับลบ cache เมื่อมีการเปลี่ยนแปลง acting position + */ + private async invalidatePermissionCache(profileIds: string[]) { + const redisClient = await this.redis.createClient({ + host: REDIS_HOST, + port: REDIS_PORT, + }); + + for (const profileId of profileIds) { + redisClient.del(`role_${profileId}_acting`); + redisClient.del(`role_${profileId}_normal`); + redisClient.del(`menu_${profileId}`); + } + } /** * API เพิ่มรักษาการในตำแหน่ง @@ -89,6 +109,12 @@ export class PosMasterActController extends Controller { posMasterAct.createdAt = new Date(); posMasterAct.lastUpdatedAt = new Date(); await this.posMasterActRepository.save(posMasterAct); + + // ลบ cache ของผู้ถูกรักษาการ (current_holder ของ posMasterChild) + if (posMasterChild.current_holderId) { + await this.invalidatePermissionCache([posMasterChild.current_holderId]); + } + return new HttpSuccess(posMasterAct); } @@ -295,6 +321,7 @@ export class PosMasterActController extends Controller { where: { id: id, }, + relations: ["posMasterChild"], }); try { result = await this.posMasterActRepository.delete({ id: id }); @@ -318,6 +345,11 @@ export class PosMasterActController extends Controller { p.posMasterOrder = i + 1; await this.posMasterActRepository.save(p); }); + + // ลบ cache ของผู้ถูกรักษาการ + if (posMasterAct.posMasterChild?.current_holderId) { + await this.invalidatePermissionCache([posMasterAct.posMasterChild.current_holderId]); + } } return new HttpSuccess(); } @@ -768,6 +800,9 @@ export class PosMasterActController extends Controller { throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลรักษาการในตำแหน่งของหน่วยงานนี้"); } + // เก็บ profileIds ที่ได้รับผลกระทบเพื่อลบ cache + const affectedProfileIds: string[] = []; + await Promise.all( posMasterActs.map(async (posMasterAct) => { const orgShortName = @@ -782,6 +817,8 @@ export class PosMasterActController extends Controller { const profileId = posMasterAct.posMasterChild?.current_holderId; if (profileId) { + affectedProfileIds.push(profileId); + const existingActivePositions = await this.actpositionRepository.find({ select: [ "id", @@ -834,6 +871,11 @@ export class PosMasterActController extends Controller { }), ); + // ลบ cache ของผู้ถูกรักษาการทั้งหมด + if (affectedProfileIds.length > 0) { + await this.invalidatePermissionCache(affectedProfileIds); + } + return new HttpSuccess(); } } diff --git a/src/controllers/ProfileActpositionController.ts b/src/controllers/ProfileActpositionController.ts index e22cf983..5f2262f3 100644 --- a/src/controllers/ProfileActpositionController.ts +++ b/src/controllers/ProfileActpositionController.ts @@ -25,6 +25,10 @@ import HttpStatus from "../interfaces/http-status"; import HttpSuccess from "../interfaces/http-success"; import permission from "../interfaces/permission"; import { setLogDataDiff } from "../interfaces/utils"; + +const REDIS_HOST = process.env.REDIS_HOST; +const REDIS_PORT = process.env.REDIS_PORT; + @Route("api/v1/org/profile/actposition") @Tags("ProfileActposition") @Security("bearerAuth") @@ -32,6 +36,23 @@ export class ProfileActpositionController extends Controller { private profileRepo = AppDataSource.getRepository(Profile); private profileActpositionRepo = AppDataSource.getRepository(ProfileActposition); private profileActpositionHistoryRepo = AppDataSource.getRepository(ProfileActpositionHistory); + private redis = require("redis"); + + /** + * Helper function สำหรับลบ cache เมื่อมีการเปลี่ยนแปลง acting position + */ + private async invalidatePermissionCache(profileIds: string[]) { + const redisClient = await this.redis.createClient({ + host: REDIS_HOST, + port: REDIS_PORT, + }); + + for (const profileId of profileIds) { + redisClient.del(`role_${profileId}_acting`); + redisClient.del(`role_${profileId}_normal`); + redisClient.del(`menu_${profileId}`); + } + } @Get("user") public async detailProfileActpositionUser(@Request() request: { user: Record }) { @@ -161,6 +182,10 @@ export class ProfileActpositionController extends Controller { history.profileActpositionId = data.id; await this.profileActpositionHistoryRepo.save(history, { data: req }); //setLogDataDiff(req, { before, after: history }); + + // ลบ cache เมื่อสร้าง acting position ใหม่ + await this.invalidatePermissionCache([body.profileId]); + return new HttpSuccess(data.id); } @@ -198,6 +223,9 @@ export class ProfileActpositionController extends Controller { // setLogDataDiff(req, { before: before_null, after: history }), ]); + // ลบ cache เมื่อแก้ไข acting position + await this.invalidatePermissionCache([record.profileId]); + return new HttpSuccess(); } @@ -236,6 +264,9 @@ export class ProfileActpositionController extends Controller { this.profileActpositionHistoryRepo.save(history, { data: req }), ]); + // ลบ cache เมื่อ soft delete acting position + await this.invalidatePermissionCache([record.profileId]); + return new HttpSuccess(); } @@ -245,6 +276,7 @@ export class ProfileActpositionController extends Controller { @Request() req: RequestWithUser, ) { const _record = await this.profileActpositionRepo.findOneBy({ id: actpositionId }); + const profileId = _record?.profileId; if (_record) { await new permission().PermissionOrgUserDelete( req, @@ -261,6 +293,11 @@ export class ProfileActpositionController extends Controller { if (result.affected == undefined || result.affected <= 0) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); + // ลบ cache เมื่อ delete acting position + if (profileId) { + await this.invalidatePermissionCache([profileId]); + } + return new HttpSuccess(); } }