summaryrefslogtreecommitdiff
path: root/lib/sedp/sync-object-class.ts
diff options
context:
space:
mode:
authordujinkim <dujin.kim@dtsolution.co.kr>2025-04-08 03:08:19 +0000
committerdujinkim <dujin.kim@dtsolution.co.kr>2025-04-08 03:08:19 +0000
commit9ceed79cf32c896f8a998399bf1b296506b2cd4a (patch)
treef84750fa6cac954d5e31221fc47a54c655fc06a9 /lib/sedp/sync-object-class.ts
parent230ce796836c25df26c130dbcd616ef97d12b2ec (diff)
로그인 및 미들웨어 처리. 구조 변경
Diffstat (limited to 'lib/sedp/sync-object-class.ts')
-rw-r--r--lib/sedp/sync-object-class.ts304
1 files changed, 304 insertions, 0 deletions
diff --git a/lib/sedp/sync-object-class.ts b/lib/sedp/sync-object-class.ts
new file mode 100644
index 00000000..1cf0c23b
--- /dev/null
+++ b/lib/sedp/sync-object-class.ts
@@ -0,0 +1,304 @@
+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<ObjectClass[]> {
+ 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<Set<string>> {
+ 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<number> {
+ 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;
+ }
+} \ No newline at end of file