30 KiB
รายงานการตรวจสอบ Unhandled Exception - Controllers ชุดที่ 3 (ไฟล์ที่ 21-30)
Project: BMA EHR Organization Backend Framework: TSOA + Express + TypeORM วันที่ตรวจสอบ: 2026-05-08 จำนวน Controllers: 10 ไฟล์ สถานะ: เสร็จสิ้น
สรุปผลการตรวจสอบ
| ระดับความรุนแรง | จำนวนจุดเสี่ยง |
|---|---|
| CRITICAL | 0 |
| HIGH | 4 |
| MEDIUM | 5 |
| LOW | 2 |
| BUG | 2 |
| รวมทั้งหมด | 13 |
Controllers ที่ตรวจสอบ
- EmployeePositionController.ts
- EmployeeTempPositionController.ts
- ExRetirementController.ts
- GenderController.ts
- ImportDataController.ts
- InsigniaController.ts
- InsigniaTypeController.ts
- IssuesController.ts
- KeycloakSyncController.ts
- LoginController.ts
รายละเอียดจุดเสี่ยงแต่ละจุด
#1 - Promise.all Without Error Handling in Position Creation (HIGH)
File & Location: EmployeePositionController.ts:690-707
Method: createEmpMaster
Problem Type: 1. Unhandled Exception / 2. Missing Error Handle
Root Cause:
- ใช้
Promise.all()สำหรับบันทึก positions หลายรายการพร้อมกัน - ถ้ามี position ไหน save ไม่สำเร็จ จะเกิด unhandled rejection
- ไม่มี try-catch รองรับ ทำให้ไม่สามารถควบคุม error ได้
- ส่งผลให้อาจเกิด data inconsistency ถ้า save บางส่วนสำเร็จ แต่บางส่วนล้มเหลว
Code ปัจจุบัน (เสี่ยง):
await Promise.all(
requestBody.positions.map(async (x: any) => {
const position = Object.assign(new EmployeePosition());
position.positionName = x.posDictName;
position.posTypeId = x.posTypeId == "" ? null : x.posTypeId;
position.posLevelId = x.posLevelId == "" ? null : x.posLevelId;
position.positionIsSelected = false;
position.posMasterId = posMaster.id;
position.createdUserId = request.user.sub;
position.createdFullName = request.user.name;
position.createdAt = new Date();
position.lastUpdateUserId = request.user.sub;
position.lastUpdateFullName = request.user.name;
position.lastUpdatedAt = new Date();
await this.employeePositionRepository.save(position, { data: request });
}),
);
return new HttpSuccess(posMaster.id);
Recommended Fix:
try {
await Promise.all(
requestBody.positions.map(async (x: any) => {
try {
const position = Object.assign(new EmployeePosition());
position.positionName = x.posDictName;
position.posTypeId = x.posTypeId == "" ? null : x.posTypeId;
position.posLevelId = x.posLevelId == "" ? null : x.posLevelId;
position.positionIsSelected = false;
position.posMasterId = posMaster.id;
position.createdUserId = request.user.sub;
position.createdFullName = request.user.name;
position.createdAt = new Date();
position.lastUpdateUserId = request.user.sub;
position.lastUpdateFullName = request.user.name;
position.lastUpdatedAt = new Date();
await this.employeePositionRepository.save(position, { data: request });
} catch (error) {
console.error(`Failed to save position "${x.posDictName}":`, error);
throw error; // Re-throw to be caught by Promise.all
}
}),
);
return new HttpSuccess(posMaster.id);
} catch (error) {
console.error("Failed to save positions:", error);
throw new HttpError(
HttpStatus.INTERNAL_SERVER_ERROR,
"Failed to save positions"
);
}
#2 - Promise.all Without Error Handling in Position Update (HIGH)
File & Location: EmployeePositionController.ts:905-921
Method: updateEmpMaster
Problem Type: 1. Unhandled Exception / 2. Missing Error Handle
Root Cause:
- Similar to #1 แต่เกิดใน update operation
Promise.all()โดยไม่มี error handling- เกิดการลบ positions เก่า ก่อน แล้วค่อยสร้างใหม่ ถ้าสร้างใหม่ fail จะเกิด data loss
Code ปัจจุบัน (เสี่ยง):
await this.employeePositionRepository.delete({ posMasterId: posMaster.id });
await Promise.all(
requestBody.positions.map(async (x: any) => {
const position = Object.assign(new EmployeePosition());
position.positionName = x.posDictName;
position.posTypeId = x.posTypeId == "" ? null : x.posTypeId;
position.posLevelId = x.posLevelId == "" ? null : x.posLevelId;
position.positionIsSelected = false;
position.posMasterId = posMaster.id;
position.createdUserId = request.user.sub;
position.createdFullName = request.user.name;
position.createdAt = new Date();
position.lastUpdateUserId = request.user.sub;
position.lastUpdateFullName = request.user.name;
position.lastUpdatedAt = new Date();
await this.employeePositionRepository.save(position, { data: request });
}),
);
Recommended Fix:
// Get existing positions as backup before deletion
const existingPositions = await this.employeePositionRepository.find({
where: { posMasterId: posMaster.id }
});
try {
await this.employeePositionRepository.delete({ posMasterId: posMaster.id });
await Promise.all(
requestBody.positions.map(async (x: any) => {
try {
const position = Object.assign(new EmployeePosition());
position.positionName = x.posDictName;
position.posTypeId = x.posTypeId == "" ? null : x.posTypeId;
position.posLevelId = x.posLevelId == "" ? null : x.posLevelId;
position.positionIsSelected = false;
position.posMasterId = posMaster.id;
position.createdUserId = request.user.sub;
position.createdFullName = request.user.name;
position.createdAt = new Date();
position.lastUpdateUserId = request.user.sub;
position.lastUpdateFullName = request.user.name;
position.lastUpdatedAt = new Date();
await this.employeePositionRepository.save(position, { data: request });
} catch (error) {
console.error(`Failed to update position "${x.posDictName}":`, error);
throw error;
}
}),
);
} catch (error) {
console.error("Failed to update positions, restoring backup:", error);
// Restore backup positions
await this.employeePositionRepository.save(existingPositions);
throw new HttpError(
HttpStatus.INTERNAL_SERVER_ERROR,
"Failed to update positions"
);
}
#3 - Async forEach Without Proper Error Handling (HIGH)
File & Location: EmployeePositionController.ts:2378-2395
Method: createEmpHolder
Problem Type: 1. Unhandled Exception
Root Cause:
- ใช้
forEachกับ async function ซึ่งไม่รอให้ทุก operation สำเร็จ - การใช้
forEachกับ async จะไม่ catch error ที่เกิดใน loop - ถ้ามีการ save ที่ fail จะไม่ทราบ และ data อาจไม่ถูกต้อง
Code ปัจจุบัน (เสี่ยง):
dataMaster.positions.forEach(async (position) => {
if (position.id === requestBody.position) {
position.positionIsSelected = true;
const profile = await this.profileRepository.findOne({
where: { id: requestBody.profileId },
});
if (profile != null) {
const _null: any = null;
profile.posLevelId = position?.posLevelId ?? _null;
profile.posTypeId = position?.posTypeId ?? _null;
profile.position = position?.positionName ?? _null;
await this.profileRepository.save(profile);
}
} else {
position.positionIsSelected = false;
}
await this.employeePositionRepository.save(position);
});
Recommended Fix:
// Use Promise.all instead of forEach with async
await Promise.all(
dataMaster.positions.map(async (position) => {
try {
if (position.id === requestBody.position) {
position.positionIsSelected = true;
const profile = await this.profileRepository.findOne({
where: { id: requestBody.profileId },
});
if (profile != null) {
const _null: any = null;
profile.posLevelId = position?.posLevelId ?? _null;
profile.posTypeId = position?.posTypeId ?? _null;
profile.position = position?.positionName ?? _null;
await this.profileRepository.save(profile);
}
} else {
position.positionIsSelected = false;
}
await this.employeePositionRepository.save(position);
} catch (error) {
console.error(`Failed to update position ${position.id}:`, error);
throw error;
}
})
);
#4 - Promise.all in EmployeeTempPositionController Without Error Handling (HIGH)
File & Location: EmployeeTempPositionController.ts:557-574
Method: createEmpMaster
Problem Type: 1. Unhandled Exception / 2. Missing Error Handle
Root Cause:
- เหมือนกับ #1 แต่เกิดใน EmployeeTempPositionController
- ใช้
Promise.all()โดยไม่มี error handling
Code ปัจจุบัน (เสี่ยง):
await Promise.all(
requestBody.positions.map(async (x: any) => {
const position = Object.assign(new EmployeePosition());
position.positionName = x.posDictName;
position.posTypeId = x.posTypeId == "" ? null : x.posTypeId;
position.posLevelId = x.posLevelId == "" ? null : x.posLevelId;
position.positionIsSelected = false;
position.posMasterTempId = posMaster.id;
position.createdUserId = request.user.sub;
position.createdFullName = request.user.name;
position.createdAt = new Date();
position.lastUpdateUserId = request.user.sub;
position.lastUpdateFullName = request.user.name;
position.lastUpdatedAt = new Date();
await this.employeePositionRepository.save(position, { data: request });
}),
);
Recommended Fix: ใช้การแก้ไขเดียวกันกับ #1
#5 - Unsafe Token Fetch in ExRetirementController (MEDIUM)
File & Location: ExRetirementController.ts:148-173
Method: getToken
Problem Type: 2. Missing Error Handle
Root Cause:
- ฟังก์ชัน
getTokenมีการ throw error แต่ใช้Promise.reject - ไม่มีการระบุประเภทของ error ที่ชัดเจน
- Error ที่เกิดขึ้นอาจไม่ถูก handle อย่างเหมาะสมในบางกรณี
- Token cache อาจเก็บ token ที่หมดอายุ
Code ปัจจุบัน (เสี่ยง):
async function getToken(ClientID: string, ClientSecret: string): Promise<string> {
const cacheKey = `${ClientID}:${ClientSecret}`;
// ลองหา token ใน cache ก่อน
const cachedToken = TokenCache.get(cacheKey);
if (cachedToken) {
return cachedToken;
}
// ถ้าไม่มีใน cache ให้ขอใหม่
try {
const formData = new FormData();
formData.append("ClientID", ClientID);
formData.append("ClientSecret", ClientSecret);
const res = await axios.post(API_URL_BANGKOK + "/authorize", formData, {
headers: {
"Content-Type": "application/json",
},
});
const token = res.data.token;
TokenCache.set(cacheKey, token);
return token;
} catch (error) {
return Promise.reject({ message: "Error occurred", error });
}
}
Recommended Fix:
async function getToken(ClientID: string, ClientSecret: string): Promise<string> {
const cacheKey = `${ClientID}:${ClientSecret}`;
// ลองหา token ใน cache ก่อน
const cachedToken = TokenCache.get(cacheKey);
if (cachedToken) {
return cachedToken;
}
// ถ้าไม่มีใน cache ให้ขอใหม่
try {
const formData = new FormData();
formData.append("ClientID", ClientID);
formData.append("ClientSecret", ClientSecret);
const res = await axios.post(API_URL_BANGKOK + "/authorize", formData, {
headers: {
"Content-Type": "application/json",
},
timeout: 10000, // Add timeout
});
if (!res.data || !res.data.token) {
throw new Error("Invalid token response from exprofile API");
}
const token = res.data.token;
TokenCache.set(cacheKey, token);
return token;
} catch (error: any) {
console.error("Failed to get exprofile token:", error);
// More specific error handling
if (error.response?.status === 401) {
throw new Error("Invalid credentials for exprofile API");
} else if (error.code === 'ECONNABORTED') {
throw new Error("Request timeout while fetching exprofile token");
} else {
throw new Error(`Failed to fetch exprofile token: ${error.message}`);
}
}
}
#6 - Promise.all Without Error Handling in ImportDataController (MEDIUM)
File & Location: ImportDataController.ts:2425-2443 Method: Import Education Mis Data
Problem Type: 1. Unhandled Exception / 2. Missing Error Handle
Root Cause:
- ใช้
Promise.all()สำหรับ batch insert ข้อมูลลง database - ไม่มี error handling ถ้า insert บางรายการ fail
- ไม่สามารถรู้ได้ว่ามีกี่รายการที่สำเร็จ/ล้มเหลว
Code ปัจจุบัน (เสี่ยง):
await Promise.all(
getExcel.map(async (item: any) => {
const educationMis = new EducationMis();
educationMis.EDUCATION_CODE = item.EDUCATION_CODE;
educationMis.EDUCATION_NAME = item.EDUCATION_NAME;
// ... set other properties
await this.educationMisRepository.save(educationMis);
}),
);
Recommended Fix:
const results = await Promise.allSettled(
getExcel.map(async (item: any) => {
try {
const educationMis = new EducationMis();
educationMis.EDUCATION_CODE = item.EDUCATION_CODE;
educationMis.EDUCATION_NAME = item.EDUCATION_NAME;
// ... set other properties
await this.educationMisRepository.save(educationMis);
return { status: 'success', code: item.EDUCATION_CODE };
} catch (error) {
console.error(`Failed to save education ${item.EDUCATION_CODE}:`, error);
return {
status: 'failed',
code: item.EDUCATION_CODE,
error: error.message
};
}
}),
);
const failed = results.filter(r => r.status === 'rejected' || (r.status === 'fulfilled' && r.value.status === 'failed'));
if (failed.length > 0) {
console.warn(`Failed to import ${failed.length} education records`);
// Optionally notify user about partial failure
}
#7 - External API Call Without Proper Error Handling (MEDIUM)
File & Location: ExRetirementController.ts:50-103
Method: getExRetirement
Problem Type: 2. Missing Error Handle
Root Cause:
- มีการเรียก external API แต่ error handling ยังไม่ครอบคลุม
- ใช้ retry mechanism แต่ไม่มี exponential backoff
- ไม่มี logging ที่ชัดเจนสำหรับการ debug
Code ปัจจุบัน (เสี่ยง):
let retryCount = 0;
const maxRetries = 2;
while (retryCount < maxRetries) {
try {
const token = await getToken(clientId, clientSecret);
if (!token) {
throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่สามารถขอ Token ได้");
}
const scope = "getOfficerRetireData";
const startRecord = requestBody.page !== 1 ? (requestBody.page - 1) * 25 : 0;
const formData = new FormData();
formData.append("scope", scope);
formData.append("startRecord", startRecord.toString());
formData.append("retireYear", requestBody.retireYear);
formData.append("citizenID", requestBody.citizenID);
formData.append("firstNameTH", requestBody.firstNameTH);
formData.append("lastNameTH", requestBody.lastNameTH);
formData.append("officerTypeID", requestBody.type === "officer" ? "1" : "2");
const res = await axios.post(API_URL_BANGKOK + "/getData", formData, {
headers: {
Authorization: `Bearer ${token}`,
},
});
return new HttpSuccess(res.data.data);
} catch (error: any) {
if (error.response?.status === 500 && retryCount < maxRetries - 1) {
TokenCache.delete(`${clientId}:${clientSecret}`);
retryCount++;
continue;
}
throw new HttpError(HttpStatusCode.INTERNAL_SERVER_ERROR, "ไม่สามารถติดต่อ API ได้");
}
}
Recommended Fix:
let retryCount = 0;
const maxRetries = 2;
const baseDelay = 1000; // 1 second
while (retryCount < maxRetries) {
try {
const token = await getToken(clientId, clientSecret);
if (!token) {
throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่สามารถขอ Token ได้");
}
const scope = "getOfficerRetireData";
const startRecord = requestBody.page !== 1 ? (requestBody.page - 1) * 25 : 0;
const formData = new FormData();
formData.append("scope", scope);
formData.append("startRecord", startRecord.toString());
formData.append("retireYear", requestBody.retireYear);
formData.append("citizenID", requestBody.citizenID);
formData.append("firstNameTH", requestBody.firstNameTH);
formData.append("lastNameTH", requestBody.lastNameTH);
formData.append("officerTypeID", requestBody.type === "officer" ? "1" : "2");
const res = await axios.post(API_URL_BANGKOK + "/getData", formData, {
headers: {
Authorization: `Bearer ${token}`,
},
timeout: 30000, // 30 second timeout
});
return new HttpSuccess(res.data.data);
} catch (error: any) {
retryCount++;
// Log error for debugging
console.error(`Error fetching retirement data (attempt ${retryCount}/${maxRetries}):`, {
message: error.message,
status: error.response?.status,
code: error.code
});
// Check if we should retry
const shouldRetry =
(error.response?.status === 500 ||
error.response?.status === 503 ||
error.code === 'ECONNRESET' ||
error.code === 'ETIMEDOUT') &&
retryCount < maxRetries;
if (shouldRetry) {
TokenCache.delete(`${clientId}:${clientSecret}`);
// Exponential backoff
const delay = baseDelay * Math.pow(2, retryCount - 1);
await new Promise(resolve => setTimeout(resolve, delay));
continue;
}
// Don't retry on client errors (4xx) or after max retries
throw new HttpError(
HttpStatusCode.INTERNAL_SERVER_ERROR,
`ไม่สามารถติดต่อ API ได้: ${error.message}`
);
}
}
#8 - Missing Error Handling in IssuesController (MEDIUM)
File & Location: IssuesController.ts:54-71
Method: updateIssue
Problem Type: 2. Missing Error Handle
Root Cause:
- ไม่มี try-catch รองรับ operation ที่อาจ fail
- ไม่มีการตรวจสอบว่า request body ถูกต้องหรือไม่
- การใช้
Object.assignโดยไม่ validate อาจทำให้เกิด invalid data
Code ปัจจุบัน (เสี่ยง):
@Put("{id}")
async updateIssue(
@Path("id") id: string,
@Body() requestBody: Partial<UpdateIssueRequest>,
@Request() request: RequestWithUser,
) {
let issue = await this.issuesRepository.findOneBy({ id });
if (!issue) {
this.setStatus(HttpStatusCode.NOT_FOUND);
return { message: "ไม่พบข้อมูลที่ต้องการแก้ไข" };
}
Object.assign(issue, requestBody);
issue.lastUpdateUserId = request.user.sub;
issue.lastUpdateFullName = request.user.name;
issue.lastUpdatedAt = new Date();
await this.issuesRepository.save(issue);
return new HttpSuccess(issue);
}
Recommended Fix:
@Put("{id}")
async updateIssue(
@Path("id") id: string,
@Body() requestBody: Partial<UpdateIssueRequest>,
@Request() request: RequestWithUser,
) {
try {
let issue = await this.issuesRepository.findOneBy({ id });
if (!issue) {
this.setStatus(HttpStatusCode.NOT_FOUND);
return new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลที่ต้องการแก้ไข");
}
// Validate request body if needed
if (requestBody.status !== undefined) {
// Validate status enum values
}
Object.assign(issue, requestBody);
issue.lastUpdateUserId = request.user.sub;
issue.lastUpdateFullName = request.user.name;
issue.lastUpdatedAt = new Date();
try {
await this.issuesRepository.save(issue);
} catch (saveError: any) {
console.error("Failed to save issue:", saveError);
throw new HttpError(
HttpStatusCode.INTERNAL_SERVER_ERROR,
"ไม่สามารถบันทึกข้อมูลได้"
);
}
return new HttpSuccess(issue);
} catch (error) {
if (error instanceof HttpError) {
throw error;
}
console.error("Error updating issue:", error);
throw new HttpError(
HttpStatusCode.INTERNAL_SERVER_ERROR,
"เกิดข้อผิดพลาดในการอัปเดตข้อมูล"
);
}
}
#9 - Promise.all Without Error Handling in Province Import (MEDIUM)
File & Location: ImportDataController.ts:2856-2874 Method: Import Province Data
Problem Type: 1. Unhandled Exception / 2. Missing Error Handle
Root Cause:
- Similar to #6 แต่เกิดใน province import
- ใช้
Promise.all()โดยไม่มี error handling
Recommended Fix: ใช้การแก้ไขเดียวกันกับ #6 และ #11
#10 - Wrong Error Status Code (BUG)
File & Location: InsigniaController.ts:62-64, InsigniaTypeController.ts:58-60
Methods: CreateInsignia, CreateInsigniaType
Problem Type: 3. Logic Bug
Root Cause:
- Throw
HttpError(HttpStatusCode.NOT_FOUND, ...)สำหรับ duplicate data errors - ควรใช้
CONFLICT(409) หรือBAD_REQUEST(400) แทน
Code ปัจจุบัน (ผิด):
if (rowRepeated) {
throw new HttpError(HttpStatusCode.NOT_FOUND, "ข้อมูล Row นี้มีอยู่ในระบบแล้ว");
}
Recommended Fix:
if (rowRepeated) {
throw new HttpError(HttpStatusCode.CONFLICT, "ข้อมูล Row นี้มีอยู่ในระบบแล้ว");
}
#11 - Promise.all Without Error Handling in Amphur Import (LOW)
File & Location: ImportDataController.ts:2889-2908 Method: Import Amphur Data
Problem Type: 1. Unhandled Exception / 2. Missing Error Handle
Root Cause:
- Similar to #6 แต่เกิดใน amphur import
- ใช้
Promise.all()โดยไม่มี error handling
Recommended Fix: ใช้การแก้ไขเดียวกันกับ #6
#12 - Redundant Promise.all in LoginController (LOW)
File & Location: LoginController.ts:38-47
Method: login
Problem Type: 3. Code Quality
Root Cause:
- ใช้
Promise.allกับ array ที่มีแค่ 1 promise - การใช้งานไม่มีประโยชน์เพราะไม่ได้ parallelize อะไรเลย
- Code อ่านยากและสับสน
Code ปัจจุบัน (ผิด):
let _data: any = null;
await Promise.all([
await new CallAPI()
.PostDataKeycloak(`/realms/${process.env.KC_REALMS}/protocol/openid-connect/token`, data)
.then(async (x) => {
_data = x;
})
.catch(async (x) => {
throw new HttpError(HttpStatus.UNAUTHORIZED, "ชื่อผู้ใช้งานหรือรหัสผ่านไม่ถูกต้อง");
}),
]);
Recommended Fix:
try {
const _data = await new CallAPI().PostDataKeycloak(
`/realms/${process.env.KC_REALMS}/protocol/openid-connect/token`,
data
);
if (!_data) {
throw new HttpError(HttpStatus.UNAUTHORIZED, "ชื่อผู้ใช้งานหรือรหัสผ่านไม่ถูกต้อง");
}
return new HttpSuccess(_data);
} catch (error: any) {
if (error instanceof HttpError) {
throw error;
}
throw new HttpError(HttpStatus.UNAUTHORIZED, "ชื่อผู้ใช้งานหรือรหัสผ่านไม่ถูกต้อง");
}
#13 - Error Message from External API Not Handled (BUG)
File & Location: LoginController.ts:44-46, LoginController.ts:85-87
Methods: login, loginCheckin
Problem Type: 3. Logic Bug
Root Cause:
- Catch error แล้ว throw HttpError ใหม่ แต่ไม่ได้ preserve error message ต้นทาง
- ทำให้ user ไม่รู้สาเหตุที่แท้จริงของการ login ล้มเหลว
Code ปัจจุบัน (ผิด):
.catch(async (x) => {
throw new HttpError(HttpStatus.UNAUTHORIZED, "ชื่อผู้ใช้งานหรือรหัสผ่านไม่ถูกต้อง");
}),
Recommended Fix:
.catch(async (error: any) => {
const errorMessage = error?.response?.data?.error_description ||
error?.response?.data?.error ||
error?.message ||
"ชื่อผู้ใช้งานหรือรหัสผ่านไม่ถูกต้อง";
console.error("Login failed:", error);
throw new HttpError(HttpStatus.UNAUTHORIZED, errorMessage);
}),
สรุปคำแนะนำการแก้ไขแบบรวม
ระดับความสำคัญ
ต้องแก้โดยเร็ว (P1 - High):
- Promise.all operations without error handling - unhandled rejections อาจทำให้ service ไม่เสถียร
- Async forEach ที่ไม่รอ completion - อาจทำให้ data ไม่ถูกต้อง
ควรแก้ (P2 - Medium): 3. External API call error handling - ควรมี retry mechanism ที่ดีขึ้น 4. Missing error handling in IssuesController 5. Promise operations in import controllers
แก้เมื่อว่าง (P3 - Low): 6. Redundant Promise.all in LoginController 7. Error status code issues
แนวทางการแก้ไขแบบ Global
- สร้าง utility function สำหรับ Promise.all ที่มี error handling:
async function safePromiseAll<T>(
items: T[],
executor: (item: T, index: number) => Promise<any>,
options: {
continueOnError?: boolean;
throwOnError?: boolean;
} = {}
) {
const { continueOnError = false, throwOnError = true } = options;
if (continueOnError) {
const results = await Promise.allSettled(
items.map((item, index) => executor(item, index))
);
const failures = results.filter(r => r.status === 'rejected');
if (failures.length > 0 && throwOnError) {
console.warn(`${failures.length} operations failed`);
}
return results;
} else {
return Promise.all(
items.map((item, index) => executor(item, index))
);
}
}
-
ใช้ try-catch รอบทุก database operation และ external API call
-
Implement logging ที่สมบูรณ์สำหรับ debugging
-
Use proper HTTP status codes ตามมาตรฐาน REST API
ไฟล์ที่ต้องแก้ไข
- src/controllers/EmployeePositionController.ts - Promise.all handling, forEach with async
- src/controllers/EmployeeTempPositionController.ts - Promise.all handling
- src/controllers/ExRetirementController.ts - External API error handling, token management
- src/controllers/ImportDataController.ts - Promise.all in import operations
- src/controllers/IssuesController.ts - Error handling
- src/controllers/LoginController.ts - Redundant Promise.all, error messages
- src/controllers/InsigniaController.ts - Error status codes
- src/controllers/InsigniaTypeController.ts - Error status codes
ข้อมูลเพิ่มเติม
- Controllers ที่ยังไม่ได้ตรวจสอบ: 110 ไฟล์
- จุดเสี่ยงที่พบซ้ำจากชุดที่ 1-2: Promise.all without error handling, wrong HTTP status codes
รายงานนี้ถูกสร้างโดย AI Code Review System สำหรับ BMA EHR Organization Project