refactor(PermissionController): getPermission

This commit is contained in:
DESKTOP-1R2VSQH\Lenovo ThinkPad E490 2026-05-07 13:27:27 +07:00
parent c1a4df63e5
commit fe1ebaa1cf
3 changed files with 230 additions and 13 deletions

View file

@ -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) { if (reply != null) {
reply = JSON.parse(reply); reply = JSON.parse(reply);
} else { } else {
const orgRevision = await this.orgRevisionRepository.findOne({
select: ["id"],
where: {
orgRevisionIsDraft: false,
orgRevisionIsCurrent: true,
},
});
let posMaster: any = await this.posMasterRepository.findOne({ let posMaster: any = await this.posMasterRepository.findOne({
select: ["authRoleId"], select: ["authRoleId"],
where: { where: {
@ -111,11 +120,140 @@ export class PermissionController extends Controller {
where: { authRoleId: getDetail.id }, where: { authRoleId: getDetail.id },
}); });
reply = { // ถ้า User มีตำแหน่งรักษาการ ให้รวมสิทธิ์
...getDetail, if (actingData.isAct && actingData.posMasterActs.length > 0) {
roles: roleAttrData, // ดึง authRoleId ของทุกตำแหน่งรักษาการ
}; const actingAuthRoleIds = await this.posMasterActRepo
redisClient.setex("role_" + profile.id, 86400, JSON.stringify(reply)); .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<string, any>();
// ลำดับความสำคัญของ privilege (มากไปน้อย)
const privilegePriority: Record<string, number> = {
"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); return new HttpSuccess(reply);
} }

View file

@ -25,6 +25,9 @@ import { ProfileActposition } from "../entities/ProfileActposition";
import { RequestWithUser } from "../middlewares/user"; import { RequestWithUser } from "../middlewares/user";
import { escape } from "querystring"; import { escape } from "querystring";
const REDIS_HOST = process.env.REDIS_HOST;
const REDIS_PORT = process.env.REDIS_PORT;
@Route("api/v1/org/pos/act") @Route("api/v1/org/pos/act")
@Tags("PosMasterAct") @Tags("PosMasterAct")
@Security("bearerAuth") @Security("bearerAuth")
@ -37,6 +40,23 @@ export class PosMasterActController extends Controller {
private posMasterActRepository = AppDataSource.getRepository(PosMasterAct); private posMasterActRepository = AppDataSource.getRepository(PosMasterAct);
private posMasterRepository = AppDataSource.getRepository(PosMaster); private posMasterRepository = AppDataSource.getRepository(PosMaster);
private actpositionRepository = AppDataSource.getRepository(ProfileActposition); 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 * API
@ -89,6 +109,12 @@ export class PosMasterActController extends Controller {
posMasterAct.createdAt = new Date(); posMasterAct.createdAt = new Date();
posMasterAct.lastUpdatedAt = new Date(); posMasterAct.lastUpdatedAt = new Date();
await this.posMasterActRepository.save(posMasterAct); await this.posMasterActRepository.save(posMasterAct);
// ลบ cache ของผู้ถูกรักษาการ (current_holder ของ posMasterChild)
if (posMasterChild.current_holderId) {
await this.invalidatePermissionCache([posMasterChild.current_holderId]);
}
return new HttpSuccess(posMasterAct); return new HttpSuccess(posMasterAct);
} }
@ -295,6 +321,7 @@ export class PosMasterActController extends Controller {
where: { where: {
id: id, id: id,
}, },
relations: ["posMasterChild"],
}); });
try { try {
result = await this.posMasterActRepository.delete({ id: id }); result = await this.posMasterActRepository.delete({ id: id });
@ -318,6 +345,11 @@ export class PosMasterActController extends Controller {
p.posMasterOrder = i + 1; p.posMasterOrder = i + 1;
await this.posMasterActRepository.save(p); await this.posMasterActRepository.save(p);
}); });
// ลบ cache ของผู้ถูกรักษาการ
if (posMasterAct.posMasterChild?.current_holderId) {
await this.invalidatePermissionCache([posMasterAct.posMasterChild.current_holderId]);
}
} }
return new HttpSuccess(); return new HttpSuccess();
} }
@ -768,6 +800,9 @@ export class PosMasterActController extends Controller {
throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลรักษาการในตำแหน่งของหน่วยงานนี้"); throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลรักษาการในตำแหน่งของหน่วยงานนี้");
} }
// เก็บ profileIds ที่ได้รับผลกระทบเพื่อลบ cache
const affectedProfileIds: string[] = [];
await Promise.all( await Promise.all(
posMasterActs.map(async (posMasterAct) => { posMasterActs.map(async (posMasterAct) => {
const orgShortName = const orgShortName =
@ -782,6 +817,8 @@ export class PosMasterActController extends Controller {
const profileId = posMasterAct.posMasterChild?.current_holderId; const profileId = posMasterAct.posMasterChild?.current_holderId;
if (profileId) { if (profileId) {
affectedProfileIds.push(profileId);
const existingActivePositions = await this.actpositionRepository.find({ const existingActivePositions = await this.actpositionRepository.find({
select: [ select: [
"id", "id",
@ -834,6 +871,11 @@ export class PosMasterActController extends Controller {
}), }),
); );
// ลบ cache ของผู้ถูกรักษาการทั้งหมด
if (affectedProfileIds.length > 0) {
await this.invalidatePermissionCache(affectedProfileIds);
}
return new HttpSuccess(); return new HttpSuccess();
} }
} }

View file

@ -25,6 +25,10 @@ import HttpStatus from "../interfaces/http-status";
import HttpSuccess from "../interfaces/http-success"; import HttpSuccess from "../interfaces/http-success";
import permission from "../interfaces/permission"; import permission from "../interfaces/permission";
import { setLogDataDiff } from "../interfaces/utils"; 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") @Route("api/v1/org/profile/actposition")
@Tags("ProfileActposition") @Tags("ProfileActposition")
@Security("bearerAuth") @Security("bearerAuth")
@ -32,6 +36,23 @@ export class ProfileActpositionController extends Controller {
private profileRepo = AppDataSource.getRepository(Profile); private profileRepo = AppDataSource.getRepository(Profile);
private profileActpositionRepo = AppDataSource.getRepository(ProfileActposition); private profileActpositionRepo = AppDataSource.getRepository(ProfileActposition);
private profileActpositionHistoryRepo = AppDataSource.getRepository(ProfileActpositionHistory); 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") @Get("user")
public async detailProfileActpositionUser(@Request() request: { user: Record<string, any> }) { public async detailProfileActpositionUser(@Request() request: { user: Record<string, any> }) {
@ -161,6 +182,10 @@ export class ProfileActpositionController extends Controller {
history.profileActpositionId = data.id; history.profileActpositionId = data.id;
await this.profileActpositionHistoryRepo.save(history, { data: req }); await this.profileActpositionHistoryRepo.save(history, { data: req });
//setLogDataDiff(req, { before, after: history }); //setLogDataDiff(req, { before, after: history });
// ลบ cache เมื่อสร้าง acting position ใหม่
await this.invalidatePermissionCache([body.profileId]);
return new HttpSuccess(data.id); return new HttpSuccess(data.id);
} }
@ -198,6 +223,9 @@ export class ProfileActpositionController extends Controller {
// setLogDataDiff(req, { before: before_null, after: history }), // setLogDataDiff(req, { before: before_null, after: history }),
]); ]);
// ลบ cache เมื่อแก้ไข acting position
await this.invalidatePermissionCache([record.profileId]);
return new HttpSuccess(); return new HttpSuccess();
} }
@ -236,6 +264,9 @@ export class ProfileActpositionController extends Controller {
this.profileActpositionHistoryRepo.save(history, { data: req }), this.profileActpositionHistoryRepo.save(history, { data: req }),
]); ]);
// ลบ cache เมื่อ soft delete acting position
await this.invalidatePermissionCache([record.profileId]);
return new HttpSuccess(); return new HttpSuccess();
} }
@ -245,6 +276,7 @@ export class ProfileActpositionController extends Controller {
@Request() req: RequestWithUser, @Request() req: RequestWithUser,
) { ) {
const _record = await this.profileActpositionRepo.findOneBy({ id: actpositionId }); const _record = await this.profileActpositionRepo.findOneBy({ id: actpositionId });
const profileId = _record?.profileId;
if (_record) { if (_record) {
await new permission().PermissionOrgUserDelete( await new permission().PermissionOrgUserDelete(
req, req,
@ -261,6 +293,11 @@ export class ProfileActpositionController extends Controller {
if (result.affected == undefined || result.affected <= 0) if (result.affected == undefined || result.affected <= 0)
throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล");
// ลบ cache เมื่อ delete acting position
if (profileId) {
await this.invalidatePermissionCache([profileId]);
}
return new HttpSuccess(); return new HttpSuccess();
} }
} }