import { Request, Response } from "express"; import { logger } from "../utils/logger"; import { TableManagementService } from "../services/tableManagementService"; import { entityJoinService } from "../services/entityJoinService"; import { referenceCacheService } from "../services/referenceCacheService"; const tableManagementService = new TableManagementService(); /** * Entity 조인 기능 컨트롤러 * ID값을 의미있는 데이터로 자동 변환하는 API 제공 */ export class EntityJoinController { /** * Entity 조인이 포함된 테이블 데이터 조회 * GET /api/table-management/tables/:tableName/data-with-joins */ async getTableDataWithJoins(req: Request, res: Response): Promise { try { const { tableName } = req.params; const { page = 1, size = 20, search, sortBy, sortOrder = "asc", enableEntityJoin = true, additionalJoinColumns, // 추가 조인 컬럼 정보 (JSON 문자열) screenEntityConfigs, // 화면별 엔티티 설정 (JSON 문자열) userLang, // userLang은 별도로 분리하여 search에 포함되지 않도록 함 ...otherParams } = req.query; logger.info(`Entity 조인 데이터 요청: ${tableName}`, { page, size, enableEntityJoin, search, }); // 검색 조건 처리 let searchConditions: Record = {}; if (search) { try { // search가 문자열인 경우 JSON 파싱 searchConditions = typeof search === "string" ? JSON.parse(search) : search; } catch (error) { logger.warn("검색 조건 파싱 오류:", error); searchConditions = {}; } } // 추가 조인 컬럼 정보 처리 let parsedAdditionalJoinColumns: any[] = []; if (additionalJoinColumns) { try { parsedAdditionalJoinColumns = typeof additionalJoinColumns === "string" ? JSON.parse(additionalJoinColumns) : additionalJoinColumns; logger.info("추가 조인 컬럼 파싱 완료:", parsedAdditionalJoinColumns); } catch (error) { logger.warn("추가 조인 컬럼 파싱 오류:", error); parsedAdditionalJoinColumns = []; } } // 화면별 엔티티 설정 처리 let parsedScreenEntityConfigs: Record = {}; if (screenEntityConfigs) { try { parsedScreenEntityConfigs = typeof screenEntityConfigs === "string" ? JSON.parse(screenEntityConfigs) : screenEntityConfigs; logger.info("화면별 엔티티 설정 파싱 완료:", parsedScreenEntityConfigs); } catch (error) { logger.warn("화면별 엔티티 설정 파싱 오류:", error); parsedScreenEntityConfigs = {}; } } const result = await tableManagementService.getTableDataWithEntityJoins( tableName, { page: Number(page), size: Number(size), search: Object.keys(searchConditions).length > 0 ? searchConditions : undefined, sortBy: sortBy as string, sortOrder: sortOrder as string, enableEntityJoin: enableEntityJoin === "true" || enableEntityJoin === true, additionalJoinColumns: parsedAdditionalJoinColumns, screenEntityConfigs: parsedScreenEntityConfigs, } ); res.status(200).json({ success: true, message: "Entity 조인 데이터 조회 성공", data: result, }); } catch (error) { logger.error("Entity 조인 데이터 조회 실패", error); res.status(500).json({ success: false, message: "Entity 조인 데이터 조회 중 오류가 발생했습니다.", error: error instanceof Error ? error.message : "Unknown error", }); } } /** * 테이블의 Entity 조인 설정 조회 * GET /api/table-management/tables/:tableName/entity-joins */ async getEntityJoinConfigs(req: Request, res: Response): Promise { try { const { tableName } = req.params; logger.info(`Entity 조인 설정 조회: ${tableName}`); const joinConfigs = await entityJoinService.detectEntityJoins(tableName); res.status(200).json({ success: true, message: "Entity 조인 설정 조회 성공", data: { tableName, joinConfigs, count: joinConfigs.length, }, }); } catch (error) { logger.error("Entity 조인 설정 조회 실패", error); res.status(500).json({ success: false, message: "Entity 조인 설정 조회 중 오류가 발생했습니다.", error: error instanceof Error ? error.message : "Unknown error", }); } } /** * 참조 테이블의 표시 가능한 컬럼 목록 조회 * GET /api/table-management/reference-tables/:tableName/columns */ async getReferenceTableColumns(req: Request, res: Response): Promise { try { const { tableName } = req.params; logger.info(`참조 테이블 컬럼 조회: ${tableName}`); const columns = await tableManagementService.getReferenceTableColumns(tableName); res.status(200).json({ success: true, message: "참조 테이블 컬럼 조회 성공", data: { tableName, columns, count: columns.length, }, }); } catch (error) { logger.error("참조 테이블 컬럼 조회 실패", error); res.status(500).json({ success: false, message: "참조 테이블 컬럼 조회 중 오류가 발생했습니다.", error: error instanceof Error ? error.message : "Unknown error", }); } } /** * 컬럼 Entity 설정 업데이트 (display_column 포함) * PUT /api/table-management/tables/:tableName/columns/:columnName/entity-settings */ async updateEntitySettings(req: Request, res: Response): Promise { try { const { tableName, columnName } = req.params; const { webType, referenceTable, referenceColumn, displayColumn, columnLabel, description, } = req.body; logger.info(`Entity 설정 업데이트: ${tableName}.${columnName}`, req.body); // Entity 타입인 경우 필수 필드 검증 if (webType === "entity") { if (!referenceTable || !referenceColumn) { res.status(400).json({ success: false, message: "Entity 타입의 경우 referenceTable과 referenceColumn이 필수입니다.", }); return; } } await tableManagementService.updateColumnLabel(tableName, columnName, { webType, referenceTable, referenceColumn, displayColumn, columnLabel, description, }); // Entity 설정 변경 시 관련 캐시 무효화 if (webType === "entity" && referenceTable) { referenceCacheService.invalidateCache( referenceTable, referenceColumn, displayColumn ); } res.status(200).json({ success: true, message: "Entity 설정 업데이트 성공", data: { tableName, columnName, settings: { webType, referenceTable, referenceColumn, displayColumn, }, }, }); } catch (error) { logger.error("Entity 설정 업데이트 실패", error); res.status(500).json({ success: false, message: "Entity 설정 업데이트 중 오류가 발생했습니다.", error: error instanceof Error ? error.message : "Unknown error", }); } } /** * 캐시 상태 조회 * GET /api/table-management/cache/status */ async getCacheStatus(req: Request, res: Response): Promise { try { logger.info("캐시 상태 조회"); const cacheInfo = referenceCacheService.getCacheInfo(); const overallHitRate = referenceCacheService.getOverallCacheHitRate(); res.status(200).json({ success: true, message: "캐시 상태 조회 성공", data: { overallHitRate, caches: cacheInfo, summary: { totalCaches: cacheInfo.length, totalSize: cacheInfo.reduce( (sum, cache) => sum + cache.dataSize, 0 ), averageHitRate: cacheInfo.length > 0 ? cacheInfo.reduce((sum, cache) => sum + cache.hitRate, 0) / cacheInfo.length : 0, }, }, }); } catch (error) { logger.error("캐시 상태 조회 실패", error); res.status(500).json({ success: false, message: "캐시 상태 조회 중 오류가 발생했습니다.", error: error instanceof Error ? error.message : "Unknown error", }); } } /** * 캐시 무효화 * DELETE /api/table-management/cache */ async invalidateCache(req: Request, res: Response): Promise { try { const { table, keyColumn, displayColumn } = req.query; logger.info("캐시 무효화 요청", { table, keyColumn, displayColumn }); if (table && keyColumn && displayColumn) { // 특정 캐시만 무효화 referenceCacheService.invalidateCache( table as string, keyColumn as string, displayColumn as string ); } else { // 전체 캐시 무효화 referenceCacheService.invalidateCache(); } res.status(200).json({ success: true, message: "캐시 무효화 완료", data: { target: table ? `${table}.${keyColumn}.${displayColumn}` : "전체", }, }); } catch (error) { logger.error("캐시 무효화 실패", error); res.status(500).json({ success: false, message: "캐시 무효화 중 오류가 발생했습니다.", error: error instanceof Error ? error.message : "Unknown error", }); } } /** * Entity 조인된 테이블의 추가 컬럼 목록 조회 * GET /api/table-management/tables/:tableName/entity-join-columns */ async getEntityJoinColumns(req: Request, res: Response): Promise { try { const { tableName } = req.params; logger.info(`Entity 조인 컬럼 조회: ${tableName}`); // 1. 현재 테이블의 Entity 조인 설정 조회 const joinConfigs = await entityJoinService.detectEntityJoins(tableName); if (joinConfigs.length === 0) { res.status(200).json({ success: true, message: "Entity 조인 설정이 없습니다.", data: { tableName, joinTables: [], availableColumns: [], }, }); return; } // 2. 각 조인 테이블의 컬럼 정보 조회 const joinTablesInfo = await Promise.all( joinConfigs.map(async (config) => { try { const columns = await tableManagementService.getReferenceTableColumns( config.referenceTable ); // 현재 display_column으로 사용 중인 컬럼 제외 const availableColumns = columns.filter( (col) => col.columnName !== config.displayColumn ); return { joinConfig: config, tableName: config.referenceTable, currentDisplayColumn: config.displayColumn, availableColumns: availableColumns.map((col) => ({ columnName: col.columnName, columnLabel: col.displayName || col.columnName, dataType: col.dataType, isNullable: true, // 기본값으로 설정 maxLength: undefined, // 정보가 없으므로 undefined description: col.displayName, })), }; } catch (error) { logger.warn( `참조 테이블 컬럼 조회 실패: ${config.referenceTable}`, error ); return { joinConfig: config, tableName: config.referenceTable, currentDisplayColumn: config.displayColumn, availableColumns: [], error: error instanceof Error ? error.message : "Unknown error", }; } }) ); // 3. 사용 가능한 모든 컬럼 목록 생성 (중복 제거) const allAvailableColumns: Array<{ tableName: string; columnName: string; columnLabel: string; dataType: string; joinAlias: string; suggestedLabel: string; }> = []; joinTablesInfo.forEach((info) => { info.availableColumns.forEach((col) => { const joinAlias = `${info.joinConfig.sourceColumn}_${col.columnName}`; const suggestedLabel = col.columnLabel; // 라벨명만 사용 allAvailableColumns.push({ tableName: info.tableName, columnName: col.columnName, columnLabel: col.columnLabel, dataType: col.dataType, joinAlias, suggestedLabel, }); }); }); res.status(200).json({ success: true, message: "Entity 조인 컬럼 조회 성공", data: { tableName, joinTables: joinTablesInfo, availableColumns: allAvailableColumns, summary: { totalJoinTables: joinConfigs.length, totalAvailableColumns: allAvailableColumns.length, }, }, }); } catch (error) { logger.error("Entity 조인 컬럼 조회 실패", error); res.status(500).json({ success: false, message: "Entity 조인 컬럼 조회 중 오류가 발생했습니다.", error: error instanceof Error ? error.message : "Unknown error", }); } } /** * 공통 참조 테이블 자동 캐싱 * POST /api/table-management/cache/preload */ async preloadCommonCaches(req: Request, res: Response): Promise { try { logger.info("공통 참조 테이블 자동 캐싱 시작"); await referenceCacheService.autoPreloadCommonTables(); const cacheInfo = referenceCacheService.getCacheInfo(); res.status(200).json({ success: true, message: "공통 참조 테이블 캐싱 완료", data: { preloadedCaches: cacheInfo.length, caches: cacheInfo, }, }); } catch (error) { logger.error("공통 참조 테이블 캐싱 실패", error); res.status(500).json({ success: false, message: "공통 참조 테이블 캐싱 중 오류가 발생했습니다.", error: error instanceof Error ? error.message : "Unknown error", }); } } } export const entityJoinController = new EntityJoinController();