/** * DDL 실행 컨트롤러 * 테이블/컬럼 생성 API 엔드포인트 */ import { Response } from "express"; import { AuthenticatedRequest } from "../middleware/superAdminMiddleware"; import { DDLExecutionService } from "../services/ddlExecutionService"; import { DDLAuditLogger } from "../services/ddlAuditLogger"; import { CreateTableRequest, AddColumnRequest } from "../types/ddl"; import { logger } from "../utils/logger"; export class DDLController { /** * POST /api/ddl/tables - 새 테이블 생성 */ static async createTable( req: AuthenticatedRequest, res: Response ): Promise { try { const { tableName, columns, description }: CreateTableRequest = req.body; const userId = req.user!.userId; const userCompanyCode = req.user!.companyCode; // 입력값 기본 검증 if (!tableName || !columns || columns.length === 0) { res.status(400).json({ success: false, error: { code: "INVALID_INPUT", details: "테이블명과 최소 1개의 컬럼이 필요합니다.", }, }); return; } logger.info("테이블 생성 요청", { tableName, userId, columnCount: columns.length, ip: req.ip, }); // inputType을 webType으로 변환 (레거시 호환성) const processedColumns = columns.map((col) => ({ ...col, webType: (col.inputType || col.webType || "text") as any, })); // DDL 실행 서비스 호출 const ddlService = new DDLExecutionService(); const result = await ddlService.createTable( tableName, processedColumns, userCompanyCode, userId, description ); if (result.success) { res.status(200).json({ success: true, message: result.message, data: { tableName, columnCount: columns.length, executedQuery: result.executedQuery, }, }); } else { res.status(400).json({ success: false, message: result.message, error: result.error, }); } } catch (error) { logger.error("테이블 생성 컨트롤러 오류:", { error: (error as Error).message, stack: (error as Error).stack, userId: req.user?.userId, body: req.body, }); res.status(500).json({ success: false, error: { code: "INTERNAL_SERVER_ERROR", details: "테이블 생성 중 서버 오류가 발생했습니다.", }, }); } } /** * POST /api/ddl/tables/:tableName/columns - 컬럼 추가 */ static async addColumn( req: AuthenticatedRequest, res: Response ): Promise { try { const { tableName } = req.params; const { column }: AddColumnRequest = req.body; const userId = req.user!.userId; const userCompanyCode = req.user!.companyCode; // 입력값 기본 검증 if (!tableName) { res.status(400).json({ success: false, error: { code: "INVALID_INPUT", details: "테이블명이 필요합니다.", }, }); return; } if (!column || !column.name || (!column.inputType && !column.webType)) { res.status(400).json({ success: false, error: { code: "INVALID_INPUT", details: "컬럼명과 입력타입이 필요합니다.", }, }); return; } logger.info("컬럼 추가 요청", { tableName, columnName: column.name, webType: column.webType, userId, ip: req.ip, }); // inputType을 webType으로 변환 (레거시 호환성) const processedColumn = { ...column, webType: (column.inputType || column.webType || "text") as any, }; // DDL 실행 서비스 호출 const ddlService = new DDLExecutionService(); const result = await ddlService.addColumn( tableName, processedColumn, userCompanyCode, userId ); if (result.success) { res.status(200).json({ success: true, message: result.message, data: { tableName, columnName: column.name, webType: column.webType, executedQuery: result.executedQuery, }, }); } else { res.status(400).json({ success: false, message: result.message, error: result.error, }); } } catch (error) { logger.error("컬럼 추가 컨트롤러 오류:", { error: (error as Error).message, stack: (error as Error).stack, userId: req.user?.userId, tableName: req.params.tableName, body: req.body, }); res.status(500).json({ success: false, error: { code: "INTERNAL_SERVER_ERROR", details: "컬럼 추가 중 서버 오류가 발생했습니다.", }, }); } } /** * GET /api/ddl/logs - DDL 실행 로그 조회 */ static async getDDLLogs( req: AuthenticatedRequest, res: Response ): Promise { try { const { limit, userId, ddlType } = req.query; const logs = await DDLAuditLogger.getRecentDDLLogs( limit ? parseInt(limit as string) : 50, userId as string, ddlType as string ); res.json({ success: true, data: { logs, total: logs.length, }, }); } catch (error) { logger.error("DDL 로그 조회 오류:", error); res.status(500).json({ success: false, error: { code: "LOG_RETRIEVAL_FAILED", details: "DDL 로그 조회 중 오류가 발생했습니다.", }, }); } } /** * GET /api/ddl/statistics - DDL 실행 통계 조회 */ static async getDDLStatistics( req: AuthenticatedRequest, res: Response ): Promise { try { const { fromDate, toDate } = req.query; const statistics = await DDLAuditLogger.getDDLStatistics( fromDate ? new Date(fromDate as string) : undefined, toDate ? new Date(toDate as string) : undefined ); res.json({ success: true, data: statistics, }); } catch (error) { logger.error("DDL 통계 조회 오류:", error); res.status(500).json({ success: false, error: { code: "STATISTICS_RETRIEVAL_FAILED", details: "DDL 통계 조회 중 오류가 발생했습니다.", }, }); } } /** * GET /api/ddl/tables/:tableName/info - 생성된 테이블 정보 조회 */ static async getTableInfo( req: AuthenticatedRequest, res: Response ): Promise { try { const { tableName } = req.params; const ddlService = new DDLExecutionService(); const tableInfo = await ddlService.getCreatedTableInfo(tableName); if (!tableInfo) { res.status(404).json({ success: false, error: { code: "TABLE_NOT_FOUND", details: `테이블 '${tableName}'을 찾을 수 없습니다.`, }, }); return; } res.json({ success: true, data: tableInfo, }); } catch (error) { logger.error("테이블 정보 조회 오류:", error); res.status(500).json({ success: false, error: { code: "TABLE_INFO_RETRIEVAL_FAILED", details: "테이블 정보 조회 중 오류가 발생했습니다.", }, }); } } /** * GET /api/ddl/tables/:tableName/history - 테이블 DDL 히스토리 조회 */ static async getTableDDLHistory( req: AuthenticatedRequest, res: Response ): Promise { try { const { tableName } = req.params; const history = await DDLAuditLogger.getTableDDLHistory(tableName); res.json({ success: true, data: { tableName, history, total: history.length, }, }); } catch (error) { logger.error("테이블 DDL 히스토리 조회 오류:", error); res.status(500).json({ success: false, error: { code: "HISTORY_RETRIEVAL_FAILED", details: "테이블 DDL 히스토리 조회 중 오류가 발생했습니다.", }, }); } } /** * POST /api/ddl/validate/table - 테이블 생성 사전 검증 */ static async validateTableCreation( req: AuthenticatedRequest, res: Response ): Promise { try { const { tableName, columns }: CreateTableRequest = req.body; if (!tableName || !columns) { res.status(400).json({ success: false, error: { code: "INVALID_INPUT", details: "테이블명과 컬럼 정보가 필요합니다.", }, }); return; } // 검증만 수행 (실제 생성하지 않음) const { DDLSafetyValidator } = await import( "../services/ddlSafetyValidator" ); const validationReport = DDLSafetyValidator.generateValidationReport( tableName, columns ); res.json({ success: true, data: { isValid: validationReport.validationResult.isValid, errors: validationReport.validationResult.errors, warnings: validationReport.validationResult.warnings, summary: validationReport.summary, }, }); } catch (error) { logger.error("테이블 생성 검증 오류:", error); res.status(500).json({ success: false, error: { code: "VALIDATION_ERROR", details: "테이블 생성 검증 중 오류가 발생했습니다.", }, }); } } /** * DELETE /api/ddl/logs/cleanup - 오래된 DDL 로그 정리 */ static async cleanupOldLogs( req: AuthenticatedRequest, res: Response ): Promise { try { const { retentionDays } = req.query; const days = retentionDays ? parseInt(retentionDays as string) : 90; const deletedCount = await DDLAuditLogger.cleanupOldLogs(days); res.json({ success: true, message: `${deletedCount}개의 오래된 DDL 로그가 삭제되었습니다.`, data: { deletedCount, retentionDays: days, }, }); } catch (error) { logger.error("DDL 로그 정리 오류:", error); res.status(500).json({ success: false, error: { code: "LOG_CLEANUP_FAILED", details: "DDL 로그 정리 중 오류가 발생했습니다.", }, }); } } }