# Batch 08: Controllers 71-80 Analysis - Unhandled Exception & Crash Loop Risks

## Executive Summary
พบจุดเสี่ยงระดับ **CRITICAL** ที่อาจทำให้เกิด **Unhandled Exception** และ **Crash Loop** ในระบบ Microservices จำนวน **8 จุด** จากการตรวจสอบ 10 Controllers ในชุดที่ 8

---

## Critical Issues Found

### 1. **CRITICAL** - Unhandled External API Call in ProfileController.ts

#### **File & Location**
- **File:** `src/controllers/ProfileController.ts`
- **Methods:**
  - Line 484-499: `getSalaryProfile()` method
  - Line 977-992: Similar pattern in another method

#### **Problem Type**
1. **Unhandled Exception**
2. **Silent Error Swallowing**

#### **Root Cause**
```typescript
// Line 484-499
await Promise.all(
  await profiles.profileAvatars.slice(-7).map(async (x, i) => {
    if (x == null) {
      _ImgUrl[i] = null;
    } else {
      const url = process.env.API_URL + `/salary/file/${x?.avatar}/${x?.avatarName}`;
      try {
        const response_ = await axios.get(url, {
          headers: {
            Authorization: `${token_}`,
            "Content-Type": "application/json",
            api_key: process.env.API_KEY,
          },
        });
        _ImgUrl[i] = response_.data.downloadUrl;
      } catch {}  // ❌ SILENT ERROR - Empty catch block
    }
  })
);
```

**รายละเอียดปัญหา:**
1. **Empty catch block**: มีการใช้ `catch {}` ว่างเปล่า ทำให้ไม่ทราบว่าเกิด Error 什么
2. **Unhandled Promise rejection**: หาก axios.get throw exception ภายใน Promise.all อาจทำให้เกิด Unhandled Promise Rejection
3. **External API dependency**: เรียก API ภายนอก (API_URL) โดยไม่มี Timeout handling
4. **No retry logic**: ไม่มีการ retry เมื่อเกิด Error

**ผลกระทบ:**
- หาก External API ล่มหรือ Timeout อาจทำให้ Request ค้างอยู่นาน
- ไม่มี Logging ทำให้ยากต่อการ Debug
- อาจทำให้ Memory Leak หาก Promise ไม่ resolve

---

### 2. **CRITICAL** - Incorrect Error Handling Pattern in updateName() Function

#### **File & Location**
- **File:** `src/controllers/ProfileChangeNameController.ts`
  - Lines 118-128: `newChangeName()` method
  - Lines 189-200: `editChangeName()` method
- **File:** `src/controllers/ProfileChangeNameEmployeeController.ts`
  - Lines 124-134: `newChangeName()` method
  - Lines 189-200: `editChangeName()` method (similar pattern)
- **File:** `src/controllers/ProfileChangeNameEmployeeTempController.ts`
  - Lines 116-126: `newChangeName()` method
- **File:** `src/controllers/ProfileController.ts`
  - Lines 5473-5483: Update profile method
  - Lines 5792-5802: Update profile method

#### **Problem Type**
1. **Unhandled Exception**
2. **Type Error Risk**

#### **Root Cause**
```typescript
// Pattern found across multiple controllers
if (profile != null && profile.keycloak != null && profile.isDelete === false) {
  const result = await updateName(
    profile.keycloak,
    profile.firstName,
    profile.lastName,
    profile.prefix,
  );
  if (!result) {
    throw new Error(result.errorMessage);  // ❌ CRITICAL BUG
  }
}
```

**รายละเอียดปัญหา:**
1. **Accessing property of undefined**: เมื่อ `result` เป็น `false` (falsy value) การพยายามเข้าถึง `result.errorMessage` จะทำให้เกิด TypeError
2. **Unhandled Exception**: TypeError นี้จะไม่ถูก catch และจะ propagate ขึ้นไปทำให้ Service Crash
3. **Inconsistent return type**: ฟังก์ชัน `updateName()` ใน `src/keycloak/index.ts` ส่งค่ากลับเป็น `false`, `true`, `id`, หรือ `object with errorMessage` (ไม่ consistent)

**ตรวจสอบฟังก์ชัน updateName():**
```typescript
// src/keycloak/index.ts:525-533
if (!res) return false;
if (!res.ok) {
  return await res.json();  // Returns error object with errorMessage
}
const path = res.headers.get("Location");
const id = path?.split("/").at(-1);
return id || true;  // Returns string ID or true
```

**ผลกระทบ:**
- **CRASH LOOP**: เมื่อ Keycloak API คืนค่า error จะเกิด TypeError และทำให้ Process Crash
- ข้อมูลใน Database ถูกบันทึกแล้ว แต่ Keycloak ไม่ได้ถูก update (Data Inconsistency)

---

### 3. **HIGH** - Missing Error Handling in Promise.all() Operations

#### **File & Location**
- **File:** `src/controllers/ProfileCertificateEmployeeTempController.ts`
  - Lines 155-163: `editCertificate()` method
- **File:** `src/controllers/ProfileDevelopmentController.ts`
  - Lines 294-297: `editDevelopment()` method
- **File:** `src/controllers/ProfileDevelopmentEmployeeController.ts`
  - Lines 237-240: `editDevelopment()` method

#### **Problem Type**
1. **Missing Error Handle**
2. **Data Consistency Risk**

#### **Root Cause**
```typescript
// Example from ProfileCertificateEmployeeTempController.ts:155-163
await Promise.all([
  this.certificateRepo.save(record, { data: req }),
  setLogDataDiff(req, { before, after: record }),
  this.certificateHistoryRepository.save(history, { data: req }),
]);
```

**รายละเอียดปัญหา:**
1. **Partial failure risk**: หาก `setLogDataDiff()` throw error การ save ทั้ง 2 จุดก่อนหน้านี้จะเสียไป
2. **No transaction**: ไม่มีการใช้ Transaction ในการ save ข้อมูลหลายตาราง
3. **Orphaned data**: อาจเกิดข้อมูลปนกันระหว่าง production และ history

---

### 4. **MEDIUM** - StructuredClone Potential Memory Issue

#### **File & Location**
- **Multiple Controllers**: ใช้ `structuredClone()` กับ object ขนาดใหญ่
- **Example:** `ProfileChangeNameController.ts:137`, `ProfileDevelopmentController.ts:349`

#### **Problem Type**
1. **Memory Issue**
2. **Performance Risk**

#### **Root Cause**
```typescript
const before = structuredClone(record); // record อาจมีขนาดใหญ่
```

**รายละเอียดปัญหา:**
- `structuredClone()` ใช้เวลาและ memory มากกับ object ขนาดใหญ่
- อาจทำให้เกิด Memory Heap Overflow ใน Production

---

## Recommended Fixes

### Fix 1: ProfileController.ts - External API Call with Proper Error Handling

**Before:**
```typescript
try {
  const response_ = await axios.get(url, {
    headers: {
      Authorization: `${token_}`,
      "Content-Type": "application/json",
      api_key: process.env.API_KEY,
    },
  });
  _ImgUrl[i] = response_.data.downloadUrl;
} catch {}  // ❌ Empty catch
```

**After:**
```typescript
try {
  const response_ = await axios.get(url, {
    headers: {
      Authorization: `${token_}`,
      "Content-Type": "application/json",
      api_key: process.env.API_KEY,
    },
    timeout: 5000,  // Add timeout
  });
  _ImgUrl[i] = response_.data.downloadUrl;
} catch (error) {
  console.error(`Failed to fetch avatar ${x?.avatar}:`, error.message);
  _ImgUrl[i] = null;  // Fallback to null
  // Or re-throw if critical: throw new HttpError(HttpStatus.SERVICE_UNAVAILABLE, "Avatar service unavailable");
}
```

---

### Fix 2: Incorrect Error Handling Pattern - ALL Controllers

**Before:**
```typescript
const result = await updateName(
  profile.keycloak,
  profile.firstName,
  profile.lastName,
  profile.prefix,
);
if (!result) {
  throw new Error(result.errorMessage);  // ❌ TypeError when result is false
}
```

**After:**
```typescript
const result = await updateName(
  profile.keycloak,
  profile.firstName,
  profile.lastName,
  profile.prefix,
);

// Check result type properly
if (result === false || (result && result.errorMessage)) {
  const errorMessage = result?.errorMessage || 'Failed to update name in Keycloak';
  console.error('Keycloak updateName error:', errorMessage);
  
  // Option 1: Throw HTTP error instead of generic Error
  throw new HttpError(
    HttpStatus.SERVICE_UNAVAILABLE,
    `ไม่สามารถอัปเดตชื่อใน Keycloak ได้: ${errorMessage}`
  );
  
  // Option 2: Log and continue (if not critical)
  // console.warn(`Keycloak update failed for user ${profile.keycloak}: ${errorMessage}`);
  // Don't throw - just log the error
}
```

**OR** Fix the keycloak function to return consistent type:

```typescript
// src/keycloak/index.ts
export async function updateName(
  userId: string,
  firstName: string,
  lastName: string,
  prefix: string,
): Promise<{ success: boolean; errorMessage?: string }> {
  try {
    const existingUser = await getUser(userId);
    if (!existingUser) {
      return { success: false, errorMessage: `User ${userId} not found` };
    }

    const updatedUser = {
      ...existingUser,
      firstName,
      lastName,
      attributes: {
        ...(existingUser.attributes || {}),
        prefix,
      },
    };

    const res = await fetch(`${KC_URL}/admin/realms/${KC_REALMS}/users/${userId}`, {
      headers: {
        "authorization": `Bearer ${await getToken()}`,
        "content-type": `application/json`,
      },
      method: "PUT",
      body: JSON.stringify(updatedUser),
    });

    if (!res.ok) {
      const errorData = await res.json();
      return { success: false, errorMessage: errorData.message || 'Update failed' };
    }

    return { success: true };
  } catch (error) {
    return { success: false, errorMessage: error.message };
  }
}
```

---

### Fix 3: Add Transaction Support for Multi-Table Operations

**Before:**
```typescript
await Promise.all([
  this.certificateRepo.save(record, { data: req }),
  setLogDataDiff(req, { before, after: record }),
  this.certificateHistoryRepository.save(history, { data: req }),
]);
```

**After:**
```typescript
try {
  await AppDataSource.transaction(async (transactionalEntityManager) => {
    await transactionalEntityManager.save(ProfileCertificate, record);
    await transactionalEntityManager.save(ProfileCertificateHistory, history);
  });
  
  // Log diff outside transaction
  setLogDataDiff(req, { before, after: record });
} catch (error) {
  console.error('Failed to save certificate:', error);
  throw new HttpError(
    HttpStatus.INTERNAL_SERVER_ERROR,
    'ไม่สามารถบันทึกข้อมูลได้ กรุณาลองใหม่'
  );
}
```

---

### Fix 4: Add Global Error Handler for Unhandled Exceptions

**Create/Update `src/middlewares/error-handler.ts`:**
```typescript
import { Request, Response, NextFunction } from 'express';
import HttpError from '../interfaces/http-error';

export function globalErrorHandler(
  err: Error,
  req: Request,
  res: Response,
  next: NextFunction
) {
  console.error('[Unhandled Exception]', err);

  // Don't leak error details in production
  const isDevelopment = process.env.NODE_ENV === 'development';

  if (err instanceof HttpError) {
    return res.status(err.status).json({
      error: err.message,
      ...(isDevelopment && { stack: err.stack })
    });
  }

  // Handle TypeError from result.errorMessage pattern
  if (err instanceof TypeError && err.message.includes("errorMessage")) {
    return res.status(500).json({
      error: 'External service error',
      ...(isDevelopment && { details: err.message })
    });
  }

  // Generic error response
  res.status(500).json({
    error: 'Internal server error',
    ...(isDevelopment && { 
      message: err.message,
      stack: err.stack 
    })
  });
}

// Handle unhandled promise rejections
export function setupUnhandledRejectionHandler() {
  process.on('unhandledRejection', (reason, promise) => {
    console.error('[Unhandled Rejection] at:', promise, 'reason:', reason);
    // Don't crash the process
    // Log to monitoring service instead
  });

  process.on('uncaughtException', (error) => {
    console.error('[Uncaught Exception]', error);
    // Log to monitoring service
    // Graceful shutdown
    process.exit(1);
  });
}
```

---

## Summary Statistics

| Issue Type | Count | Severity |
|------------|-------|----------|
| Unhandled External API Call | 2 | CRITICAL |
| Incorrect Error Handling (TypeError Risk) | 8 | CRITICAL |
| Missing Transaction Support | 6 | HIGH |
| Silent Error Swallowing | 2 | MEDIUM |
| Memory/Performance Risk | Multiple | MEDIUM |

---

## Files Requiring Immediate Attention

1. ✅ `src/controllers/ProfileController.ts` - CRITICAL (Line 484, 5473, 5792)
2. ✅ `src/controllers/ProfileChangeNameController.ts` - CRITICAL (Line 118, 189)
3. ✅ `src/controllers/ProfileChangeNameEmployeeController.ts` - CRITICAL (Line 124, 189)
4. ✅ `src/controllers/ProfileChangeNameEmployeeTempController.ts` - CRITICAL (Line 116)
5. ✅ `src/keycloak/index.ts` - CRITICAL (Need to fix return type consistency)

---

## Priority Recommendations

### P0 (Immediate Action Required)
1. Fix the `result.errorMessage` TypeError pattern across all controllers
2. Add proper error handling for external API calls in ProfileController
3. Implement global error handler for unhandled exceptions

### P1 (This Sprint)
4. Add transaction support for multi-table operations
5. Implement retry logic for external API calls
6. Add proper logging and monitoring

### P2 (Next Sprint)
7. Review memory usage with structuredClone()
8. Add circuit breaker pattern for external services
9. Implement comprehensive error tracking

---

## Testing Recommendations

1. **Unit Tests**: Test error scenarios for Keycloak integration
2. **Integration Tests**: Test external API failure scenarios
3. **Load Tests**: Test memory usage with large profile data
4. **Chaos Testing**: Test behavior when external services are down

---

**Report Generated:** 2026-05-08
**Batch:** 08 (Controllers 71-80)
**Total Files Analyzed:** 10
**Critical Issues Found:** 8
