diff --git a/src/controllers/CommandController.ts b/src/controllers/CommandController.ts index aa2ab3d1..857d6d55 100644 --- a/src/controllers/CommandController.ts +++ b/src/controllers/CommandController.ts @@ -64,6 +64,7 @@ import { getRoleMappings, removeUserRoles, getToken, + updateUserAttributes, } from "../keycloak"; import { ProfileEducation, CreateProfileEducation } from "../entities/ProfileEducation"; import { ProfileEducationHistory } from "../entities/ProfileEducationHistory"; @@ -4242,6 +4243,12 @@ export class CommandController extends Controller { profile.isActive = true; } await this.profileRepository.save(profile); + // update user attribute in keycloak + await updateUserAttributes(profile.keycloak ?? "", { + profileId: [profile.id], + prefix: [profile.prefix || ""], + }); + // Task #2190 if (code && ["C-PM-17", "C-PM-18"].includes(code)) { let organizeName = ""; @@ -6329,8 +6336,7 @@ export class CommandController extends Controller { name: x.name, })), ); - } - else { + } else { userKeycloakId = checkUser[0].id; const rolesData = await getRoleMappings(userKeycloakId); if (rolesData) { @@ -6393,9 +6399,9 @@ export class CommandController extends Controller { if (profileEmployee.keycloak != null) { // const delUserKeycloak = await deleteUser(profileEmployee.keycloak); // if (delUserKeycloak) { - profileEmployee.keycloak = _null; - profileEmployee.roleKeycloaks = []; - profileEmployee.isActive = false; + profileEmployee.keycloak = _null; + profileEmployee.roleKeycloaks = []; + profileEmployee.isActive = false; // } } profileEmployee.isLeave = true; @@ -6448,6 +6454,11 @@ export class CommandController extends Controller { profile.phone = item.bodyProfile.phone ?? null; await this.profileRepository.save(profile); + // update user attribute in keycloak + await updateUserAttributes(profile.keycloak ?? "", { + profileId: [profile.id], + prefix: [profile.prefix || ""], + }); setLogDataDiff(req, { before, after: profile }); } //ขรก.ในระบบ หรือ ขรก.ในระบบที่สถานะพ้นจากราชการ diff --git a/src/controllers/MyController.ts b/src/controllers/MyController.ts index f8fbc848..16809b6d 100644 --- a/src/controllers/MyController.ts +++ b/src/controllers/MyController.ts @@ -1,4 +1,3 @@ -import { profile } from "console"; import { Controller, Get, Post, Query, Route, Security, Tags } from "tsoa"; import { calculateGovAge } from "../interfaces/utils"; import HttpSuccess from "../interfaces/http-success"; @@ -14,7 +13,7 @@ export class AppController extends Controller { @Post() public async Post(@Query() profileId: string) { - const result = calculateGovAge(profileId,"OFFICER"); + const result = calculateGovAge(profileId, "OFFICER"); return new HttpSuccess(result); } } diff --git a/src/controllers/OrganizationController.ts b/src/controllers/OrganizationController.ts index 89015402..fdf24b66 100644 --- a/src/controllers/OrganizationController.ts +++ b/src/controllers/OrganizationController.ts @@ -50,8 +50,13 @@ import { getUserByUsername, getRoles, addUserRoles, + updateUserAttributes, } from "../keycloak"; -import { getPositionCountsAggregated, getPositionCount, PositionCountsByNode } from "../services/OrganizationService"; +import { + getPositionCountsAggregated, + getPositionCount, + PositionCountsByNode, +} from "../services/OrganizationService"; import { BatchSavePosMasterHistoryOfficer, CreatePosMasterHistoryEmployee, @@ -4688,7 +4693,12 @@ export class OrganizationController extends Controller { case 2: { const data = await this.child1Repository.findOne({ where: { id: idNode }, - relations: ["orgRevision", "orgChild2s", "orgChild2s.orgChild3s", "orgChild2s.orgChild3s.orgChild4s"], + relations: [ + "orgRevision", + "orgChild2s", + "orgChild2s.orgChild3s", + "orgChild2s.orgChild3s.orgChild4s", + ], }); if (!data) { throw new HttpError(HttpStatusCode.NOT_FOUND, "not found child1Id"); @@ -4700,7 +4710,9 @@ export class OrganizationController extends Controller { deptID: data.id, type: 2, totalPositionCount: child1Counts.totalCount, - totalPositionVacant: isDraft ? child1Counts.nextVacantCount : child1Counts.currentVacantCount, + totalPositionVacant: isDraft + ? child1Counts.nextVacantCount + : child1Counts.currentVacantCount, children: this.buildOrgChild2s(data.orgChild2s, positionCounts, isDraft), }; return new HttpSuccess([formattedData]); @@ -4720,7 +4732,9 @@ export class OrganizationController extends Controller { deptID: data.id, type: 3, totalPositionCount: child2Counts.totalCount, - totalPositionVacant: isDraft ? child2Counts.nextVacantCount : child2Counts.currentVacantCount, + totalPositionVacant: isDraft + ? child2Counts.nextVacantCount + : child2Counts.currentVacantCount, children: this.buildOrgChild3s(data.orgChild3s, positionCounts, isDraft), }; return new HttpSuccess([formattedData]); @@ -4740,7 +4754,9 @@ export class OrganizationController extends Controller { deptID: data.id, type: 4, totalPositionCount: child3Counts.totalCount, - totalPositionVacant: isDraft ? child3Counts.nextVacantCount : child3Counts.currentVacantCount, + totalPositionVacant: isDraft + ? child3Counts.nextVacantCount + : child3Counts.currentVacantCount, children: this.buildOrgChild4s(data.orgChild4s, positionCounts, isDraft), }; return new HttpSuccess([formattedData]); @@ -4760,7 +4776,9 @@ export class OrganizationController extends Controller { deptID: data.id, type: 5, totalPositionCount: child4Counts.totalCount, - totalPositionVacant: isDraft ? child4Counts.nextVacantCount : child4Counts.currentVacantCount, + totalPositionVacant: isDraft + ? child4Counts.nextVacantCount + : child4Counts.currentVacantCount, }; return new HttpSuccess([formattedData]); } @@ -8089,7 +8107,12 @@ export class OrganizationController extends Controller { if (_item) { _item.roleKeycloaks = Array.from(new Set([..._item.roleKeycloaks, ...roleKeycloak])); check += 1; - await this.profileEmployeeRepo.save(_item); + _item = await this.profileEmployeeRepo.save(_item); + // update user attribute in keycloak + await updateUserAttributes(_item.keycloak, { + profileId: [_item.id], + prefix: [_item.prefix || ""], + }); } } catch (error) { console.error(`Error processing ${_item.citizenId}:`, error); @@ -9096,7 +9119,7 @@ export class OrganizationController extends Controller { */ private sumAllVacantCounts( map: Map, - isDraft: boolean + isDraft: boolean, ): number { let sum = 0; for (const value of map.values()) { @@ -9111,7 +9134,7 @@ export class OrganizationController extends Controller { private buildOrgRoots( orgRoots: OrgRoot[], positionCounts: PositionCountsByNode, - isDraft: boolean + isDraft: boolean, ) { if (!orgRoots) return []; return orgRoots @@ -9135,7 +9158,7 @@ export class OrganizationController extends Controller { private buildOrgChild1s( orgChild1s: OrgChild1[], positionCounts: PositionCountsByNode, - isDraft: boolean + isDraft: boolean, ) { if (!orgChild1s) return []; return orgChild1s @@ -9159,7 +9182,7 @@ export class OrganizationController extends Controller { private buildOrgChild2s( orgChild2s: OrgChild2[], positionCounts: PositionCountsByNode, - isDraft: boolean + isDraft: boolean, ) { if (!orgChild2s) return []; return orgChild2s @@ -9183,7 +9206,7 @@ export class OrganizationController extends Controller { private buildOrgChild3s( orgChild3s: OrgChild3[], positionCounts: PositionCountsByNode, - isDraft: boolean + isDraft: boolean, ) { if (!orgChild3s) return []; return orgChild3s @@ -9207,7 +9230,7 @@ export class OrganizationController extends Controller { private buildOrgChild4s( orgChild4s: OrgChild4[], positionCounts: PositionCountsByNode, - isDraft: boolean + isDraft: boolean, ) { if (!orgChild4s) return []; return orgChild4s diff --git a/src/controllers/ProfileChangeNameController.ts b/src/controllers/ProfileChangeNameController.ts index 26741c46..77e63aa6 100644 --- a/src/controllers/ProfileChangeNameController.ts +++ b/src/controllers/ProfileChangeNameController.ts @@ -116,7 +116,12 @@ export class ProfileChangeNameController extends Controller { setLogDataDiff(req, { before, after: profile }); if (profile != null && profile.keycloak != null) { - const result = await updateName(profile.keycloak, profile.firstName, profile.lastName); + const result = await updateName( + profile.keycloak, + profile.firstName, + profile.lastName, + profile.prefix, + ); if (!result) { throw new Error(result.errorMessage); } @@ -182,7 +187,12 @@ export class ProfileChangeNameController extends Controller { // ปิดไว้ก่อนเพราะ error ต้องใช้ keycloak ที่มีสิทธิ์ในการ update //update 17/07 if (profile != null && profile.keycloak != null) { - const result = await updateName(profile.keycloak, profile.firstName, profile.lastName); + const result = await updateName( + profile.keycloak, + profile.firstName, + profile.lastName, + profile.prefix, + ); if (!result) { throw new Error(result.errorMessage); } @@ -197,10 +207,7 @@ export class ProfileChangeNameController extends Controller { * @param trainingId คีย์ประวัติการเปลี่ยนชื่อ - นามสกุล */ @Patch("update-delete/{changeNameId}") - public async updateIsDeleted( - @Request() req: RequestWithUser, - @Path() changeNameId: string, - ) { + public async updateIsDeleted(@Request() req: RequestWithUser, @Path() changeNameId: string) { const record = await this.changeNameRepository.findOneBy({ id: changeNameId }); if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); if (record.isDeleted === true) { diff --git a/src/controllers/ProfileChangeNameEmployeeController.ts b/src/controllers/ProfileChangeNameEmployeeController.ts index 7772646d..76f5da7f 100644 --- a/src/controllers/ProfileChangeNameEmployeeController.ts +++ b/src/controllers/ProfileChangeNameEmployeeController.ts @@ -122,7 +122,12 @@ export class ProfileChangeNameEmployeeController extends Controller { setLogDataDiff(req, { before, after: profile }); if (profile != null && profile.keycloak != null) { - const result = await updateName(profile.keycloak, profile.firstName, profile.lastName); + const result = await updateName( + profile.keycloak, + profile.firstName, + profile.lastName, + profile.prefix, + ); if (!result) { throw new Error(result.errorMessage); } @@ -195,16 +200,17 @@ export class ProfileChangeNameEmployeeController extends Controller { * @param trainingId คีย์ประวัติการเปลี่ยนชื่อ - นามสกุล */ @Patch("update-delete/{changeNameId}") - public async updateIsDeleted( - @Request() req: RequestWithUser, - @Path() changeNameId: string, - ) { + public async updateIsDeleted(@Request() req: RequestWithUser, @Path() changeNameId: string) { const record = await this.changeNameRepository.findOneBy({ id: changeNameId }); if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); if (record.isDeleted === true) { return new HttpSuccess(); } - await new permission().PermissionOrgUserDelete(req, "SYS_REGISTRY_EMP", record.profileEmployeeId); + await new permission().PermissionOrgUserDelete( + req, + "SYS_REGISTRY_EMP", + record.profileEmployeeId, + ); const before = structuredClone(record); const history = new ProfileChangeNameHistory(); const now = new Date(); diff --git a/src/controllers/ProfileChangeNameEmployeeTempController.ts b/src/controllers/ProfileChangeNameEmployeeTempController.ts index 049e2b07..78aaa09d 100644 --- a/src/controllers/ProfileChangeNameEmployeeTempController.ts +++ b/src/controllers/ProfileChangeNameEmployeeTempController.ts @@ -114,7 +114,12 @@ export class ProfileChangeNameEmployeeTempController extends Controller { setLogDataDiff(req, { before, after: profile }); if (profile != null && profile.keycloak != null) { - const result = await updateName(profile.keycloak, profile.firstName, profile.lastName); + const result = await updateName( + profile.keycloak, + profile.firstName, + profile.lastName, + profile.prefix, + ); if (!result) { throw new Error(result.errorMessage); } @@ -187,10 +192,7 @@ export class ProfileChangeNameEmployeeTempController extends Controller { * @param trainingId คีย์ประวัติการเปลี่ยนชื่อ - นามสกุล */ @Patch("update-delete/{changeNameId}") - public async updateIsDeleted( - @Request() req: RequestWithUser, - @Path() changeNameId: string, - ) { + public async updateIsDeleted(@Request() req: RequestWithUser, @Path() changeNameId: string) { const record = await this.changeNameRepository.findOneBy({ id: changeNameId }); if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); if (record.isDeleted === true) { diff --git a/src/controllers/ProfileController.ts b/src/controllers/ProfileController.ts index 483c580d..5fbfa83b 100644 --- a/src/controllers/ProfileController.ts +++ b/src/controllers/ProfileController.ts @@ -5092,7 +5092,12 @@ export class ProfileController extends Controller { // setLogDataDiff(request, { before, after: record }); if (record != null && record.keycloak != null) { - const result = await updateName(record.keycloak, record.firstName, record.lastName); + const result = await updateName( + record.keycloak, + record.firstName, + record.lastName, + record.prefix, + ); if (!result) { throw new Error(result.errorMessage); } @@ -5406,7 +5411,12 @@ export class ProfileController extends Controller { setLogDataDiff(request, { before, after: record }); if (record != null && record.keycloak != null) { - const result = await updateName(record.keycloak, record.firstName, record.lastName); + const result = await updateName( + record.keycloak, + record.firstName, + record.lastName, + record.prefix, + ); if (!result) { throw new Error(result.errorMessage); } @@ -5507,27 +5517,27 @@ export class ProfileController extends Controller { @Get("history/user") async getHistoryProfileByUser(@Request() request: RequestWithUser) { const profile = await this.profileRepo.findOne({ - where: { keycloak: request.user.sub } + where: { keycloak: request.user.sub }, }); if (!profile) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); const profileHistory = await this.profileHistoryRepo.find({ where: { profileId: profile.id }, - order: { createdAt: "ASC" } + order: { createdAt: "ASC" }, }); - + if (profileHistory.length == 0) { await this.profileHistoryRepo.save( Object.assign(new ProfileHistory(), { ...profile, birthDateOld: profile?.birthDate, profileId: profile.id, - id: undefined + id: undefined, }), ); const firstRecord = await this.profileHistoryRepo.find({ where: { profileId: profile.id }, - order: { createdAt: "ASC" } + order: { createdAt: "ASC" }, }); return new HttpSuccess(firstRecord); } @@ -6482,27 +6492,27 @@ export class ProfileController extends Controller { async getProfileHistory(@Path() id: string, @Request() req: RequestWithUser) { //await new permission().PermissionOrgUserGet(req, "SYS_REGISTRY_OFFICER", id); //ไม่แน่ใจOFFปิดไว้ก่อน const profile = await this.profileRepo.findOne({ - where: { id: id } + where: { id: id }, }); if (!profile) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); const profileHistory = await this.profileHistoryRepo.find({ where: { profileId: id }, - order: { createdAt: "ASC" } + order: { createdAt: "ASC" }, }); - + if (profileHistory.length == 0) { await this.profileHistoryRepo.save( Object.assign(new ProfileHistory(), { ...profile, birthDateOld: profile?.birthDate, profileId: id, - id: undefined + id: undefined, }), ); const firstRecord = await this.profileHistoryRepo.find({ where: { profileId: id }, - order: { createdAt: "ASC" } + order: { createdAt: "ASC" }, }); return new HttpSuccess(firstRecord); } diff --git a/src/controllers/UserController.ts b/src/controllers/UserController.ts index ecfef0ca..9889bd9b 100644 --- a/src/controllers/UserController.ts +++ b/src/controllers/UserController.ts @@ -33,6 +33,7 @@ import { getUserByUsername, changeUserPassword, resetPassword, + updateUserAttributes, } from "../keycloak"; import { AppDataSource } from "../database/data-source"; import { Profile } from "../entities/Profile"; @@ -137,6 +138,13 @@ export class KeycloakController extends Controller { } profile.email = body.email == null ? _null : body.email; await this.profileRepo.save(profile); + + // Update Keycloak with profile prefix after profile is loaded + await updateUserAttributes(userId, { + profileId: [profile.id], + prefix: [profile.prefix || ""], + }); + if (body.roles != null && body.roles.length > 0) { const roleKeycloak = await this.roleKeycloakRepo.find({ where: { id: In(body.roles) }, @@ -195,6 +203,12 @@ export class KeycloakController extends Controller { } profile.email = body.email == null ? _null : body.email; await this.profileEmpRepo.save(profile); + // Update Keycloak with profile prefix after profile is loaded + await updateUserAttributes(userId, { + profileId: [profile.id], + prefix: [profile.prefix || ""], + }); + if (body.roles != null && body.roles.length > 0) { const roleKeycloak = await this.roleKeycloakRepo.find({ where: { id: In(body.roles) }, @@ -475,14 +489,14 @@ export class KeycloakController extends Controller { @Request() req: RequestWithUser, @Body() body: { - page: number, - pageSize: number, - keyword: string | null, - type: string, - isAll: boolean, - node: number | null, - nodeId: string | null, - } + page: number; + pageSize: number; + keyword: string | null; + type: string; + isAll: boolean; + node: number | null; + nodeId: string | null; + }, ) { let checkChildFromRole: any = {}; @@ -558,10 +572,14 @@ export class KeycloakController extends Controller { .andWhere( new Brackets((qb) => { qb.orWhere( - body.keyword != null && body.keyword != "" ? `profile.citizenId like '%${body.keyword}%'` : "1=1", + body.keyword != null && body.keyword != "" + ? `profile.citizenId like '%${body.keyword}%'` + : "1=1", ) .orWhere( - body.keyword != null && body.keyword != "" ? `profile.email like '%${body.keyword}%'` : "1=1", + body.keyword != null && body.keyword != "" + ? `profile.email like '%${body.keyword}%'` + : "1=1", ) .orWhere( body.keyword != null && body.keyword != "" @@ -737,6 +755,12 @@ export class KeycloakController extends Controller { } profile.email = body.email == null ? _null : body.email; await this.profileEmpRepo.save(profile); + // Update Keycloak with profile prefix after profile is loaded + await updateUserAttributes(userId, { + profileId: [profile.id], + prefix: [profile.prefix || ""], + }); + if (body.roles != null && body.roles.length > 0) { const roleKeycloak = await this.roleKeycloakRepo.find({ where: { id: In(body.roles) }, @@ -783,7 +807,6 @@ export class KeycloakController extends Controller { @Get("user/role/{id}") async getRoleUser(@Request() req: RequestWithUser, @Path("id") id: string) { - const profile = await this.profileRepo.findOne({ where: { keycloak: id }, relations: ["roleKeycloaks"], @@ -791,8 +814,8 @@ export class KeycloakController extends Controller { if ( req.user.sub === id && - req.user.role.some(x => x === 'ADMIN') && - !req.user.role.some(x => x === 'SUPER_ADMIN') + req.user.role.some((x) => x === "ADMIN") && + !req.user.role.some((x) => x === "SUPER_ADMIN") ) { throw new HttpError(HttpStatus.NOT_FOUND, "ไม่มีสิทธิ์เข้าถึงข้อมูลนี้"); } diff --git a/src/keycloak/index.ts b/src/keycloak/index.ts index 85724eec..90ab019c 100644 --- a/src/keycloak/index.ts +++ b/src/keycloak/index.ts @@ -298,6 +298,7 @@ export async function updateName( userId: string, firstName: string, lastName: string, + prefix: string, // opts: Record, ) { // const { password, ...rest } = opts; @@ -315,6 +316,9 @@ export async function updateName( // ...rest, firstName, lastName, + attributes: { + prefix, + }, }), }).catch((e) => console.log("Keycloak Error: ", e)); @@ -971,3 +975,44 @@ export async function getAllUsersPaginated( enabled: v.enabled === true || v.enabled === "true", })); } + +/** + * Create keycloak user by given username and password with roles + * + * Client must have permission to manage realm's user + * + * @returns user uuid or true if success, false otherwise. + */ +export async function createUserHaveProfile( + username: string, + password: string, + profileId: string, + prefix: string, + opts?: Record, + token?: string, +) { + const res = await fetch(`${KC_URL}/admin/realms/${KC_REALMS}/users`, { + // prettier-ignore + headers: { + "authorization": `Bearer ${token || await getToken()}`, + "content-type": `application/json`, + }, + method: "POST", + body: JSON.stringify({ + enabled: true, + credentials: [{ type: "password", value: password, temporary: false }], + username, + ...opts, + }), + }).catch((e) => console.log("Keycloak Error: ", e)); + + if (!res) return false; + if (!res.ok) { + // return Boolean(console.error("Keycloak Error Response: ", await res.json())); + return await res.json(); + } + + const path = res.headers.get("Location"); + const id = path?.split("/").at(-1); + return id || true; +} diff --git a/src/services/KeycloakAttributeService.ts b/src/services/KeycloakAttributeService.ts index c5759274..53cce4b4 100644 --- a/src/services/KeycloakAttributeService.ts +++ b/src/services/KeycloakAttributeService.ts @@ -5,7 +5,7 @@ import { ProfileEmployee } from "../entities/ProfileEmployee"; // import { EmployeePosMaster } from "../entities/EmployeePosMaster"; // import { OrgRoot } from "../entities/OrgRoot"; import { - createUser, + createUserHaveProfile, getUser, getUserByUsername, updateUserAttributes, @@ -809,12 +809,18 @@ export class KeycloakAttributeService { } // Create new user in Keycloak - const createResult = await createUser(profile.citizenId, "P@ssw0rd", { - firstName: profile.firstName || "", - lastName: profile.lastName || "", - email: profile.email || undefined, - enabled: true, - }); + const createResult = await createUserHaveProfile( + profile.citizenId, + "P@ssw0rd", + profile.id, + profile.prefix, + { + firstName: profile.firstName || "", + lastName: profile.lastName || "", + email: profile.email || undefined, + enabled: true, + }, + ); if (!createResult || typeof createResult !== "string") { return {