diff options
Diffstat (limited to 'lib/approval')
| -rw-r--r-- | lib/approval/approval-workflow.ts | 92 | ||||
| -rw-r--r-- | lib/approval/cache-utils.ts | 82 | ||||
| -rw-r--r-- | lib/approval/index.ts | 7 |
3 files changed, 174 insertions, 7 deletions
diff --git a/lib/approval/approval-workflow.ts b/lib/approval/approval-workflow.ts index 4c7eec09..fc6a0dcf 100644 --- a/lib/approval/approval-workflow.ts +++ b/lib/approval/approval-workflow.ts @@ -180,6 +180,11 @@ export async function withApproval<T>( * - Knox 상신 성공 후 DB 업데이트 실패 위험 제거 */ + // 캐시 무효화 (결재 상신 시) + console.log(`[Approval Workflow] Revalidating cache after approval submission`); + const { revalidateApprovalLogs } = await import('./cache-utils'); + await revalidateApprovalLogs(); + let pendingActionId: number | undefined; try { @@ -253,38 +258,81 @@ export async function withApproval<T>( * @returns 액션 실행 결과 */ export async function executeApprovedAction(apInfId: string) { + // debug-utils import + const { debugLog, debugError, debugSuccess } = await import('@/lib/debug-utils'); + + debugLog('[executeApprovedAction] 시작', { apInfId }); + try { // 핸들러 자동 초기화 (폴링 서비스의 격리 문제 해결) + debugLog('[executeApprovedAction] 핸들러 초기화 중'); await ensureHandlersInitialized(); + debugLog('[executeApprovedAction] 핸들러 초기화 완료'); // 1. apInfId로 pendingAction 조회 + debugLog('[executeApprovedAction] pendingAction 조회 중', { apInfId }); const pendingAction = await db.query.pendingActions.findFirst({ where: eq(pendingActions.apInfId, apInfId), }); if (!pendingAction) { + debugLog('[executeApprovedAction] pendingAction 없음 (결재만 존재)', { apInfId }); console.log(`[Approval Workflow] No pending action found for approval: ${apInfId}`); return null; // 결재만 있고 실행할 액션이 없는 경우 } + debugLog('[executeApprovedAction] pendingAction 조회 완료', { + id: pendingAction.id, + actionType: pendingAction.actionType, + status: pendingAction.status, + createdBy: pendingAction.createdBy, + }); + // 이미 실행되었거나 실패한 액션은 스킵 if (['executed', 'failed'].includes(pendingAction.status)) { + debugLog('[executeApprovedAction] 이미 처리된 액션 스킵', { + apInfId, + status: pendingAction.status, + }); console.log(`[Approval Workflow] Pending action already processed: ${apInfId} (${pendingAction.status})`); return null; } // 2. 등록된 핸들러 조회 + debugLog('[executeApprovedAction] 핸들러 조회 중', { + actionType: pendingAction.actionType, + }); const handler = actionHandlers.get(pendingAction.actionType); if (!handler) { + debugError('[executeApprovedAction] 핸들러를 찾을 수 없음', { + actionType: pendingAction.actionType, + availableHandlers: Array.from(actionHandlers.keys()), + }); console.error('[Approval Workflow] Available handlers:', Array.from(actionHandlers.keys())); throw new Error(`Handler not found for action type: ${pendingAction.actionType}`); } + debugLog('[executeApprovedAction] 핸들러 조회 완료', { + actionType: pendingAction.actionType, + }); // 3. 실제 액션 실행 + debugLog('[executeApprovedAction] 핸들러 실행 시작', { + actionType: pendingAction.actionType, + apInfId, + payloadKeys: Object.keys(pendingAction.actionPayload || {}), + }); console.log(`[Approval Workflow] Executing action: ${pendingAction.actionType} (${apInfId})`); + const result = await handler(pendingAction.actionPayload); + debugSuccess('[executeApprovedAction] 핸들러 실행 완료', { + actionType: pendingAction.actionType, + apInfId, + resultKeys: result ? Object.keys(result) : [], + }); + // 4. 실행 완료 상태 업데이트 + debugLog('[executeApprovedAction] 상태 업데이트 중 (executed)'); await db.update(pendingActions) .set({ status: 'executed', @@ -292,21 +340,46 @@ export async function executeApprovedAction(apInfId: string) { executionResult: result, }) .where(eq(pendingActions.apInfId, apInfId)); + debugLog('[executeApprovedAction] 상태 업데이트 완료 (executed)'); + // 5. 캐시 무효화 (백그라운드에서도 동작) + debugLog('[executeApprovedAction] 캐시 무효화 중'); + const { revalidateApprovalLogs } = await import('./cache-utils'); + await revalidateApprovalLogs(); + debugLog('[executeApprovedAction] 캐시 무효화 완료'); + + debugSuccess('[executeApprovedAction] 전체 프로세스 완료', { + actionType: pendingAction.actionType, + apInfId, + }); console.log(`[Approval Workflow] ✅ Successfully executed: ${pendingAction.actionType} (${apInfId})`); return result; } catch (error) { + debugError('[executeApprovedAction] 실행 중 에러 발생', { + apInfId, + error: error instanceof Error ? error.message : String(error), + stack: error instanceof Error ? error.stack : undefined, + }); console.error(`[Approval Workflow] ❌ Failed to execute action for ${apInfId}:`, error); // 실패 상태 업데이트 - await db.update(pendingActions) - .set({ - status: 'failed', - errorMessage: error instanceof Error ? error.message : String(error), - executedAt: new Date(), - }) - .where(eq(pendingActions.apInfId, apInfId)); + try { + debugLog('[executeApprovedAction] 상태 업데이트 중 (failed)'); + await db.update(pendingActions) + .set({ + status: 'failed', + errorMessage: error instanceof Error ? error.message : String(error), + executedAt: new Date(), + }) + .where(eq(pendingActions.apInfId, apInfId)); + debugLog('[executeApprovedAction] 상태 업데이트 완료 (failed)'); + } catch (updateError) { + debugError('[executeApprovedAction] 상태 업데이트 실패', { + apInfId, + updateError: updateError instanceof Error ? updateError.message : String(updateError), + }); + } throw error; } @@ -337,6 +410,11 @@ export async function handleRejectedAction(apInfId: string, reason?: string) { }) .where(eq(pendingActions.apInfId, apInfId)); + // 캐시 무효화 (백그라운드에서도 동작) + console.log(`[Approval Workflow] Revalidating cache for rejected action: ${apInfId}`); + const { revalidateApprovalLogs } = await import('./cache-utils'); + await revalidateApprovalLogs(); + // TODO: 요청자에게 알림 발송 등 추가 처리 } catch (error) { console.error(`[Approval Workflow] Failed to handle rejected action for approval ${apInfId}:`, error); diff --git a/lib/approval/cache-utils.ts b/lib/approval/cache-utils.ts new file mode 100644 index 00000000..f4c56c25 --- /dev/null +++ b/lib/approval/cache-utils.ts @@ -0,0 +1,82 @@ +/** + * 결재 시스템 캐시 무효화 유틸리티 + * + * 백그라운드 프로세스(폴링 서비스)에서 request 컨텍스트 없이 + * Next.js 캐시를 무효화하기 위한 헬퍼 함수들 + */ + +/** + * 결재 관련 캐시 무효화 + * + * @param tags - 무효화할 캐시 태그 배열 + * @returns 무효화 결과 + * + * @example + * // 결재 로그 캐시 무효화 + * await revalidateApprovalCache(['approval-logs']); + * + * // 여러 캐시 동시 무효화 + * await revalidateApprovalCache(['approval-logs', 'pending-actions']); + */ +export async function revalidateApprovalCache(tags: string[]) { + try { + // 내부 API 호출로 캐시 무효화 + const baseUrl = process.env.NEXT_PUBLIC_BASE_URL || 'http://localhost:3000'; + const response = await fetch(`${baseUrl}/api/revalidate/approval`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + tags, + secret: process.env.REVALIDATION_SECRET, // 선택적 보안 + }), + }); + + if (!response.ok) { + throw new Error(`Cache revalidation failed: ${response.statusText}`); + } + + const result = await response.json(); + console.log(`[Approval Cache] Successfully revalidated tags:`, tags); + return result; + } catch (error) { + // 캐시 무효화 실패는 치명적이지 않으므로 로그만 남기고 진행 + console.error('[Approval Cache] Failed to revalidate cache:', error); + console.warn('[Approval Cache] Continuing despite cache revalidation failure'); + return { success: false, error }; + } +} + +/** + * 결재 로그 캐시 무효화 + */ +export async function revalidateApprovalLogs() { + return revalidateApprovalCache(['approval-logs']); +} + +/** + * Pending Actions 캐시 무효화 + */ +export async function revalidatePendingActions() { + return revalidateApprovalCache(['pending-actions']); +} + +/** + * 결재 관련 모든 캐시 무효화 + */ +export async function revalidateAllApprovalCaches() { + return revalidateApprovalCache([ + 'approval-logs', + 'pending-actions', + 'approval-templates', + ]); +} + +/** + * 특정 결재 ID의 상세 캐시 무효화 + */ +export async function revalidateApprovalDetail(apInfId: string) { + return revalidateApprovalCache([`approval-log-${apInfId}`]); +} + diff --git a/lib/approval/index.ts b/lib/approval/index.ts index 644c5fa8..82abac9a 100644 --- a/lib/approval/index.ts +++ b/lib/approval/index.ts @@ -32,3 +32,10 @@ export { export type { TemplateVariables, ApprovalConfig, ApprovalResult } from './types'; +export { + revalidateApprovalCache, + revalidateApprovalLogs, + revalidatePendingActions, + revalidateAllApprovalCaches, + revalidateApprovalDetail, +} from './cache-utils'; |
