summaryrefslogtreecommitdiff
path: root/lib/approval
diff options
context:
space:
mode:
Diffstat (limited to 'lib/approval')
-rw-r--r--lib/approval/approval-workflow.ts92
-rw-r--r--lib/approval/cache-utils.ts82
-rw-r--r--lib/approval/index.ts7
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';