diff --git a/src/controllers/ApiManageController.ts b/src/controllers/ApiManageController.ts index 01a27eb5..34f0c824 100644 --- a/src/controllers/ApiManageController.ts +++ b/src/controllers/ApiManageController.ts @@ -325,7 +325,123 @@ export class ApiManageController extends Controller { ]; private readonly DEFAULT_PAGE_SIZE = 10; // ขนาดหน้าเริ่มต้น - private readonly EXCLUDED_COLUMNS = ["createdUserId", "lastUpdateUserId"]; // ฟิลด์ที่ไม่ต้องการแสดงในผลลัพธ์ + private readonly EXCLUDED_COLUMNS = [ + "createdUserId", + "lastUpdateUserId", + "createdAt", + "createdFullName", + "lastUpdateFullName", + "avatarName", + "profileId", + "prefixId", + "profileEmployeeId", + "documentId", + "orgRevisionId", + "posMasterId", + "orgRootId", + "orgChild1Id", + "orgChild2Id", + "orgChild3Id", + "orgChild4Id", + "ancestorDNA", + "keycloak", + "commandId", + "prefixMain", + "authRoleId", + "next_holderId", + "current_holderId", + ]; // ฟิลด์ที่ไม่ต้องการแสดงในผลลัพธ์ + + // การแทนที่ฟิลด์ ID ด้วยฟิลด์ Name สำหรับ Profile entity + private readonly PROFILE_FIELD_REPLACEMENTS: Record< + string, + { propertyName: string; type: string; comment: string; joinTable: string; joinField: string } + > = { + posLevelId: { + propertyName: "posLevelName", + type: "string", + comment: "ระดับตำแหน่ง", + joinTable: "PosLevel", + joinField: "posLevelName", + }, + posTypeId: { + propertyName: "posTypeName", + type: "string", + comment: "ประเภทตำแหน่ง", + joinTable: "PosType", + joinField: "posTypeName", + }, + registrationProvinceId: { + propertyName: "registrationProvinceName", + type: "string", + comment: "จังหวัดตามทะเบียนบ้าน", + joinTable: "Province", + joinField: "name", + }, + registrationDistrictId: { + propertyName: "registrationDistrictName", + type: "string", + comment: "เขตตามทะเบียนบ้าน", + joinTable: "District", + joinField: "name", + }, + registrationSubDistrictId: { + propertyName: "registrationSubDistrictName", + type: "string", + comment: "แขวงตามทะเบียนบ้าน", + joinTable: "SubDistrict", + joinField: "name", + }, + currentProvinceId: { + propertyName: "currentProvinceName", + type: "string", + comment: "จังหวัดตามปัจจุบัน", + joinTable: "Province", + joinField: "name", + }, + currentDistrictId: { + propertyName: "currentDistrictName", + type: "string", + comment: "เขตตามปัจจุบัน", + joinTable: "District", + joinField: "name", + }, + currentSubDistrictId: { + propertyName: "currentSubDistrictName", + type: "string", + comment: "แขวงตามปัจจุบัน", + joinTable: "SubDistrict", + joinField: "name", + }, + }; + + // การแทนที่ฟิลด์ ID ด้วยฟิลด์ Name สำหรับ Position entity + private readonly POSITION_FIELD_REPLACEMENTS: Record< + string, + { propertyName: string; type: string; comment: string; joinTable: string; joinField: string } + > = { + posTypeId: { + propertyName: "posTypeName", + type: "string", + comment: "ประเภทตำแหน่ง", + joinTable: "PosType", + joinField: "posTypeName", + }, + posLevelId: { + propertyName: "posLevelName", + type: "string", + comment: "ระดับตำแหน่ง", + joinTable: "PosLevel", + joinField: "posLevelName", + }, + posExecutiveId: { + propertyName: "posExecutiveName", + type: "string", + comment: "ตำแหน่งทางการบริหาร", + joinTable: "PosExecutive", + joinField: "posExecutiveName", + }, + }; private validateSuperAdminRole(user: any): void { if (!user.role.includes("SUPER_ADMIN")) { @@ -364,11 +480,8 @@ export class ApiManageController extends Controller { const result = this.entities .filter((entity) => entity.system.includes(system)) - .map(({ name, repository, description, isMain }) => ({ - tb: name, - description, - isMain: isMain || false, - propertys: repository.metadata.columns + .map(({ name, repository, description, isMain }) => { + let columns = repository.metadata.columns .filter( (column: any) => !column.isPrimary && !this.EXCLUDED_COLUMNS.includes(column.propertyName), @@ -378,8 +491,74 @@ export class ApiManageController extends Controller { type: typeof column.type === "string" ? column.type : "string", comment: column.comment, key: column.propertyName, - })), - })); + })); + + // Special handling for Profile entity - replace ID fields with name fields + if (name === "Profile") { + const replacementKeys = Object.keys(this.PROFILE_FIELD_REPLACEMENTS); + + // Remove ID fields that should be replaced + columns = columns.filter( + (col: { propertyName: string }) => !replacementKeys.includes(col.propertyName), + ); + + // Add the corresponding name fields + const nameFields = replacementKeys.map((key) => ({ + propertyName: this.PROFILE_FIELD_REPLACEMENTS[key].propertyName, + type: "string", + comment: this.PROFILE_FIELD_REPLACEMENTS[key].comment, + key: this.PROFILE_FIELD_REPLACEMENTS[key].propertyName, + })); + + columns = [...columns, ...nameFields]; + } + + // Special handling for Position entity - replace ID fields with name fields + if (name === "Position") { + const replacementKeys = Object.keys(this.POSITION_FIELD_REPLACEMENTS); + + // Remove ID fields that should be replaced + columns = columns.filter( + (col: { propertyName: string }) => !replacementKeys.includes(col.propertyName), + ); + + // Add the corresponding name fields + const nameFields = replacementKeys.map((key) => ({ + propertyName: this.POSITION_FIELD_REPLACEMENTS[key].propertyName, + type: "string", + comment: this.POSITION_FIELD_REPLACEMENTS[key].comment, + key: this.POSITION_FIELD_REPLACEMENTS[key].propertyName, + })); + + columns = [...columns, ...nameFields]; + } + + // Special handling for PosMaster entity - add Profile fields for holder information + if (name === "PosMaster") { + // Add Profile fields that are accessible via current_holder relation + const profileFields = ["prefix", "rank", "firstName", "lastName", "citizenId"]; + const profileRepository = AppDataSource.getRepository(Profile); + const profileColumns = profileRepository.metadata.columns + .filter( + (column: any) => !column.isPrimary && profileFields.includes(column.propertyName), + ) + .map((column: any) => ({ + propertyName: `Profile.${column.propertyName}`, + type: typeof column.type === "string" ? column.type : "string", + comment: column.comment, + key: `Profile.${column.propertyName}`, + })); + + columns = [...columns, ...profileColumns]; + } + + return { + tb: name, + description, + isMain: isMain || false, + propertys: columns, + }; + }); return new HttpSuccess(result); } catch (error) { diff --git a/src/controllers/ApiWebServiceController.ts b/src/controllers/ApiWebServiceController.ts index 61f3d54a..49e9287d 100644 --- a/src/controllers/ApiWebServiceController.ts +++ b/src/controllers/ApiWebServiceController.ts @@ -20,6 +20,153 @@ export class ApiWebServiceController extends Controller { private apiNameRepository = AppDataSource.getRepository(ApiName); private orgRevisionRepository = AppDataSource.getRepository(OrgRevision); private apiHistoryRepository = AppDataSource.getRepository(ApiHistory); + private currentRevisionId: string = ""; + + // การแทนที่ฟิลด์ ID ด้วยฟิลด์ Name สำหรับ Profile entity + private readonly PROFILE_FIELD_REPLACEMENTS: Record< + string, + { propertyName: string; joinRelation: string; joinField: string } + > = { + posLevelName: { + propertyName: "posLevelId", + joinRelation: "posLevel", + joinField: "posLevelName", + }, + posTypeName: { + propertyName: "posTypeId", + joinRelation: "posType", + joinField: "posTypeName", + }, + registrationProvinceName: { + propertyName: "registrationProvinceId", + joinRelation: "registrationProvince", + joinField: "name", + }, + registrationDistrictName: { + propertyName: "registrationDistrictId", + joinRelation: "registrationDistrict", + joinField: "name", + }, + registrationSubDistrictName: { + propertyName: "registrationSubDistrictId", + joinRelation: "registrationSubDistrict", + joinField: "name", + }, + currentProvinceName: { + propertyName: "currentProvinceId", + joinRelation: "currentProvince", + joinField: "name", + }, + currentDistrictName: { + propertyName: "currentDistrictId", + joinRelation: "currentDistrict", + joinField: "name", + }, + currentSubDistrictName: { + propertyName: "currentSubDistrictId", + joinRelation: "currentSubDistrict", + joinField: "name", + }, + }; + + // การแทนที่ฟิลด์ ID ด้วยฟิลด์ Name สำหรับ Position entity + private readonly POSITION_FIELD_REPLACEMENTS: Record< + string, + { propertyName: string; joinRelation: string; joinField: string } + > = { + posTypeName: { + propertyName: "posTypeId", + joinRelation: "posType", + joinField: "posTypeName", + }, + posLevelName: { + propertyName: "posLevelId", + joinRelation: "posLevel", + joinField: "posLevelName", + }, + posExecutiveName: { + propertyName: "posExecutiveId", + joinRelation: "posExecutive", + joinField: "posExecutiveName", + }, + }; + + /** + * build posMaster permission condition + * @summary สร้างเงื่อนไขการกรองข้อมูลตามสิทธิ์การเข้าถึง + */ + private buildPosMasterPermissionCondition( + accessType: string | undefined, + dnaIds: { + dnaRootId?: string | null; + dnaChild1Id?: string | null; + dnaChild2Id?: string | null; + dnaChild3Id?: string | null; + dnaChild4Id?: string | null; + }, + tableAlias: string = "posMaster", + ): string { + // ALL - no filtering + if (accessType === "ALL") { + return "1=1"; + } + + // No access type specified but has DNA IDs - default to NORMAL behavior + const conditions: string[] = []; + + if (accessType === "ROOT" && dnaIds.dnaRootId) { + // All organizations under this root + conditions.push( + `${tableAlias}.orgRootId IN (SELECT id FROM orgRoot WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA LIKE "${dnaIds.dnaRootId}%")`, + ); + } else if (accessType === "CHILD" || accessType === "NORMAL") { + // Build conditions based on which DNA level is specified + if (dnaIds.dnaChild4Id) { + conditions.push( + `${tableAlias}.orgChild4Id IN (SELECT id FROM orgChild4 WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA = "${dnaIds.dnaChild4Id}")`, + ); + } else if (dnaIds.dnaChild3Id) { + conditions.push( + `${tableAlias}.orgChild3Id IN (SELECT id FROM orgChild3 WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA = "${dnaIds.dnaChild3Id}")`, + ); + // For CHILD type, include all descendants + if (accessType === "CHILD") { + conditions.push( + `(${tableAlias}.orgChild3Id IN (SELECT id FROM orgChild3 WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA LIKE "${dnaIds.dnaChild3Id}%") OR ${tableAlias}.orgChild4Id IS NOT NULL)`, + ); + } + } else if (dnaIds.dnaChild2Id) { + conditions.push( + `${tableAlias}.orgChild2Id IN (SELECT id FROM orgChild2 WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA = "${dnaIds.dnaChild2Id}")`, + ); + if (accessType === "CHILD") { + conditions.push( + `(${tableAlias}.orgChild2Id IN (SELECT id FROM orgChild2 WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA LIKE "${dnaIds.dnaChild2Id}%") OR ${tableAlias}.orgChild3Id IS NOT NULL)`, + ); + } + } else if (dnaIds.dnaChild1Id) { + conditions.push( + `${tableAlias}.orgChild1Id IN (SELECT id FROM orgChild1 WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA = "${dnaIds.dnaChild1Id}")`, + ); + if (accessType === "CHILD") { + conditions.push( + `(${tableAlias}.orgChild1Id IN (SELECT id FROM orgChild1 WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA LIKE "${dnaIds.dnaChild1Id}%") OR ${tableAlias}.orgChild2Id IS NOT NULL)`, + ); + } + } else if (dnaIds.dnaRootId) { + conditions.push( + `${tableAlias}.orgRootId IN (SELECT id FROM orgRoot WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA = "${dnaIds.dnaRootId}")`, + ); + if (accessType === "CHILD") { + conditions.push( + `(${tableAlias}.orgRootId IN (SELECT id FROM orgRoot WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA LIKE "${dnaIds.dnaRootId}%") OR ${tableAlias}.orgChild1Id IS NOT NULL)`, + ); + } + } + } + + return conditions.length > 0 ? `(${conditions.join(" OR ")})` : "1=1"; + } /** * list fields by systems @@ -50,7 +197,7 @@ export class ApiWebServiceController extends Controller { } await isPermissionRequest(request, apiName.id); const offset = (page - 1) * pageSize; - const propertyKey = apiName.apiAttributes.map((attr) => `${attr.tbName}.${attr.propertyKey}`); + let propertyKey = apiName.apiAttributes.map((attr) => `${attr.tbName}.${attr.propertyKey}`); let tbMain: string = ""; let condition: string = "1=1"; @@ -78,6 +225,104 @@ export class ApiWebServiceController extends Controller { condition = `PosMaster.orgRevisionId = "${revision?.id}"`; } + let posMasterCondition: string = ""; + let posMasterAlias: string = ""; + + // Special handling for Profile and ProfileEmployee systems with permission filtering + if (system == "registry") { + // Get current revision + const revision = await this.orgRevisionRepository.findOne({ + select: ["id"], + where: { orgRevisionIsCurrent: true, orgRevisionIsDraft: false }, + }); + + // Store for use in permission building + this.currentRevisionId = revision?.id || ""; + posMasterAlias = "posMaster"; + + // Build permission condition + posMasterCondition = this.buildPosMasterPermissionCondition( + request.user.accessType, + { + dnaRootId: request.user.dnaRootId, + dnaChild1Id: request.user.dnaChild1Id, + dnaChild2Id: request.user.dnaChild2Id, + dnaChild3Id: request.user.dnaChild3Id, + dnaChild4Id: request.user.dnaChild4Id, + }, + posMasterAlias, + ); + } else if (system == "registry_emp") { + // Get current revision + const revision = await this.orgRevisionRepository.findOne({ + select: ["id"], + where: { orgRevisionIsCurrent: true, orgRevisionIsDraft: false }, + }); + + // Store for use in permission building + this.currentRevisionId = revision?.id || ""; + posMasterAlias = "employeePosMaster"; + + // Build permission condition + posMasterCondition = this.buildPosMasterPermissionCondition( + request.user.accessType, + { + dnaRootId: request.user.dnaRootId, + dnaChild1Id: request.user.dnaChild1Id, + dnaChild2Id: request.user.dnaChild2Id, + dnaChild3Id: request.user.dnaChild3Id, + dnaChild4Id: request.user.dnaChild4Id, + }, + posMasterAlias, + ); + } else if (system == "registry_temp") { + // Get current revision + const revision = await this.orgRevisionRepository.findOne({ + select: ["id"], + where: { orgRevisionIsCurrent: true, orgRevisionIsDraft: false }, + }); + + // Store for use in permission building + this.currentRevisionId = revision?.id || ""; + posMasterAlias = "employeeTempPosMaster"; + + // Build permission condition + posMasterCondition = this.buildPosMasterPermissionCondition( + request.user.accessType, + { + dnaRootId: request.user.dnaRootId, + dnaChild1Id: request.user.dnaChild1Id, + dnaChild2Id: request.user.dnaChild2Id, + dnaChild3Id: request.user.dnaChild3Id, + dnaChild4Id: request.user.dnaChild4Id, + }, + posMasterAlias, + ); + } else if (system == "position") { + // Get current revision + const revision = await this.orgRevisionRepository.findOne({ + select: ["id"], + where: { orgRevisionIsCurrent: true, orgRevisionIsDraft: false }, + }); + + // Store for use in permission building + this.currentRevisionId = revision?.id || ""; + posMasterAlias = "PosMaster"; // Note: Uses PascalCase to match tbMain alias + + // Build permission condition + posMasterCondition = this.buildPosMasterPermissionCondition( + request.user.accessType, + { + dnaRootId: request.user.dnaRootId, + dnaChild1Id: request.user.dnaChild1Id, + dnaChild2Id: request.user.dnaChild2Id, + dnaChild3Id: request.user.dnaChild3Id, + dnaChild4Id: request.user.dnaChild4Id, + }, + posMasterAlias, + ); + } + const repo = AppDataSource.getRepository(tbMain); const metadata = repo.metadata; @@ -92,6 +337,40 @@ export class ApiWebServiceController extends Controller { ...new Set(propertyKey.map((x) => x.split(".")[0]).filter((tb) => tb !== tbMain)), ]; + // สำหรับ Profile: ตรวจสอบฟิลด์ที่ต้องการ join และแปลง propertyKey + const profileFieldJoins: Record = {}; // alias -> relationName + if (tbMain === "Profile") { + propertyKey = propertyKey.map((key) => { + const [table, field] = key.split("."); + if (table === "Profile") { + const replacement = this.PROFILE_FIELD_REPLACEMENTS[field]; + if (replacement) { + const alias = `${table}_${replacement.joinRelation}`; + profileFieldJoins[alias] = replacement.joinRelation; + return `${alias}.${replacement.joinField}`; + } + } + return key; + }); + } + + // สำหรับ Position: ตรวจสอบฟิลด์ที่ต้องการ join และแปลง propertyKey + const positionFieldJoins: Record = {}; // alias -> relationName + if (tbMain === "Position") { + propertyKey = propertyKey.map((key) => { + const [table, field] = key.split("."); + if (table === "Position") { + const replacement = this.POSITION_FIELD_REPLACEMENTS[field]; + if (replacement) { + const alias = `${table}_${replacement.joinRelation}`; + positionFieldJoins[alias] = replacement.joinRelation; + return `${alias}.${replacement.joinField}`; + } + } + return key; + }); + } + const queryBuilder = repo.createQueryBuilder(tbMain); // join กับตารารอง @@ -107,6 +386,49 @@ export class ApiWebServiceController extends Controller { }); } + // join สำหรับฟิลด์ Profile ที่ต้องการดึงค่าจากตารางอื่น + if (tbMain === "Profile" && Object.keys(profileFieldJoins).length > 0) { + Object.entries(profileFieldJoins).forEach(([alias, relationName]) => { + queryBuilder.leftJoin(`${tbMain}.${relationName}`, alias); + }); + } + + // join สำหรับฟิลด์ Position ที่ต้องการดึงค่าจากตารางอื่น + if (tbMain === "Position" && Object.keys(positionFieldJoins).length > 0) { + Object.entries(positionFieldJoins).forEach(([alias, relationName]) => { + queryBuilder.leftJoin(`${tbMain}.${relationName}`, alias); + }); + } + + // join สำหรับ PosMaster เมื่อต้องการดึงค่าจาก Profile (ข้อมูลคนครอง) + const posMasterProfileFields: string[] = []; + if (tbMain === "PosMaster") { + propertyKey.forEach((key) => { + if (key.startsWith("Profile.")) { + posMasterProfileFields.push(key); + } + }); + } + + // join PosMaster กับ Profile เมื่อมีการขอ Profile fields + if (tbMain === "PosMaster" && posMasterProfileFields.length > 0) { + queryBuilder.leftJoin("PosMaster.current_holder", "Profile"); + } + + // join กับ posMaster/employeePosMaster/employeeTempPosMaster เพื่อกรองตามสิทธิ์การเข้าถึง + if ((tbMain === "Profile" || tbMain === "ProfileEmployee") && posMasterCondition !== "1=1") { + if (tbMain === "Profile") { + queryBuilder.leftJoin("Profile.current_holders", "posMaster"); + } else if (tbMain === "ProfileEmployee") { + // Use the correct relation based on posMasterAlias + if (posMasterAlias === "employeeTempPosMaster") { + queryBuilder.leftJoin("ProfileEmployee.current_holderTemps", "employeeTempPosMaster"); + } else { + queryBuilder.leftJoin("ProfileEmployee.current_holders", "employeePosMaster"); + } + } + } + // // เพิ่ม Main.id เพราะจะใช้ pk ในการแมบและนับจำนวน // if (!propertyKey.includes(`${Main}.id`)) { // propertyKey.push(`${Main}.id`); @@ -125,6 +447,7 @@ export class ApiWebServiceController extends Controller { const [items, total] = await queryBuilder .select(propertyKey) .where(condition) + .andWhere(posMasterCondition) .orderBy(propertyKey[0], "ASC") .skip(offset) .take(pageSize) @@ -141,8 +464,55 @@ export class ApiWebServiceController extends Controller { // split object id ออกก่อน return const data = items.map((item) => { - const { [pk]: removedPk, ...x } = item; - return x; + const { [pk]: removedPk, ...rest } = item; + + // สำหรับ Profile: แปลงฟิลด์ที่มาจาก join กลับเป็นชื่อเดิม + if (tbMain === "Profile") { + const flattened: any = { ...rest }; + Object.entries(this.PROFILE_FIELD_REPLACEMENTS).forEach(([nameField, config]) => { + const alias = `${tbMain}_${config.joinRelation}`; + if (rest[alias] && rest[alias][config.joinField] !== undefined) { + flattened[nameField] = rest[alias][config.joinField]; + delete flattened[alias]; + } + }); + return flattened; + } + + // สำหรับ Position: แปลงฟิลด์ที่มาจาก join กลับเป็นชื่อเดิม + if (tbMain === "Position") { + const flattened: any = { ...rest }; + Object.entries(this.POSITION_FIELD_REPLACEMENTS).forEach(([nameField, config]) => { + // Remove the original ID field + delete flattened[config.propertyName]; + // Add the name field from joined table + const alias = `${tbMain}_${config.joinRelation}`; + if (rest[alias] && rest[alias][config.joinField] !== undefined) { + flattened[nameField] = rest[alias][config.joinField]; + } + // Remove the joined table object + delete flattened[alias]; + }); + return flattened; + } + + // สำหรับ PosMaster: แปลงฟิลด์ Profile ที่มาจาก join กลับเป็นฟิลด์ระดับบน + if (tbMain === "PosMaster" && posMasterProfileFields.length > 0) { + const flattened: any = { ...rest }; + // Extract Profile fields and add them at top level with "profile_" prefix to avoid conflicts + if (rest["Profile"]) { + flattened["profile_prefix"] = rest["Profile"].prefix; + flattened["profile_rank"] = rest["Profile"].rank; + flattened["profile_firstName"] = rest["Profile"].firstName; + flattened["profile_lastName"] = rest["Profile"].lastName; + flattened["profile_citizenId"] = rest["Profile"].citizenId; + // Remove the nested Profile object + delete flattened["Profile"]; + } + return flattened; + } + + return rest; }); // console.log("queryBuilder ===> ", queryBuilder.getQuery()); diff --git a/src/controllers/ScriptProfileOrgController.ts b/src/controllers/ScriptProfileOrgController.ts index 4881249e..0494be98 100644 --- a/src/controllers/ScriptProfileOrgController.ts +++ b/src/controllers/ScriptProfileOrgController.ts @@ -49,7 +49,7 @@ export class ScriptProfileOrgController extends Controller { * @summary Update org structure for profiles updated within a certain time window and sync to Keycloak */ @Post("update-org") - public async cronjobUpdateOrg(@Request() request: RequestWithUser) { + public async cronjobUpdateOrg(@Request() _request: RequestWithUser) { // Idempotency check - prevent concurrent runs if (this.isRunning) { console.log("cronjobUpdateOrg: Job already running, skipping this execution"); diff --git a/src/middlewares/authWebService.ts b/src/middlewares/authWebService.ts index fa50b3fe..1f17b9cf 100644 --- a/src/middlewares/authWebService.ts +++ b/src/middlewares/authWebService.ts @@ -17,7 +17,17 @@ export async function handleWebServiceAuth(request: express.Request) { // ตรวจสอบ API Key กับฐานข้อมูล const apiKeyData = await AppDataSource.getRepository(ApiKey).findOne({ - select: { id: true, name: true, keyApi: true }, + select: { + id: true, + name: true, + keyApi: true, + accessType: true, + dnaRootId: true, + dnaChild1Id: true, + dnaChild2Id: true, + dnaChild3Id: true, + dnaChild4Id: true, + }, where: { keyApi: apiKey }, relations: ["apiNames"], }); @@ -40,6 +50,12 @@ export async function handleWebServiceAuth(request: express.Request) { name: apiKeyData.name, type: "web-service", accessApi: apiKeyData.apiNames.map((x) => x.id) ?? [], + accessType: apiKeyData.accessType, + dnaRootId: apiKeyData.dnaRootId, + dnaChild1Id: apiKeyData.dnaChild1Id, + dnaChild2Id: apiKeyData.dnaChild2Id, + dnaChild3Id: apiKeyData.dnaChild3Id, + dnaChild4Id: apiKeyData.dnaChild4Id, }; } diff --git a/src/middlewares/user.ts b/src/middlewares/user.ts index 75c84d01..09e32ef9 100644 --- a/src/middlewares/user.ts +++ b/src/middlewares/user.ts @@ -25,5 +25,11 @@ export type RequestWithUserWebService = Request & { id: string; name: string; accessApi: string[]; + accessType?: string; + dnaRootId?: string | null; + dnaChild1Id?: string | null; + dnaChild2Id?: string | null; + dnaChild3Id?: string | null; + dnaChild4Id?: string | null; }; }; diff --git a/src/services/KeycloakAttributeService.ts b/src/services/KeycloakAttributeService.ts index 1e0f3f07..7bfe88ed 100644 --- a/src/services/KeycloakAttributeService.ts +++ b/src/services/KeycloakAttributeService.ts @@ -530,18 +530,20 @@ export class KeycloakAttributeService { // Initialize rate limiter if rate limiting is enabled if (rateLimit && rateLimit > 0) { rateLimiter = new RateLimiter(rateLimit); - console.log(`[syncMissingEmpTypeByMonth] Rate limiting enabled: ${rateLimit} requests/second`); + console.log( + `[syncMissingEmpTypeByMonth] Rate limiting enabled: ${rateLimit} requests/second`, + ); } // Select repository based on profile type - const repo = - profileType === "PROFILE" ? this.profileRepo : this.profileEmployeeRepo; + const repo = profileType === "PROFILE" ? this.profileRepo : this.profileEmployeeRepo; // Query profiles updated within the month const profiles = await repo .createQueryBuilder("p") .where("p.keycloak IS NOT NULL") .andWhere("p.keycloak != :empty", { empty: "" }) + .andWhere("p.isDelete = :isDelete", { isDelete: false }) .andWhere("p.lastUpdatedAt BETWEEN :start AND :end", { start: startDate, end: endDate, @@ -579,8 +581,7 @@ export class KeycloakAttributeService { try { // Check if empType is empty in Keycloak - const { isEmpty, currentEmpType } = - await this.checkEmpTypeEmpty(keycloakUserId); + const { isEmpty, currentEmpType } = await this.checkEmpTypeEmpty(keycloakUserId); result.profilesChecked++; @@ -607,8 +608,7 @@ export class KeycloakAttributeService { // Sync the profile const success = await withRetry( - async () => - this.syncOnOrganizationChange(profile.id, profileType), + async () => this.syncOnOrganizationChange(profile.id, profileType), 3, // maxRetries 1000, // baseDelay ); @@ -768,7 +768,13 @@ export class KeycloakAttributeService { maxRetries?: number; // Retry attempts for failed operations rateLimit?: number; // Requests per second clearProgress?: boolean; // Start fresh, ignore existing progress - }): Promise<{ total: number; success: number; failed: number; details: any[]; resumed?: boolean }> { + }): Promise<{ + total: number; + success: number; + failed: number; + details: any[]; + resumed?: boolean; + }> { const limit = options?.limit; const concurrency = options?.concurrency ?? 5; const resume = options?.resume ?? false; @@ -922,7 +928,10 @@ export class KeycloakAttributeService { // Save progress after each batch SyncProgressManager.save(updatedState); // Log progress every 50 items - if (updatedState.lastSyncedIndex % 50 === 0 || updatedState.lastSyncedIndex === updatedState.totalProfiles) { + if ( + updatedState.lastSyncedIndex % 50 === 0 || + updatedState.lastSyncedIndex === updatedState.totalProfiles + ) { SyncProgressManager.logProgress(updatedState); } },