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 1/5] 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(); } } From bd102a9609e717353caa8156bc13be6a7c14515d Mon Sep 17 00:00:00 2001 From: "DESKTOP-1R2VSQH\\Lenovo ThinkPad E490" Date: Thu, 7 May 2026 14:40:56 +0700 Subject: [PATCH 2/5] fix --- src/controllers/PosMasterActController.ts | 42 ------------------- .../ProfileActpositionController.ts | 37 ---------------- 2 files changed, 79 deletions(-) diff --git a/src/controllers/PosMasterActController.ts b/src/controllers/PosMasterActController.ts index ac37e65c..dd4acd1b 100644 --- a/src/controllers/PosMasterActController.ts +++ b/src/controllers/PosMasterActController.ts @@ -25,9 +25,6 @@ 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") @@ -40,23 +37,6 @@ 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 เพิ่มรักษาการในตำแหน่ง @@ -109,12 +89,6 @@ 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); } @@ -321,7 +295,6 @@ export class PosMasterActController extends Controller { where: { id: id, }, - relations: ["posMasterChild"], }); try { result = await this.posMasterActRepository.delete({ id: id }); @@ -345,11 +318,6 @@ 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(); } @@ -800,9 +768,6 @@ 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 = @@ -817,8 +782,6 @@ export class PosMasterActController extends Controller { const profileId = posMasterAct.posMasterChild?.current_holderId; if (profileId) { - affectedProfileIds.push(profileId); - const existingActivePositions = await this.actpositionRepository.find({ select: [ "id", @@ -871,11 +834,6 @@ 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 5f2262f3..e22cf983 100644 --- a/src/controllers/ProfileActpositionController.ts +++ b/src/controllers/ProfileActpositionController.ts @@ -25,10 +25,6 @@ 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") @@ -36,23 +32,6 @@ 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 }) { @@ -182,10 +161,6 @@ 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); } @@ -223,9 +198,6 @@ export class ProfileActpositionController extends Controller { // setLogDataDiff(req, { before: before_null, after: history }), ]); - // ลบ cache เมื่อแก้ไข acting position - await this.invalidatePermissionCache([record.profileId]); - return new HttpSuccess(); } @@ -264,9 +236,6 @@ export class ProfileActpositionController extends Controller { this.profileActpositionHistoryRepo.save(history, { data: req }), ]); - // ลบ cache เมื่อ soft delete acting position - await this.invalidatePermissionCache([record.profileId]); - return new HttpSuccess(); } @@ -276,7 +245,6 @@ 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, @@ -293,11 +261,6 @@ 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(); } } From c313da8d5cb729dba3fff70ebd8ef86cc5926df1 Mon Sep 17 00:00:00 2001 From: "DESKTOP-1R2VSQH\\Lenovo ThinkPad E490" Date: Thu, 7 May 2026 15:07:30 +0700 Subject: [PATCH 3/5] fix --- src/controllers/PermissionController.ts | 396 +++++++++++++++++++----- 1 file changed, 324 insertions(+), 72 deletions(-) diff --git a/src/controllers/PermissionController.ts b/src/controllers/PermissionController.ts index feb8d7a3..1116396c 100644 --- a/src/controllers/PermissionController.ts +++ b/src/controllers/PermissionController.ts @@ -289,7 +289,15 @@ export class PermissionController extends Controller { } } - let reply = await getAsync("menu_" + profile.id); + // Query ตำแหน่งรักษาการ + const actingData = await actingPositionService.getActingPositionsWithPrivilege( + profile.id, + orgRevision?.id + ); + + // ใช้ cache key แบบเดียวกับ getPermission + const cacheKey = `menu_${profile.id}_${actingData.isAct ? 'acting' : 'normal'}`; + let reply = await getAsync(cacheKey); if (reply != null) { reply = JSON.parse(reply); } else { @@ -325,10 +333,47 @@ export class PermissionController extends Controller { if (!authRole) { throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูลสิทธิ์"); } - const roleAttrData = await this.authRoleAttrRepo.find({ + + // ดึง roleAttrData ของ user ปกติ + let roleAttrData = await this.authRoleAttrRepo.find({ select: ["authSysId", "parentNode"], where: { authRoleId: authRole.id, attrIsList: true }, }); + + // ถ้ามี acting positions ให้รวมสิทธิ์ + 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 (เฉพาะที่มี attrIsList: true) + const actingRoleIds = actingAuthRoleIds.map(x => x.authRoleId).filter(id => id != null); + const actingRoleAttrs = await this.authRoleAttrRepo.find({ + select: ["authSysId", "parentNode"], + where: { authRoleId: In(actingRoleIds) as any, attrIsList: true }, + }); + + // รวม authSysId และ parentNode จาก acting เข้ากับ base + // สำหรับระบบที่มีในทั้งสอง ให้ใช้ค่าของ acting (parentNode) + for (const actingAttr of actingRoleAttrs) { + const existingIndex = roleAttrData.findIndex(x => x.authSysId === actingAttr.authSysId); + if (existingIndex >= 0) { + // ระบบนี้มีใน base ด้วย -> ใช้ parentNode ของ acting + roleAttrData[existingIndex].parentNode = actingAttr.parentNode; + } else { + // ระบบนี้มีเฉพาะใน acting -> เพิ่มเข้าไป + roleAttrData.push(actingAttr); + } + } + } + const parentNode = roleAttrData.map((x) => x.parentNode); const authSysId = roleAttrData.map((x) => x.authSysId); const sysId = parentNode.concat(authSysId); @@ -369,7 +414,7 @@ export class PermissionController extends Controller { }; }); - redisClient.setex("menu_" + profile.id, 86400, JSON.stringify(reply)); + redisClient.setex(cacheKey, 86400, JSON.stringify(reply)); } return new HttpSuccess(reply); @@ -824,17 +869,26 @@ export class PermissionController extends Controller { } } - let reply = await getAsync("role_" + profile.id); + // Query ตำแหน่งรักษาการ + const orgRevision = await this.orgRevisionRepository.findOne({ + select: ["id"], + where: { + orgRevisionIsDraft: false, + orgRevisionIsCurrent: true, + }, + }); + + const actingData = await actingPositionService.getActingPositionsWithPrivilege( + profile.id, + orgRevision?.id + ); + + // ใช้ cache key แบบเดียวกับ getPermission() + 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: { @@ -878,11 +932,129 @@ export class PermissionController extends Controller { where: { authRoleId: getDetail.id }, }); - reply = { - ...getDetail, - roles: roleAttrData, - }; - redisClient.setex("role_" + profile.id, 86400, JSON.stringify(reply)); + // ถ้ามี acting positions ให้รวมสิทธิ์ + 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 }, + }); + + // ลำดับความสำคัญของ 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 => { + if (own1 === "OWNER" || own2 === "OWNER") return "OWNER"; + if (own1 === "STAFF" || own2 === "STAFF") return "STAFF"; + return null; + }; + + // สร้าง map ของ authSysId -> สิทธิ์ที่ดีที่สุดจาก acting + const actingPermissionMap = new Map(); + + for (const attr of actingRoleAttrs) { + const key = attr.authSysId; + if (!actingPermissionMap.has(key)) { + actingPermissionMap.set(key, attr); + } else { + 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, + attrOwnership: getHigherOwnership(attr.attrOwnership, existing.attrOwnership), + }); + } + } + + // รวมกับสิทธิ์พื้นฐานของ User + const mergedRoleAttrs = roleAttrData.map((baseAttr) => { + const actingAttr = actingPermissionMap.get(baseAttr.authSysId); + if (actingAttr) { + 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, + _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, + }; + } else { + reply = { + ...getDetail, + roles: roleAttrData, + }; + } + + redisClient.setex(cacheKey, 86400, JSON.stringify(reply)); } return reply; } @@ -932,77 +1104,157 @@ export class PermissionController extends Controller { } } - let reply = await getAsync("posMaster_" + profile.id); + // Query ตำแหน่งรักษาการ + const orgRevision = await this.orgRevisionRepository.findOne({ + select: ["id"], + where: { + orgRevisionIsDraft: false, + orgRevisionIsCurrent: true, + }, + }); + + const actingData = await actingPositionService.getActingPositionsWithPrivilege( + profile.id, + orgRevision?.id + ); + + // ใช้ cache key แบบใหม่ + const cacheKey = `posMaster_${profile.id}_${actingData.isAct ? 'acting' : 'normal'}`; + let reply = await getAsync(cacheKey); if (reply != null) { reply = JSON.parse(reply); } else { let privilege = await this.Permission(request, system, action); - const orgRevision = await this.orgRevisionRepository.findOne({ - select: ["id"], - where: { - orgRevisionIsDraft: false, - orgRevisionIsCurrent: true, - }, - }); - if (profileType == "OFFICER") { - const posMaster = await this.posMasterRepository.findOne({ - where: { - current_holderId: profile.id, - orgRevisionId: orgRevision?.id, - }, - }); - if (!posMaster) { + + // ถ้ากำลังรักษาการ ให้ดึง org จาก acting position + if (actingData.isAct) { + // ดึงข้อมูล permission เพื่อเช็คว่าระบบนี้มาจาก acting หรือไม่ + const permData: any = await this.getPermissionFunc(request); + const role = permData.roles.find((r: any) => r.authSysId === system); + + if (role && role._isActing) { + // ระบบนี้มาจาก acting position ดึง org จาก acting + const actingOrgData = await this.getActingOrgScope(profile.id, orgRevision?.id, system, profileType); reply = { - orgRootId: null, - orgChild1Id: null, - orgChild2Id: null, - orgChild3Id: null, - orgChild4Id: null, + orgRootId: actingOrgData.orgRootId, + orgChild1Id: actingOrgData.orgChild1Id, + orgChild2Id: actingOrgData.orgChild2Id, + orgChild3Id: actingOrgData.orgChild3Id, + orgChild4Id: actingOrgData.orgChild4Id, privilege: privilege, }; } else { - reply = { - orgRootId: posMaster.orgRootId, - orgChild1Id: posMaster.orgChild1Id, - orgChild2Id: posMaster.orgChild2Id, - orgChild3Id: posMaster.orgChild3Id, - orgChild4Id: posMaster.orgChild4Id, - privilege: privilege, - }; + // ระบบนี้มาจากตำแหน่งปกติ ใช้ org ปกติ + reply = await this.getBaseOrgScope(profile.id, orgRevision?.id, profileType, privilege); } - redisClient.setex("posMaster_" + profile.id, 86400, JSON.stringify(reply)); } else { - const posMaster = await this.posMasterEmpRepository.findOne({ - where: { - current_holderId: profile.id, - orgRevisionId: orgRevision?.id, - }, - }); - if (!posMaster) { - reply = { - orgRootId: null, - orgChild1Id: null, - orgChild2Id: null, - orgChild3Id: null, - orgChild4Id: null, - privilege: privilege, - }; - } else { - reply = { - orgRootId: posMaster.orgRootId, - orgChild1Id: posMaster.orgChild1Id, - orgChild2Id: posMaster.orgChild2Id, - orgChild3Id: posMaster.orgChild3Id, - orgChild4Id: posMaster.orgChild4Id, - privilege: privilege, - }; - } - redisClient.setex("posMaster_" + profile.id, 86400, JSON.stringify(reply)); + // ไม่มี acting ใช้ org ปกติ + reply = await this.getBaseOrgScope(profile.id, orgRevision?.id, profileType, privilege); } + + redisClient.setex(cacheKey, 86400, JSON.stringify(reply)); } return reply; } + // Helper method: ดึง org scope จากตำแหน่งปกติ + private async getBaseOrgScope(profileId: string, orgRevisionId: string | undefined, profileType: string, privilege: any) { + if (profileType == "OFFICER") { + const posMaster = await this.posMasterRepository.findOne({ + where: { + current_holderId: profileId, + orgRevisionId: orgRevisionId, + }, + }); + if (!posMaster) { + return { + orgRootId: null, + orgChild1Id: null, + orgChild2Id: null, + orgChild3Id: null, + orgChild4Id: null, + privilege: privilege, + }; + } else { + return { + orgRootId: posMaster.orgRootId, + orgChild1Id: posMaster.orgChild1Id, + orgChild2Id: posMaster.orgChild2Id, + orgChild3Id: posMaster.orgChild3Id, + orgChild4Id: posMaster.orgChild4Id, + privilege: privilege, + }; + } + } else { + const posMaster = await this.posMasterEmpRepository.findOne({ + where: { + current_holderId: profileId, + orgRevisionId: orgRevisionId, + }, + }); + if (!posMaster) { + return { + orgRootId: null, + orgChild1Id: null, + orgChild2Id: null, + orgChild3Id: null, + orgChild4Id: null, + privilege: privilege, + }; + } else { + return { + orgRootId: posMaster.orgRootId, + orgChild1Id: posMaster.orgChild1Id, + orgChild2Id: posMaster.orgChild2Id, + orgChild3Id: posMaster.orgChild3Id, + orgChild4Id: posMaster.orgChild4Id, + privilege: privilege, + }; + } + } + } + + // Helper method: ดึง org scope จาก acting position ที่มีสิทธิ์ในระบบนั้น + private async getActingOrgScope(profileId: string, orgRevisionId: string | undefined, system: string, profileType: string) { + const repo = profileType === "OFFICER" ? this.posMasterRepository : this.posMasterEmpRepository; + + const actingOrgData = await this.posMasterActRepo + .createQueryBuilder("posMasterAct") + .leftJoin("posMasterAct.posMaster", "posMaster") + .select([ + "posMaster.orgRootId", + "posMaster.orgChild1Id", + "posMaster.orgChild2Id", + "posMaster.orgChild3Id", + "posMaster.orgChild4Id", + ]) + .leftJoin("posMasterAct.posMasterChild", "posMasterChild") + .leftJoin("posMasterChild.current_holder", "profile") + .where("profile.id = :profileId", { profileId }) + .andWhere("posMaster.orgRevisionId = :orgRevisionId", { orgRevisionId }) + .orderBy("posMasterAct.posMasterOrder", "ASC") + .getRawOne(); + + if (!actingOrgData) { + // ไม่พบ acting position คืนค่า null + return { + orgRootId: null, + orgChild1Id: null, + orgChild2Id: null, + orgChild3Id: null, + orgChild4Id: null, + }; + } + + return { + orgRootId: actingOrgData.orgRootId, + orgChild1Id: actingOrgData.orgChild1Id, + orgChild2Id: actingOrgData.orgChild2Id, + orgChild3Id: actingOrgData.orgChild3Id, + orgChild4Id: actingOrgData.orgChild4Id, + }; + } + public async PermissionOrg(req: RequestWithUser, system: string, action: string) { let x: any = await this.listAuthSysOrgFunc(req, system, action); let privilege = x.privilege; From 8670d609baee3ee6d12779744e1d28e2884ff42a Mon Sep 17 00:00:00 2001 From: "DESKTOP-1R2VSQH\\Lenovo ThinkPad E490" Date: Thu, 7 May 2026 15:24:09 +0700 Subject: [PATCH 4/5] fix --- src/controllers/PermissionController.ts | 28 +++++++++++-------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/src/controllers/PermissionController.ts b/src/controllers/PermissionController.ts index 1116396c..8ff3dfb5 100644 --- a/src/controllers/PermissionController.ts +++ b/src/controllers/PermissionController.ts @@ -71,9 +71,8 @@ export class PermissionController extends Controller { orgRevision?.id ); - // ใช้ cache key ที่รวมสถานะ acting - const cacheKey = `role_${profile.id}_${actingData.isAct ? 'acting' : 'normal'}`; - let reply = await getAsync(cacheKey); + // ใช้ cache key เดิม และตรวจสอบสถานะ acting ทุกครั้ง + let reply = await getAsync("role_" + profile.id); if (reply != null) { reply = JSON.parse(reply); } else { @@ -253,7 +252,7 @@ export class PermissionController extends Controller { isActing: false, }; } - redisClient.setex(cacheKey, 86400, JSON.stringify(reply)); + redisClient.setex("role_" + profile.id, 86400, JSON.stringify(reply)); } return new HttpSuccess(reply); } @@ -295,9 +294,8 @@ export class PermissionController extends Controller { orgRevision?.id ); - // ใช้ cache key แบบเดียวกับ getPermission - const cacheKey = `menu_${profile.id}_${actingData.isAct ? 'acting' : 'normal'}`; - let reply = await getAsync(cacheKey); + // ใช้ cache key เดิม + let reply = await getAsync("menu_" + profile.id); if (reply != null) { reply = JSON.parse(reply); } else { @@ -414,7 +412,7 @@ export class PermissionController extends Controller { }; }); - redisClient.setex(cacheKey, 86400, JSON.stringify(reply)); + redisClient.setex("menu_" + profile.id, 86400, JSON.stringify(reply)); } return new HttpSuccess(reply); @@ -883,9 +881,8 @@ export class PermissionController extends Controller { orgRevision?.id ); - // ใช้ cache key แบบเดียวกับ getPermission() - const cacheKey = `role_${profile.id}_${actingData.isAct ? 'acting' : 'normal'}`; - let reply = await getAsync(cacheKey); + // ใช้ cache key เดิม + let reply = await getAsync("role_" + profile.id); if (reply != null) { reply = JSON.parse(reply); } else { @@ -1054,7 +1051,7 @@ export class PermissionController extends Controller { }; } - redisClient.setex(cacheKey, 86400, JSON.stringify(reply)); + redisClient.setex("role_" + profile.id, 86400, JSON.stringify(reply)); } return reply; } @@ -1118,9 +1115,8 @@ export class PermissionController extends Controller { orgRevision?.id ); - // ใช้ cache key แบบใหม่ - const cacheKey = `posMaster_${profile.id}_${actingData.isAct ? 'acting' : 'normal'}`; - let reply = await getAsync(cacheKey); + // ใช้ cache key เดิม + let reply = await getAsync("posMaster_" + profile.id); if (reply != null) { reply = JSON.parse(reply); } else { @@ -1152,7 +1148,7 @@ export class PermissionController extends Controller { reply = await this.getBaseOrgScope(profile.id, orgRevision?.id, profileType, privilege); } - redisClient.setex(cacheKey, 86400, JSON.stringify(reply)); + redisClient.setex("posMaster_" + profile.id, 86400, JSON.stringify(reply)); } return reply; } From aff6200368592e5447b67d7260712e83f6cfe480 Mon Sep 17 00:00:00 2001 From: "DESKTOP-1R2VSQH\\Lenovo ThinkPad E490" Date: Thu, 7 May 2026 17:01:03 +0700 Subject: [PATCH 5/5] refactor(PosMasterActController): redisClient.del role_ menu_ --- src/controllers/PosMasterActController.ts | 66 +++++++++++++++-------- 1 file changed, 45 insertions(+), 21 deletions(-) diff --git a/src/controllers/PosMasterActController.ts b/src/controllers/PosMasterActController.ts index dd4acd1b..72aac19b 100644 --- a/src/controllers/PosMasterActController.ts +++ b/src/controllers/PosMasterActController.ts @@ -24,6 +24,10 @@ import Extension from "../interfaces/extension"; import { ProfileActposition } from "../entities/ProfileActposition"; import { RequestWithUser } from "../middlewares/user"; import { escape } from "querystring"; +import { promisify } from "util"; + +const REDIS_HOST = process.env.REDIS_HOST; +const REDIS_PORT = process.env.REDIS_PORT; @Route("api/v1/org/pos/act") @Tags("PosMasterAct") @@ -37,6 +41,7 @@ export class PosMasterActController extends Controller { private posMasterActRepository = AppDataSource.getRepository(PosMasterAct); private posMasterRepository = AppDataSource.getRepository(PosMaster); private actpositionRepository = AppDataSource.getRepository(ProfileActposition); + private redis = require("redis"); /** * API เพิ่มรักษาการในตำแหน่ง @@ -92,7 +97,6 @@ export class PosMasterActController extends Controller { return new HttpSuccess(posMasterAct); } - /** * API ค้นหาตำแหน่งในระบบสมัครสอบ ขรก. * @@ -125,9 +129,7 @@ export class PosMasterActController extends Controller { throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลประเภทตำแหน่งนี้"); } - let posId: any[] = posMasterMain.posMasterActs.map( - (x) => x.posMasterChildId - ); + let posId: any[] = posMasterMain.posMasterActs.map((x) => x.posMasterChildId); posId.push(body.posmasterId); const query = await AppDataSource.getRepository(PosMaster) @@ -172,31 +174,31 @@ export class PosMasterActController extends Controller { posMasterMain.orgRootId == null ? "posMaster.orgRootId IS NULL" : "posMaster.orgRootId = :orgRootId", - { orgRootId: posMasterMain.orgRootId } + { orgRootId: posMasterMain.orgRootId }, ) .andWhere( posMasterMain.orgChild1Id == null ? "posMaster.orgChild1Id IS NULL" : "posMaster.orgChild1Id = :orgChild1Id", - { orgChild1Id: posMasterMain.orgChild1Id } + { orgChild1Id: posMasterMain.orgChild1Id }, ) .andWhere( posMasterMain.orgChild2Id == null ? "posMaster.orgChild2Id IS NULL" : "posMaster.orgChild2Id = :orgChild2Id", - { orgChild2Id: posMasterMain.orgChild2Id } + { orgChild2Id: posMasterMain.orgChild2Id }, ) .andWhere( posMasterMain.orgChild3Id == null ? "posMaster.orgChild3Id IS NULL" : "posMaster.orgChild3Id = :orgChild3Id", - { orgChild3Id: posMasterMain.orgChild3Id } + { orgChild3Id: posMasterMain.orgChild3Id }, ) .andWhere( posMasterMain.orgChild4Id == null ? "posMaster.orgChild4Id IS NULL" : "posMaster.orgChild4Id = :orgChild4Id", - { orgChild4Id: posMasterMain.orgChild4Id } + { orgChild4Id: posMasterMain.orgChild4Id }, ); } } else { @@ -210,7 +212,7 @@ export class PosMasterActController extends Controller { new Brackets((qb) => { qb.where( `CONCAT(current_holder.prefix, current_holder.firstName, ' ', current_holder.lastName) LIKE :keyword`, - { keyword: `%${keyword}%` } + { keyword: `%${keyword}%` }, ) .orWhere(`current_holder.citizenId LIKE :keyword`, { keyword: `%${keyword}%`, @@ -228,7 +230,7 @@ export class PosMasterActController extends Controller { ' ', posMaster.posMasterNo ) LIKE :keyword`, - { keyword: `%${keyword}%` } + { keyword: `%${keyword}%` }, ) .orWhere(`posLevel.posLevelName LIKE :keyword`, { keyword: `%${keyword}%`, @@ -238,8 +240,8 @@ export class PosMasterActController extends Controller { }) .orWhere(`current_holder.position LIKE :keyword`, { keyword: `%${keyword}%`, - }) - }) + }); + }), ); } @@ -280,7 +282,6 @@ export class PosMasterActController extends Controller { return new HttpSuccess({ data: data, total }); } - /** * API ลบรักษาการในตำแหน่ง * @@ -690,12 +691,12 @@ export class PosMasterActController extends Controller { x.posMasterChild?.orgRoot?.orgRootShortName, ].find((name) => !!name) && x.posMasterChild?.posMasterNo ? `${[ - x.posMasterChild?.orgChild4?.orgChild4ShortName, - x.posMasterChild?.orgChild3?.orgChild3ShortName, - x.posMasterChild?.orgChild2?.orgChild2ShortName, - x.posMasterChild?.orgChild1?.orgChild1ShortName, - x.posMasterChild?.orgRoot?.orgRootShortName, - ].find((name) => !!name)} ${x.posMasterChild.posMasterNo}` + x.posMasterChild?.orgChild4?.orgChild4ShortName, + x.posMasterChild?.orgChild3?.orgChild3ShortName, + x.posMasterChild?.orgChild2?.orgChild2ShortName, + x.posMasterChild?.orgChild1?.orgChild1ShortName, + x.posMasterChild?.orgRoot?.orgRootShortName, + ].find((name) => !!name)} ${x.posMasterChild.posMasterNo}` : x.posMasterChild?.posMasterNo || null; const orgShortNameAct = [ @@ -768,6 +769,9 @@ export class PosMasterActController extends Controller { throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลรักษาการในตำแหน่งของหน่วยงานนี้"); } + // เก็บรวบรวม profileIds ทั้งหมดเพื่อ clear cache หลังจากบันทึกเสร็จ + const profileIdsToClearCache = new Set(); + await Promise.all( posMasterActs.map(async (posMasterAct) => { const orgShortName = @@ -782,6 +786,8 @@ export class PosMasterActController extends Controller { const profileId = posMasterAct.posMasterChild?.current_holderId; if (profileId) { + profileIdsToClearCache.add(profileId); + const existingActivePositions = await this.actpositionRepository.find({ select: [ "id", @@ -790,7 +796,7 @@ export class PosMasterActController extends Controller { "lastUpdateFullName", "lastUpdatedAt", "dateEnd", - "isDeleted" + "isDeleted", ], where: { profileId, status: true, isDeleted: false }, }); @@ -834,6 +840,24 @@ export class PosMasterActController extends Controller { }), ); + // Clear Redis cache หลังจากบันทึกข้อมูลเสร็จแล้ว + // ทำงานนอก loop เพื่อ clear รอบเดียว ไม่ใช่ทุก iteration + if (profileIdsToClearCache.size > 0) { + await Promise.all( + Array.from(profileIdsToClearCache).map(async (profileId) => { + const redisClient = await this.redis.createClient({ + host: REDIS_HOST, + port: REDIS_PORT, + }); + + const delAsync = promisify(redisClient.del).bind(redisClient); + await delAsync("role_" + profileId); + await delAsync("menu_" + profileId); + + redisClient.quit(); + }), + ); + } return new HttpSuccess(); } }