/** * Helper functions for Keycloak user operations * * สร้างเพื่อแยก Keycloak operations ออกจาก DB transaction * และใช้ profile data ที่ save แล้วแทน input data */ import { createUser, addUserRoles, removeUserRoles, updateUserAttributes, getUserByUsername, getRoleMappings, } from "./index"; import { Profile } from "../entities/Profile"; import HttpError from "../interfaces/http-error"; import HttpStatusCode from "../interfaces/http-status"; /** * สร้าง password จาก profile.birthDate เหมือน reset-password endpoint * * @param profile - Profile object ที่มี birthDate * @returns password string ในรูปแบบ ddmmyyyy (เช่น "31122563") * * Reference: UserController.ts reset-password (บรรทัด 867-876) */ export function generatePasswordFromProfile(profile: Profile): string { if (!profile.birthDate) { throw new Error("Profile birthDate is required for password generation"); } const _date = new Date(profile.birthDate.toDateString()) .getDate() .toString() .padStart(2, "0"); const _month = ( new Date(profile.birthDate.toDateString()).getMonth() + 1 ) .toString() .padStart(2, "0"); const _year = new Date(profile.birthDate.toDateString()).getFullYear() + 543; return `${_date}${_month}${_year}`; } /** * Sync Keycloak user สำหรับ profile ที่ save แล้ว * * รวมทุก Keycloak operations: * - ตรวจสอบว่ามี user อยู่แล้วหรือไม่ * - สร้าง user ใหม่ (ถ้ายังไม่มี) ด้วย password จาก profile.birthDate * - กำหนด role USER * - อัปเดต attributes (profileId, prefix) * * @param profile - Profile object ที่ถูก save แล้ว (ต้องมี id และ birthDate) * @param roleList - List of roles จาก Keycloak (ผลลัพธ์จาก getRoles()) * @returns userKeycloakId string หรือ throws HttpError * * @throws HttpError ถ้า createUser หรือ role operations ล้มเหลว */ export async function syncKeycloakForProfile( profile: Profile, roleList: any[], ): Promise { console.log( `[syncKeycloakForProfile] Starting for citizenId: ${profile.citizenId}, profileId: ${profile.id}`, ); if (!profile.id) { throw new Error("Profile ID is required for Keycloak sync"); } // ตรวจสอบว่ามี user อยู่แล้วหรือไม่ const checkUser = await getUserByUsername(profile.citizenId); console.log( `[syncKeycloakForProfile] Keycloak user exists: ${checkUser.length > 0}`, ); let userKeycloakId: string; if (checkUser.length === 0) { // สร้าง user ใหม่ console.log( `[syncKeycloakForProfile] Creating new Keycloak user for citizenId: ${profile.citizenId}`, ); // สร้าง password จาก profile.birthDate (NOT input birthDate) const password = generatePasswordFromProfile(profile); console.log( `[syncKeycloakForProfile] Generated password from profile.birthDate: ${profile.birthDate}`, ); // กรอง "." ออกจาก firstName ก่อนส่งไป keycloak const sanitizedFirstName = profile.firstName?.replace(/\./g, "") ?? ""; console.log( `[syncKeycloakForProfile] Creating user - firstName: ${sanitizedFirstName}, lastName: ${profile.lastName}`, ); const createUserResult: any = await createUser(profile.citizenId, password, { firstName: sanitizedFirstName, lastName: profile.lastName ?? "", }); // ตรวจสอบ createUser error if ( createUserResult && typeof createUserResult === "object" && createUserResult.errorMessage ) { console.error( `[syncKeycloakForProfile] createUser FAILED - field: ${createUserResult.field}, errorMessage: ${createUserResult.errorMessage}`, ); throw new HttpError( HttpStatusCode.BAD_REQUEST, `Keycloak validation failed: ${createUserResult.field} - ${createUserResult.errorMessage}`, ); } userKeycloakId = createUserResult; console.log( `[syncKeycloakForProfile] User created successfully, userKeycloakId: ${userKeycloakId}`, ); } else { // ใช้ user ที่มีอยู่แล้ว console.log( `[syncKeycloakForProfile] Using existing Keycloak user, userKeycloakId: ${checkUser[0].id}`, ); userKeycloakId = checkUser[0].id; // ลบ roles เดิมแล้วกำหนด role USER ใหม่ const rolesData = await getRoleMappings(userKeycloakId); if (rolesData) { const _delRole = rolesData.map((x: any) => ({ id: x.id, name: x.name, })); console.log( `[syncKeycloakForProfile] Removing old roles: ${_delRole.length}`, ); await removeUserRoles(userKeycloakId, _delRole); } } // กำหนด role USER console.log(`[syncKeycloakForProfile] Assigning USER role`); const result = await addUserRoles( userKeycloakId, roleList .filter((v) => v.name === "USER") .map((x) => ({ id: x.id, name: x.name, })), ); console.log(`[syncKeycloakForProfile] USER role assigned, result: ${result}`); // อัปเดต attributes console.log(`[syncKeycloakForProfile] Updating user attributes`); await updateUserAttributes(userKeycloakId, { profileId: [profile.id], prefix: [profile.prefix || ""], }); console.log( `[syncKeycloakForProfile] Completed successfully for citizenId: ${profile.citizenId}`, ); return userKeycloakId; } /** * จัดการ error จาก Keycloak operations * * @param error - Error object จาก Keycloak operations * @param context - Context object { citizenId, profileId, operation } * * @returns HttpError ที่ formatted สำหรับ throw กลับไป */ export function handleKeycloakError( error: any, context: { citizenId?: string; profileId?: string; operation: string; }, ): HttpError { console.error( `[KeycloakError] ${context.operation} failed for citizenId: ${context.citizenId}, profileId: ${context.profileId}`, error, ); const errorMessage = error instanceof Error ? error.message : "Unknown Keycloak error"; return new HttpError( HttpStatusCode.INTERNAL_SERVER_ERROR, `Keycloak ${context.operation} failed: ${errorMessage}. Profile created but Keycloak sync failed - manual fix required.`, ); }