import db from "@/db/db"; import { projects, tagClasses, tagTypes } from '@/db/schema'; import { eq, and } from 'drizzle-orm'; import { getSEDPToken } from "./sedp-token"; // 환경 변수 const SEDP_API_BASE_URL = process.env.SEDP_API_BASE_URL || 'http://sedpwebapi.ship.samsung.co.kr/dev/api'; // ObjectClass 인터페이스 정의 interface ObjectClass { PROJ_NO: string; CLS_ID: string; DESC: string; TAG_TYPE_ID: string | null; PRT_CLS_ID: string | null; LNK_ATT: any[]; DELETED: boolean; DEL_USER: string | null; DEL_DTM: string | null; CRTER_NO: string; CRTE_DTM: string; CHGER_NO: string | null; CHGE_DTM: string | null; _id: string; } interface Project { id: number; code: string; name: string; type?: string; createdAt?: Date; updatedAt?: Date; } // 동기화 결과 인터페이스 interface SyncResult { project: string; success: boolean; count?: number; error?: string; } // 오브젝트 클래스 데이터 가져오기 async function getObjectClasses(projectCode: string, token:string): Promise { try { const response = await fetch( `${SEDP_API_BASE_URL}/ObjectClass/Get`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'accept': '*/*', 'ApiKey': token, 'ProjectNo': projectCode }, body: JSON.stringify({ ContainDeleted: true }) } ); if (!response.ok) { throw new Error(`오브젝트 클래스 요청 실패: ${response.status} ${response.statusText}`); } const data = await response.json(); // 결과가 배열인지 확인 if (Array.isArray(data)) { return data; } else { // 단일 객체인 경우 배열로 변환 return [data]; } } catch (error) { console.error(`프로젝트 ${projectCode}의 오브젝트 클래스 가져오기 실패:`, error); throw error; } } // 태그 타입 존재 확인 async function verifyTagTypes(projectId: number, tagTypeCodes: string[]): Promise> { try { // 프로젝트에 있는 태그 타입 코드 조회 const existingTagTypes = await db.select({ code: tagTypes.code }) .from(tagTypes) .where(eq(tagTypes.projectId, projectId)); // 존재하는 태그 타입 코드 Set으로 반환 return new Set(existingTagTypes.map(type => type.code)); } catch (error) { console.error(`프로젝트 ID ${projectId}의 태그 타입 확인 실패:`, error); throw error; } } // 데이터베이스에 오브젝트 클래스 저장 (upsert 사용) async function saveObjectClassesToDatabase(projectId: number, classes: ObjectClass[]): Promise { try { // null이 아닌 TAG_TYPE_ID만 필터링 const validClasses = classes.filter(cls => cls.TAG_TYPE_ID !== null); if (validClasses.length === 0) { console.log(`프로젝트 ID ${projectId}에 저장할 유효한 오브젝트 클래스가 없습니다.`); return 0; } // 모든 태그 타입 ID 목록 추출 const tagTypeCodes = validClasses.map(cls => cls.TAG_TYPE_ID!); // 존재하는 태그 타입 확인 const existingTagTypeCodes = await verifyTagTypes(projectId, tagTypeCodes); // 태그 타입이 존재하는 오브젝트 클래스만 필터링 const classesToSave = validClasses.filter(cls => cls.TAG_TYPE_ID !== null && existingTagTypeCodes.has(cls.TAG_TYPE_ID) ); if (classesToSave.length === 0) { console.log(`프로젝트 ID ${projectId}에 저장할 유효한 오브젝트 클래스가 없습니다 (태그 타입 존재하지 않음).`); return 0; } // 현재 프로젝트의 오브젝트 클래스 코드 가져오기 const existingClasses = await db.select() .from(tagClasses) .where(eq(tagClasses.projectId, projectId)); // 코드 기준으로 맵 생성 const existingClassMap = new Map( existingClasses.map(cls => [cls.code, cls]) ); // 새로 추가할 항목 const toInsert = []; // 업데이트할 항목 const toUpdate = []; // API에 있는 코드 목록 const apiClassCodes = new Set(classesToSave.map(cls => cls.CLS_ID)); // 삭제할 코드 목록 const codesToDelete = existingClasses .map(cls => cls.code) .filter(code => !apiClassCodes.has(code)); // 클래스 데이터 처리 for (const cls of classesToSave) { // 데이터베이스 레코드 준비 const record = { code: cls.CLS_ID, projectId: projectId, label: cls.DESC, tagTypeCode: cls.TAG_TYPE_ID!, updatedAt: new Date() }; // 이미 존재하는 코드인지 확인 if (existingClassMap.has(cls.CLS_ID)) { // 업데이트 항목에 추가 toUpdate.push(record); } else { // 새로 추가할 항목에 추가 (createdAt 필드 추가) toInsert.push({ ...record, createdAt: new Date() }); } } // 트랜잭션 실행 let totalChanged = 0; // 1. 새 항목 삽입 if (toInsert.length > 0) { await db.insert(tagClasses).values(toInsert); totalChanged += toInsert.length; console.log(`프로젝트 ID ${projectId}에 ${toInsert.length}개의 새 오브젝트 클래스 추가 완료`); } // 2. 기존 항목 업데이트 for (const item of toUpdate) { await db.update(tagClasses) .set({ label: item.label, tagTypeCode: item.tagTypeCode, updatedAt: item.updatedAt }) .where( and( eq(tagClasses.code, item.code), eq(tagClasses.projectId, item.projectId) ) ); totalChanged += 1; } if (toUpdate.length > 0) { console.log(`프로젝트 ID ${projectId}의 ${toUpdate.length}개 오브젝트 클래스 업데이트 완료`); } // 3. 더 이상 존재하지 않는 항목 삭제 if (codesToDelete.length > 0) { for (const code of codesToDelete) { await db.delete(tagClasses) .where( and( eq(tagClasses.code, code), eq(tagClasses.projectId, projectId) ) ); } console.log(`프로젝트 ID ${projectId}에서 ${codesToDelete.length}개의 오브젝트 클래스 삭제 완료`); totalChanged += codesToDelete.length; } return totalChanged; } catch (error) { console.error(`오브젝트 클래스 저장 실패 (프로젝트 ID: ${projectId}):`, error); throw error; } } // 메인 동기화 함수 export async function syncObjectClasses() { try { console.log('오브젝트 클래스 동기화 시작:', new Date().toISOString()); // 1. 토큰 가져오기 const token = await getSEDPToken(); // 2. 모든 프로젝트 가져오기 const allProjects = await db.select().from(projects); // 3. 각 프로젝트에 대해 오브젝트 클래스 동기화 const results = await Promise.allSettled( allProjects.map(async (project: Project) => { try { // 오브젝트 클래스 데이터 가져오기 const objectClasses = await getObjectClasses(project.code, token); // 데이터베이스에 저장 const count = await saveObjectClassesToDatabase(project.id, objectClasses); return { project: project.code, success: true, count } as SyncResult; } catch (error) { console.error(`프로젝트 ${project.code} 동기화 실패:`, error); return { project: project.code, success: false, error: error instanceof Error ? error.message : String(error) } as SyncResult; } }) ); // 결과 처리를 위한 배열 준비 const successfulResults: SyncResult[] = []; const failedResults: SyncResult[] = []; // 결과 분류 results.forEach((result) => { if (result.status === 'fulfilled') { if (result.value.success) { successfulResults.push(result.value); } else { failedResults.push(result.value); } } else { // 거부된 프로미스는 실패로 간주 failedResults.push({ project: 'unknown', success: false, error: result.reason?.toString() || 'Unknown error' }); } }); const successCount = successfulResults.length; const failCount = failedResults.length; // 이제 안전하게 count 속성에 접근 가능 const totalItems = successfulResults.reduce((sum, result) => sum + (result.count || 0), 0 ); console.log(`오브젝트 클래스 동기화 완료: ${successCount}개 프로젝트 성공 (총 ${totalItems}개 항목), ${failCount}개 프로젝트 실패`); return { success: successCount, failed: failCount, items: totalItems, timestamp: new Date().toISOString() }; } catch (error) { console.error('오브젝트 클래스 동기화 중 오류 발생:', error); throw error; } }