diff options
Diffstat (limited to 'app/api/cron')
| -rw-r--r-- | app/api/cron/form-tags/start/route.ts | 136 | ||||
| -rw-r--r-- | app/api/cron/form-tags/status/route.ts | 46 | ||||
| -rw-r--r-- | app/api/cron/forms/route.ts | 57 | ||||
| -rw-r--r-- | app/api/cron/forms/start/route.ts | 100 | ||||
| -rw-r--r-- | app/api/cron/forms/status/route.ts | 46 | ||||
| -rw-r--r-- | app/api/cron/object-classes/route.ts | 4 | ||||
| -rw-r--r-- | app/api/cron/projects/route.ts | 5 | ||||
| -rw-r--r-- | app/api/cron/tag-types/route.ts | 2 | ||||
| -rw-r--r-- | app/api/cron/tags/start/route.ts | 133 | ||||
| -rw-r--r-- | app/api/cron/tags/status/route.ts | 46 |
10 files changed, 567 insertions, 8 deletions
diff --git a/app/api/cron/form-tags/start/route.ts b/app/api/cron/form-tags/start/route.ts new file mode 100644 index 00000000..6a029c4c --- /dev/null +++ b/app/api/cron/form-tags/start/route.ts @@ -0,0 +1,136 @@ +// app/api/cron/tags/start/route.ts +import { NextRequest } from 'next/server'; +import { v4 as uuidv4 } from 'uuid'; +import { revalidateTag } from 'next/cache'; + +// 동기화 작업의 상태를 저장할 Map +// 실제 프로덕션에서는 Redis 또는 DB에 저장하는 것이 좋습니다 +const syncJobs = new Map<string, { + status: 'queued' | 'processing' | 'completed' | 'failed'; + startTime: Date; + endTime?: Date; + result?: any; + error?: string; + progress?: number; + projectCode?: string; + formCode?: string; + packageId?: number; +}>(); + +export async function POST(request: NextRequest) { + try { + // 요청 데이터 가져오기 + let projectCode: string | undefined; + let formCode: string | undefined; + let packageId: number | undefined; + + + const body = await request.json(); + projectCode = body.projectCode; + formCode = body.formCode; + packageId = body.contractItemId; + + + // 고유 ID 생성 + const syncId = uuidv4(); + + // 작업 상태 초기화 + syncJobs.set(syncId, { + status: 'queued', + startTime: new Date(), + formCode, + projectCode, + packageId + + }); + + // 비동기 작업 시작 (백그라운드에서 실행) + processTagImport(syncId).catch(error => { + console.error('Background tag import job failed:', error); + syncJobs.set(syncId, { + ...syncJobs.get(syncId)!, + status: 'failed', + endTime: new Date(), + error: error.message || 'Unknown error occurred' + }); + }); + + // 즉시 응답 반환 (작업 ID 포함) + return Response.json({ + success: true, + message: 'Tag import job started', + syncId + }, { status: 200 }); + + } catch (error: any) { + console.error('Failed to start tag import job:', error); + return Response.json({ + success: false, + error: error.message || 'Failed to start tag import job' + }, { status: 500 }); + } +} + +// 백그라운드에서 실행되는 태그 가져오기 작업 +async function processTagImport(syncId: string) { + try { + const jobInfo = syncJobs.get(syncId)!; + const formCode = jobInfo.formCode; + const projectCode = jobInfo.projectCode; + const packageId = jobInfo.packageId || 0; + + // 상태 업데이트: 처리 중 + syncJobs.set(syncId, { + ...jobInfo, + status: 'processing', + progress: 0, + }); + + if (!formCode || !projectCode ) { + throw new Error('formCode,projectCode is required'); + } + + // 여기서 실제 태그 가져오기 로직 import + const { importTagsFromSEDP } = await import('@/lib/sedp/get-form-tags'); + + // 진행 상황 업데이트를 위한 콜백 함수 + const updateProgress = (progress: number) => { + syncJobs.set(syncId, { + ...syncJobs.get(syncId)!, + progress + }); + }; + + // 실제 태그 가져오기 실행 + const result = await importTagsFromSEDP(formCode, projectCode, packageId, updateProgress); + + // 명시적으로 캐시 무효화 + revalidateTag(`forms-${packageId}`); + + // 상태 업데이트: 완료 + syncJobs.set(syncId, { + ...syncJobs.get(syncId)!, + status: 'completed', + endTime: new Date(), + result, + progress: 100, + }); + + return result; + } catch (error: any) { + // 에러 발생 시 상태 업데이트 + syncJobs.set(syncId, { + ...syncJobs.get(syncId)!, + status: 'failed', + endTime: new Date(), + error: error.message || 'Unknown error occurred', + }); + + throw error; // 에러 다시 던지기 + } +} + +// 서버 메모리에 저장된 작업 상태 접근 함수 (다른 API에서 사용) +export function getSyncJobStatus(id: string) { + return syncJobs.get(id); +}
\ No newline at end of file diff --git a/app/api/cron/form-tags/status/route.ts b/app/api/cron/form-tags/status/route.ts new file mode 100644 index 00000000..9d288f52 --- /dev/null +++ b/app/api/cron/form-tags/status/route.ts @@ -0,0 +1,46 @@ +// app/api/cron/tags/status/route.ts +import { NextRequest } from 'next/server'; +import { getSyncJobStatus } from '../start/route'; + +export async function GET(request: NextRequest) { + try { + // URL에서 작업 ID 가져오기 + const searchParams = request.nextUrl.searchParams; + const syncId = searchParams.get('id'); + + if (!syncId) { + return Response.json({ + success: false, + error: 'Missing sync ID parameter' + }, { status: 400 }); + } + + // 작업 상태 조회 + const jobStatus = getSyncJobStatus(syncId); + + if (!jobStatus) { + return Response.json({ + success: false, + error: 'Sync job not found' + }, { status: 404 }); + } + + // 작업 상태 반환 + return Response.json({ + success: true, + status: jobStatus.status, + startTime: jobStatus.startTime, + endTime: jobStatus.endTime, + progress: jobStatus.progress, + result: jobStatus.result, + error: jobStatus.error + }, { status: 200 }); + + } catch (error: any) { + console.error('Error retrieving tag import status:', error); + return Response.json({ + success: false, + error: error.message || 'Failed to retrieve tag import status' + }, { status: 500 }); + } +}
\ No newline at end of file diff --git a/app/api/cron/forms/route.ts b/app/api/cron/forms/route.ts index f58c146b..abe6753a 100644 --- a/app/api/cron/forms/route.ts +++ b/app/api/cron/forms/route.ts @@ -1,20 +1,65 @@ -// src/app/api/cron/tag-form-mappings/route.ts +// app/api/cron/forms/route.ts import { syncTagFormMappings } from '@/lib/sedp/sync-form'; import { NextRequest } from 'next/server'; +import { revalidateTag } from 'next/cache'; + +// TypeScript에서 global 객체를 확장하기 위한 type 선언 +declare global { + var pendingTasks: Set<Promise<any>>; +} + +// 글로벌 태스크 집합 초기화 (서버가 시작될 때만 한 번 실행됨) +if (!global.pendingTasks) { + global.pendingTasks = new Set<Promise<any>>(); +} + +// 이 함수는 비동기 작업을 더 안전하게 처리하기 위한 도우미 함수입니다 +function runBackgroundTask<T>(task: Promise<T>, taskName: string): Promise<T> { + // 작업을 추적 세트에 추가 + global.pendingTasks.add(task); + + // finally 블록을 사용하여 작업이 완료될 때 세트에서 제거 + task + .then(result => { + console.log(`Background task '${taskName}' completed successfully`); + return result; + }) + .catch(error => { + console.error(`Background task '${taskName}' failed:`, error); + }) + .finally(() => { + global.pendingTasks.delete(task); + }); + + return task; +} export async function GET(request: NextRequest) { try { console.log('태그 폼 매핑 동기화 API 호출됨:', new Date().toISOString()); - // syncTagFormMappings 함수 호출 - const result = await syncTagFormMappings(); + // 비동기 작업을 생성하고 전역 객체에 저장 + const syncTask = syncTagFormMappings() + .then(result => { + // 작업이 완료되면 캐시 무효화 + revalidateTag('form-lists'); + return result; + }); + + // 백그라운드에서 작업이 계속 실행되도록 보장 + runBackgroundTask(syncTask, 'form-sync'); - // 성공 시 결과와 함께 200 OK 반환 - return Response.json({ success: true, result }, { status: 200 }); + // 먼저 상태를 반환하고, 그 동안 백그라운드에서 작업 계속 + return new Response( + JSON.stringify({ + success: true, + message: 'Form sync started in background. This may take a while.' + }), + { status: 202, headers: { 'Content-Type': 'application/json' } } + ); } catch (error: any) { console.error('태그 폼 매핑 동기화 API 에러:', error); - // 에러 시에는 message를 담아 500 반환 const message = error.message || 'Something went wrong'; return Response.json({ success: false, error: message }, { status: 500 }); } diff --git a/app/api/cron/forms/start/route.ts b/app/api/cron/forms/start/route.ts new file mode 100644 index 00000000..a99c4677 --- /dev/null +++ b/app/api/cron/forms/start/route.ts @@ -0,0 +1,100 @@ +// app/api/cron/forms/start/route.ts +import { NextRequest } from 'next/server'; +import { v4 as uuidv4 } from 'uuid'; +import { revalidateTag } from 'next/cache'; + +// 동기화 작업의 상태를 저장할 Map +// 실제 프로덕션에서는 Redis 또는 DB에 저장하는 것이 좋습니다 +const syncJobs = new Map<string, { + status: 'queued' | 'processing' | 'completed' | 'failed'; + startTime: Date; + endTime?: Date; + result?: any; + error?: string; + progress?: number; +}>(); + +export async function POST(request: NextRequest) { + try { + // 고유 ID 생성 + const syncId = uuidv4(); + + // 작업 상태 초기화 + syncJobs.set(syncId, { + status: 'queued', + startTime: new Date(), + }); + + // 비동기 작업 시작 (백그라운드에서 실행) + processSyncJob(syncId).catch(error => { + console.error('Background sync job failed:', error); + syncJobs.set(syncId, { + ...syncJobs.get(syncId)!, + status: 'failed', + endTime: new Date(), + error: error.message || 'Unknown error occurred' + }); + }); + + // 즉시 응답 반환 (작업 ID 포함) + return Response.json({ + success: true, + message: 'Form sync job started', + syncId + }, { status: 200 }); + + } catch (error: any) { + console.error('Failed to start sync job:', error); + return Response.json({ + success: false, + error: error.message || 'Failed to start sync job' + }, { status: 500 }); + } +} + +// 백그라운드에서 실행되는 동기화 작업 +async function processSyncJob(syncId: string) { + try { + // 상태 업데이트: 처리 중 + syncJobs.set(syncId, { + ...syncJobs.get(syncId)!, + status: 'processing', + progress: 0, + }); + + // 여기서 실제 동기화 로직 가져오기 + const { syncTagFormMappings } = await import('@/lib/sedp/sync-form'); + + // 실제 동기화 작업 실행 + const result = await syncTagFormMappings(); + + // 명시적으로 캐시 무효화 (동적 import 대신 상단에서 import) + revalidateTag('form-lists'); + + // 상태 업데이트: 완료 + syncJobs.set(syncId, { + ...syncJobs.get(syncId)!, + status: 'completed', + endTime: new Date(), + result, + progress: 100, + }); + + return result; + } catch (error: any) { + // 에러 발생 시 상태 업데이트 + syncJobs.set(syncId, { + ...syncJobs.get(syncId)!, + status: 'failed', + endTime: new Date(), + error: error.message || 'Unknown error occurred', + }); + + throw error; // 에러 다시 던지기 + } +} + +// 서버 메모리에 저장된 작업 상태 접근 함수 (다른 API에서 사용) +export function getSyncJobStatus(id: string) { + return syncJobs.get(id); +}
\ No newline at end of file diff --git a/app/api/cron/forms/status/route.ts b/app/api/cron/forms/status/route.ts new file mode 100644 index 00000000..c0e27b2e --- /dev/null +++ b/app/api/cron/forms/status/route.ts @@ -0,0 +1,46 @@ +// app/api/cron/forms/status/route.ts +import { NextRequest } from 'next/server'; +import { getSyncJobStatus } from '../start/route'; + +export async function GET(request: NextRequest) { + try { + // URL에서 작업 ID 가져오기 + const searchParams = request.nextUrl.searchParams; + const syncId = searchParams.get('id'); + + if (!syncId) { + return Response.json({ + success: false, + error: 'Missing sync ID parameter' + }, { status: 400 }); + } + + // 작업 상태 조회 + const jobStatus = getSyncJobStatus(syncId); + + if (!jobStatus) { + return Response.json({ + success: false, + error: 'Sync job not found' + }, { status: 404 }); + } + + // 작업 상태 반환 + return Response.json({ + success: true, + status: jobStatus.status, + startTime: jobStatus.startTime, + endTime: jobStatus.endTime, + progress: jobStatus.progress, + result: jobStatus.result, + error: jobStatus.error + }, { status: 200 }); + + } catch (error: any) { + console.error('Error retrieving sync status:', error); + return Response.json({ + success: false, + error: error.message || 'Failed to retrieve sync status' + }, { status: 500 }); + } +}
\ No newline at end of file diff --git a/app/api/cron/object-classes/route.ts b/app/api/cron/object-classes/route.ts index 9a574b1b..6743da70 100644 --- a/app/api/cron/object-classes/route.ts +++ b/app/api/cron/object-classes/route.ts @@ -1,6 +1,7 @@ // src/app/api/cron/object-classes/route.ts import { syncObjectClasses } from '@/lib/sedp/sync-object-class'; import { NextRequest } from 'next/server'; +import { revalidateTag } from 'next/cache'; export async function GET(request: NextRequest) { try { @@ -8,7 +9,8 @@ export async function GET(request: NextRequest) { // syncObjectClasses 함수 호출 const result = await syncObjectClasses(); - + revalidateTag("equip-class") + // 성공 시 결과와 함께 200 OK 반환 return Response.json({ success: true, result }, { status: 200 }); } catch (error: any) { diff --git a/app/api/cron/projects/route.ts b/app/api/cron/projects/route.ts index d8e6af51..12c89bdb 100644 --- a/app/api/cron/projects/route.ts +++ b/app/api/cron/projects/route.ts @@ -1,6 +1,7 @@ // src/app/api/cron/projects/route.ts import { syncProjects } from '@/lib/sedp/sync-projects'; import { NextRequest } from 'next/server'; +import { revalidateTag } from 'next/cache'; export async function GET(request: NextRequest) { try { @@ -8,7 +9,9 @@ export async function GET(request: NextRequest) { // syncProjects 함수 호출 const result = await syncProjects(); - + + revalidateTag('project-lists') + // 성공 시 결과와 함께 200 OK 반환 return Response.json({ success: true, result }, { status: 200 }); } catch (error: any) { diff --git a/app/api/cron/tag-types/route.ts b/app/api/cron/tag-types/route.ts index 35145984..43644833 100644 --- a/app/api/cron/tag-types/route.ts +++ b/app/api/cron/tag-types/route.ts @@ -1,5 +1,6 @@ import { syncTagSubfields } from '@/lib/sedp/sync-tag-types'; import { NextRequest } from 'next/server'; +import { revalidateTag } from 'next/cache'; export async function GET(request: NextRequest) { try { @@ -7,6 +8,7 @@ export async function GET(request: NextRequest) { // syncTagSubfields 함수 호출 const result = await syncTagSubfields(); + revalidateTag('tag-numbering') // 성공 시 결과와 함께 200 OK 반환 return Response.json({ success: true, result }, { status: 200 }); diff --git a/app/api/cron/tags/start/route.ts b/app/api/cron/tags/start/route.ts new file mode 100644 index 00000000..b506b9a3 --- /dev/null +++ b/app/api/cron/tags/start/route.ts @@ -0,0 +1,133 @@ +// app/api/cron/tags/start/route.ts +import { NextRequest } from 'next/server'; +import { v4 as uuidv4 } from 'uuid'; +import { revalidateTag } from 'next/cache'; + +// 동기화 작업의 상태를 저장할 Map +// 실제 프로덕션에서는 Redis 또는 DB에 저장하는 것이 좋습니다 +const syncJobs = new Map<string, { + status: 'queued' | 'processing' | 'completed' | 'failed'; + startTime: Date; + endTime?: Date; + result?: any; + error?: string; + progress?: number; + packageId?: number; +}>(); + +export async function POST(request: NextRequest) { + try { + // 요청 데이터 가져오기 + let packageId: number | undefined; + + try { + const body = await request.json(); + packageId = body.packageId; + } catch (error) { + // 요청 본문이 없거나 JSON이 아닌 경우, URL 파라미터 확인 + const searchParams = request.nextUrl.searchParams; + const packageIdParam = searchParams.get('packageId'); + if (packageIdParam) { + packageId = parseInt(packageIdParam, 10); + } + } + + // 고유 ID 생성 + const syncId = uuidv4(); + + // 작업 상태 초기화 + syncJobs.set(syncId, { + status: 'queued', + startTime: new Date(), + packageId + }); + + // 비동기 작업 시작 (백그라운드에서 실행) + processTagImport(syncId).catch(error => { + console.error('Background tag import job failed:', error); + syncJobs.set(syncId, { + ...syncJobs.get(syncId)!, + status: 'failed', + endTime: new Date(), + error: error.message || 'Unknown error occurred' + }); + }); + + // 즉시 응답 반환 (작업 ID 포함) + return Response.json({ + success: true, + message: 'Tag import job started', + syncId + }, { status: 200 }); + + } catch (error: any) { + console.error('Failed to start tag import job:', error); + return Response.json({ + success: false, + error: error.message || 'Failed to start tag import job' + }, { status: 500 }); + } +} + +// 백그라운드에서 실행되는 태그 가져오기 작업 +async function processTagImport(syncId: string) { + try { + const jobInfo = syncJobs.get(syncId)!; + const packageId = jobInfo.packageId; + + // 상태 업데이트: 처리 중 + syncJobs.set(syncId, { + ...jobInfo, + status: 'processing', + progress: 0, + }); + + if (!packageId) { + throw new Error('Package ID is required'); + } + + // 여기서 실제 태그 가져오기 로직 import + const { importTagsFromSEDP } = await import('@/lib/sedp/get-tags'); + + // 진행 상황 업데이트를 위한 콜백 함수 + const updateProgress = (progress: number) => { + syncJobs.set(syncId, { + ...syncJobs.get(syncId)!, + progress + }); + }; + + // 실제 태그 가져오기 실행 + const result = await importTagsFromSEDP(packageId, updateProgress); + + // 명시적으로 캐시 무효화 + revalidateTag(`tags-${packageId}`); + revalidateTag(`forms-${packageId}`); + + // 상태 업데이트: 완료 + syncJobs.set(syncId, { + ...syncJobs.get(syncId)!, + status: 'completed', + endTime: new Date(), + result, + progress: 100, + }); + + return result; + } catch (error: any) { + // 에러 발생 시 상태 업데이트 + syncJobs.set(syncId, { + ...syncJobs.get(syncId)!, + status: 'failed', + endTime: new Date(), + error: error.message || 'Unknown error occurred', + }); + + throw error; // 에러 다시 던지기 + } +} + +// 서버 메모리에 저장된 작업 상태 접근 함수 (다른 API에서 사용) +export function getSyncJobStatus(id: string) { + return syncJobs.get(id); +}
\ No newline at end of file diff --git a/app/api/cron/tags/status/route.ts b/app/api/cron/tags/status/route.ts new file mode 100644 index 00000000..9d288f52 --- /dev/null +++ b/app/api/cron/tags/status/route.ts @@ -0,0 +1,46 @@ +// app/api/cron/tags/status/route.ts +import { NextRequest } from 'next/server'; +import { getSyncJobStatus } from '../start/route'; + +export async function GET(request: NextRequest) { + try { + // URL에서 작업 ID 가져오기 + const searchParams = request.nextUrl.searchParams; + const syncId = searchParams.get('id'); + + if (!syncId) { + return Response.json({ + success: false, + error: 'Missing sync ID parameter' + }, { status: 400 }); + } + + // 작업 상태 조회 + const jobStatus = getSyncJobStatus(syncId); + + if (!jobStatus) { + return Response.json({ + success: false, + error: 'Sync job not found' + }, { status: 404 }); + } + + // 작업 상태 반환 + return Response.json({ + success: true, + status: jobStatus.status, + startTime: jobStatus.startTime, + endTime: jobStatus.endTime, + progress: jobStatus.progress, + result: jobStatus.result, + error: jobStatus.error + }, { status: 200 }); + + } catch (error: any) { + console.error('Error retrieving tag import status:', error); + return Response.json({ + success: false, + error: error.message || 'Failed to retrieve tag import status' + }, { status: 500 }); + } +}
\ No newline at end of file |
