diff --git a/backend-node/src/controllers/batchController.ts b/backend-node/src/controllers/batchController.ts index 0858cc37..8a29e5bf 100644 --- a/backend-node/src/controllers/batchController.ts +++ b/backend-node/src/controllers/batchController.ts @@ -4,7 +4,11 @@ import { Request, Response } from "express"; import { BatchService } from "../services/batchService"; import { BatchSchedulerService } from "../services/batchSchedulerService"; -import { BatchConfigFilter, CreateBatchConfigRequest, UpdateBatchConfigRequest } from "../types/batchTypes"; +import { + BatchConfigFilter, + CreateBatchConfigRequest, + UpdateBatchConfigRequest, +} from "../types/batchTypes"; export interface AuthenticatedRequest extends Request { user?: { @@ -16,32 +20,36 @@ export interface AuthenticatedRequest extends Request { export class BatchController { /** - * 배치 설정 목록 조회 + * 배치 설정 목록 조회 (회사별) * GET /api/batch-configs */ static async getBatchConfigs(req: AuthenticatedRequest, res: Response) { try { const { page = 1, limit = 10, search, isActive } = req.query; - + const userCompanyCode = req.user?.companyCode; + const filter: BatchConfigFilter = { page: Number(page), limit: Number(limit), search: search as string, - is_active: isActive as string + is_active: isActive as string, }; - const result = await BatchService.getBatchConfigs(filter); - + const result = await BatchService.getBatchConfigs( + filter, + userCompanyCode + ); + res.json({ success: true, data: result.data, - pagination: result.pagination + pagination: result.pagination, }); } catch (error) { console.error("배치 설정 목록 조회 오류:", error); res.status(500).json({ success: false, - message: "배치 설정 목록 조회에 실패했습니다." + message: "배치 설정 목록 조회에 실패했습니다.", }); } } @@ -50,10 +58,13 @@ export class BatchController { * 사용 가능한 커넥션 목록 조회 * GET /api/batch-configs/connections */ - static async getAvailableConnections(req: AuthenticatedRequest, res: Response) { + static async getAvailableConnections( + req: AuthenticatedRequest, + res: Response + ) { try { const result = await BatchService.getAvailableConnections(); - + if (result.success) { res.json(result); } else { @@ -63,7 +74,7 @@ export class BatchController { console.error("커넥션 목록 조회 오류:", error); res.status(500).json({ success: false, - message: "커넥션 목록 조회에 실패했습니다." + message: "커넥션 목록 조회에 실패했습니다.", }); } } @@ -73,20 +84,26 @@ export class BatchController { * GET /api/batch-configs/connections/:type/tables * GET /api/batch-configs/connections/:type/:id/tables */ - static async getTablesFromConnection(req: AuthenticatedRequest, res: Response) { + static async getTablesFromConnection( + req: AuthenticatedRequest, + res: Response + ) { try { const { type, id } = req.params; - - if (!type || (type !== 'internal' && type !== 'external')) { + + if (!type || (type !== "internal" && type !== "external")) { return res.status(400).json({ success: false, - message: "올바른 연결 타입을 지정해주세요. (internal 또는 external)" + message: "올바른 연결 타입을 지정해주세요. (internal 또는 external)", }); } - const connectionId = type === 'external' ? Number(id) : undefined; - const result = await BatchService.getTablesFromConnection(type, connectionId); - + const connectionId = type === "external" ? Number(id) : undefined; + const result = await BatchService.getTablesFromConnection( + type, + connectionId + ); + if (result.success) { return res.json(result); } else { @@ -96,7 +113,7 @@ export class BatchController { console.error("테이블 목록 조회 오류:", error); return res.status(500).json({ success: false, - message: "테이블 목록 조회에 실패했습니다." + message: "테이블 목록 조회에 실패했습니다.", }); } } @@ -109,24 +126,28 @@ export class BatchController { static async getTableColumns(req: AuthenticatedRequest, res: Response) { try { const { type, id, tableName } = req.params; - + if (!type || !tableName) { return res.status(400).json({ success: false, - message: "연결 타입과 테이블명을 모두 지정해주세요." + message: "연결 타입과 테이블명을 모두 지정해주세요.", }); } - if (type !== 'internal' && type !== 'external') { + if (type !== "internal" && type !== "external") { return res.status(400).json({ success: false, - message: "올바른 연결 타입을 지정해주세요. (internal 또는 external)" + message: "올바른 연결 타입을 지정해주세요. (internal 또는 external)", }); } - const connectionId = type === 'external' ? Number(id) : undefined; - const result = await BatchService.getTableColumns(type, connectionId, tableName); - + const connectionId = type === "external" ? Number(id) : undefined; + const result = await BatchService.getTableColumns( + type, + connectionId, + tableName + ); + if (result.success) { return res.json(result); } else { @@ -136,36 +157,40 @@ export class BatchController { console.error("컬럼 정보 조회 오류:", error); return res.status(500).json({ success: false, - message: "컬럼 정보 조회에 실패했습니다." + message: "컬럼 정보 조회에 실패했습니다.", }); } } /** - * 특정 배치 설정 조회 + * 특정 배치 설정 조회 (회사별) * GET /api/batch-configs/:id */ static async getBatchConfigById(req: AuthenticatedRequest, res: Response) { try { const { id } = req.params; - const batchConfig = await BatchService.getBatchConfigById(Number(id)); - + const userCompanyCode = req.user?.companyCode; + const batchConfig = await BatchService.getBatchConfigById( + Number(id), + userCompanyCode + ); + if (!batchConfig) { return res.status(404).json({ success: false, - message: "배치 설정을 찾을 수 없습니다." + message: "배치 설정을 찾을 수 없습니다.", }); } - + return res.json({ success: true, - data: batchConfig + data: batchConfig, }); } catch (error) { console.error("배치 설정 조회 오류:", error); return res.status(500).json({ success: false, - message: "배치 설정 조회에 실패했습니다." + message: "배치 설정 조회에 실패했습니다.", }); } } @@ -177,11 +202,17 @@ export class BatchController { static async createBatchConfig(req: AuthenticatedRequest, res: Response) { try { const { batchName, description, cronSchedule, mappings } = req.body; - - if (!batchName || !cronSchedule || !mappings || !Array.isArray(mappings)) { + + if ( + !batchName || + !cronSchedule || + !mappings || + !Array.isArray(mappings) + ) { return res.status(400).json({ success: false, - message: "필수 필드가 누락되었습니다. (batchName, cronSchedule, mappings)" + message: + "필수 필드가 누락되었습니다. (batchName, cronSchedule, mappings)", }); } @@ -189,102 +220,123 @@ export class BatchController { batchName, description, cronSchedule, - mappings + mappings, } as CreateBatchConfigRequest); // 생성된 배치가 활성화 상태라면 스케줄러에 등록 (즉시 실행 비활성화) - if (batchConfig.data && batchConfig.data.is_active === 'Y' && batchConfig.data.id) { - await BatchSchedulerService.updateBatchSchedule(batchConfig.data.id, false); + if ( + batchConfig.data && + batchConfig.data.is_active === "Y" && + batchConfig.data.id + ) { + await BatchSchedulerService.updateBatchSchedule( + batchConfig.data.id, + false + ); } - + return res.status(201).json({ success: true, data: batchConfig, - message: "배치 설정이 성공적으로 생성되었습니다." + message: "배치 설정이 성공적으로 생성되었습니다.", }); } catch (error) { console.error("배치 설정 생성 오류:", error); return res.status(500).json({ success: false, - message: "배치 설정 생성에 실패했습니다." + message: "배치 설정 생성에 실패했습니다.", }); } } /** - * 배치 설정 수정 + * 배치 설정 수정 (회사별) * PUT /api/batch-configs/:id */ static async updateBatchConfig(req: AuthenticatedRequest, res: Response) { try { const { id } = req.params; - const { batchName, description, cronSchedule, mappings, isActive } = req.body; - + const { batchName, description, cronSchedule, mappings, isActive } = + req.body; + const userId = req.user?.userId; + const userCompanyCode = req.user?.companyCode; + if (!batchName || !cronSchedule) { return res.status(400).json({ success: false, - message: "필수 필드가 누락되었습니다. (batchName, cronSchedule)" + message: "필수 필드가 누락되었습니다. (batchName, cronSchedule)", }); } - const batchConfig = await BatchService.updateBatchConfig(Number(id), { - batchName, - description, - cronSchedule, - mappings, - isActive - } as UpdateBatchConfigRequest); - + const batchConfig = await BatchService.updateBatchConfig( + Number(id), + { + batchName, + description, + cronSchedule, + mappings, + isActive, + } as UpdateBatchConfigRequest, + userId, + userCompanyCode + ); + if (!batchConfig) { return res.status(404).json({ success: false, - message: "배치 설정을 찾을 수 없습니다." + message: "배치 설정을 찾을 수 없습니다.", }); } // 스케줄러에서 배치 스케줄 업데이트 (즉시 실행 비활성화) await BatchSchedulerService.updateBatchSchedule(Number(id), false); - + return res.json({ success: true, data: batchConfig, - message: "배치 설정이 성공적으로 수정되었습니다." + message: "배치 설정이 성공적으로 수정되었습니다.", }); } catch (error) { console.error("배치 설정 수정 오류:", error); return res.status(500).json({ success: false, - message: "배치 설정 수정에 실패했습니다." + message: "배치 설정 수정에 실패했습니다.", }); } } /** - * 배치 설정 삭제 (논리 삭제) + * 배치 설정 삭제 (논리 삭제, 회사별) * DELETE /api/batch-configs/:id */ static async deleteBatchConfig(req: AuthenticatedRequest, res: Response) { try { const { id } = req.params; - const result = await BatchService.deleteBatchConfig(Number(id)); - + const userId = req.user?.userId; + const userCompanyCode = req.user?.companyCode; + const result = await BatchService.deleteBatchConfig( + Number(id), + userId, + userCompanyCode + ); + if (!result) { return res.status(404).json({ success: false, - message: "배치 설정을 찾을 수 없습니다." + message: "배치 설정을 찾을 수 없습니다.", }); } - + return res.json({ success: true, - message: "배치 설정이 성공적으로 삭제되었습니다." + message: "배치 설정이 성공적으로 삭제되었습니다.", }); } catch (error) { console.error("배치 설정 삭제 오류:", error); return res.status(500).json({ success: false, - message: "배치 설정 삭제에 실패했습니다." + message: "배치 설정 삭제에 실패했습니다.", }); } } -} \ No newline at end of file +} diff --git a/backend-node/src/controllers/batchExecutionLogController.ts b/backend-node/src/controllers/batchExecutionLogController.ts index 68d8d880..84608731 100644 --- a/backend-node/src/controllers/batchExecutionLogController.ts +++ b/backend-node/src/controllers/batchExecutionLogController.ts @@ -4,7 +4,11 @@ import { Request, Response } from "express"; import { AuthenticatedRequest } from "../types/auth"; import { BatchExecutionLogService } from "../services/batchExecutionLogService"; -import { BatchExecutionLogFilter, CreateBatchExecutionLogRequest, UpdateBatchExecutionLogRequest } from "../types/batchExecutionLogTypes"; +import { + BatchExecutionLogFilter, + CreateBatchExecutionLogRequest, + UpdateBatchExecutionLogRequest, +} from "../types/batchExecutionLogTypes"; export class BatchExecutionLogController { /** @@ -18,7 +22,7 @@ export class BatchExecutionLogController { start_date, end_date, page, - limit + limit, } = req.query; const filter: BatchExecutionLogFilter = { @@ -27,11 +31,15 @@ export class BatchExecutionLogController { start_date: start_date ? new Date(start_date as string) : undefined, end_date: end_date ? new Date(end_date as string) : undefined, page: page ? Number(page) : undefined, - limit: limit ? Number(limit) : undefined + limit: limit ? Number(limit) : undefined, }; - const result = await BatchExecutionLogService.getExecutionLogs(filter); - + const userCompanyCode = req.user?.companyCode; + const result = await BatchExecutionLogService.getExecutionLogs( + filter, + userCompanyCode + ); + if (result.success) { res.json(result); } else { @@ -42,7 +50,7 @@ export class BatchExecutionLogController { res.status(500).json({ success: false, message: "배치 실행 로그 조회 중 오류가 발생했습니다.", - error: error instanceof Error ? error.message : "알 수 없는 오류" + error: error instanceof Error ? error.message : "알 수 없는 오류", }); } } @@ -53,9 +61,9 @@ export class BatchExecutionLogController { static async createExecutionLog(req: AuthenticatedRequest, res: Response) { try { const data: CreateBatchExecutionLogRequest = req.body; - + const result = await BatchExecutionLogService.createExecutionLog(data); - + if (result.success) { res.status(201).json(result); } else { @@ -66,7 +74,7 @@ export class BatchExecutionLogController { res.status(500).json({ success: false, message: "배치 실행 로그 생성 중 오류가 발생했습니다.", - error: error instanceof Error ? error.message : "알 수 없는 오류" + error: error instanceof Error ? error.message : "알 수 없는 오류", }); } } @@ -78,9 +86,12 @@ export class BatchExecutionLogController { try { const { id } = req.params; const data: UpdateBatchExecutionLogRequest = req.body; - - const result = await BatchExecutionLogService.updateExecutionLog(Number(id), data); - + + const result = await BatchExecutionLogService.updateExecutionLog( + Number(id), + data + ); + if (result.success) { res.json(result); } else { @@ -91,7 +102,7 @@ export class BatchExecutionLogController { res.status(500).json({ success: false, message: "배치 실행 로그 업데이트 중 오류가 발생했습니다.", - error: error instanceof Error ? error.message : "알 수 없는 오류" + error: error instanceof Error ? error.message : "알 수 없는 오류", }); } } @@ -102,9 +113,11 @@ export class BatchExecutionLogController { static async deleteExecutionLog(req: AuthenticatedRequest, res: Response) { try { const { id } = req.params; - - const result = await BatchExecutionLogService.deleteExecutionLog(Number(id)); - + + const result = await BatchExecutionLogService.deleteExecutionLog( + Number(id) + ); + if (result.success) { res.json(result); } else { @@ -115,7 +128,7 @@ export class BatchExecutionLogController { res.status(500).json({ success: false, message: "배치 실행 로그 삭제 중 오류가 발생했습니다.", - error: error instanceof Error ? error.message : "알 수 없는 오류" + error: error instanceof Error ? error.message : "알 수 없는 오류", }); } } @@ -126,9 +139,11 @@ export class BatchExecutionLogController { static async getLatestExecutionLog(req: AuthenticatedRequest, res: Response) { try { const { batchConfigId } = req.params; - - const result = await BatchExecutionLogService.getLatestExecutionLog(Number(batchConfigId)); - + + const result = await BatchExecutionLogService.getLatestExecutionLog( + Number(batchConfigId) + ); + if (result.success) { res.json(result); } else { @@ -139,7 +154,7 @@ export class BatchExecutionLogController { res.status(500).json({ success: false, message: "최신 배치 실행 로그 조회 중 오류가 발생했습니다.", - error: error instanceof Error ? error.message : "알 수 없는 오류" + error: error instanceof Error ? error.message : "알 수 없는 오류", }); } } @@ -149,18 +164,14 @@ export class BatchExecutionLogController { */ static async getExecutionStats(req: AuthenticatedRequest, res: Response) { try { - const { - batch_config_id, - start_date, - end_date - } = req.query; + const { batch_config_id, start_date, end_date } = req.query; const result = await BatchExecutionLogService.getExecutionStats( batch_config_id ? Number(batch_config_id) : undefined, start_date ? new Date(start_date as string) : undefined, end_date ? new Date(end_date as string) : undefined ); - + if (result.success) { res.json(result); } else { @@ -171,9 +182,8 @@ export class BatchExecutionLogController { res.status(500).json({ success: false, message: "배치 실행 통계 조회 중 오류가 발생했습니다.", - error: error instanceof Error ? error.message : "알 수 없는 오류" + error: error instanceof Error ? error.message : "알 수 없는 오류", }); } } } - diff --git a/backend-node/src/controllers/batchManagementController.ts b/backend-node/src/controllers/batchManagementController.ts index 71640577..d1be2311 100644 --- a/backend-node/src/controllers/batchManagementController.ts +++ b/backend-node/src/controllers/batchManagementController.ts @@ -3,7 +3,12 @@ import { Response } from "express"; import { AuthenticatedRequest } from "../types/auth"; -import { BatchManagementService, BatchConnectionInfo, BatchTableInfo, BatchColumnInfo } from "../services/batchManagementService"; +import { + BatchManagementService, + BatchConnectionInfo, + BatchTableInfo, + BatchColumnInfo, +} from "../services/batchManagementService"; import { BatchService } from "../services/batchService"; import { BatchSchedulerService } from "../services/batchSchedulerService"; import { BatchExternalDbService } from "../services/batchExternalDbService"; @@ -11,11 +16,16 @@ import { CreateBatchConfigRequest, BatchConfig } from "../types/batchTypes"; export class BatchManagementController { /** - * 사용 가능한 커넥션 목록 조회 + * 사용 가능한 커넥션 목록 조회 (회사별) */ - static async getAvailableConnections(req: AuthenticatedRequest, res: Response) { + static async getAvailableConnections( + req: AuthenticatedRequest, + res: Response + ) { try { - const result = await BatchManagementService.getAvailableConnections(); + const userCompanyCode = req.user?.companyCode; + const result = + await BatchManagementService.getAvailableConnections(userCompanyCode); if (result.success) { res.json(result); } else { @@ -26,28 +36,36 @@ export class BatchManagementController { res.status(500).json({ success: false, message: "커넥션 목록 조회 중 오류가 발생했습니다.", - error: error instanceof Error ? error.message : "알 수 없는 오류" + error: error instanceof Error ? error.message : "알 수 없는 오류", }); } } /** - * 특정 커넥션의 테이블 목록 조회 + * 특정 커넥션의 테이블 목록 조회 (회사별) */ - static async getTablesFromConnection(req: AuthenticatedRequest, res: Response) { + static async getTablesFromConnection( + req: AuthenticatedRequest, + res: Response + ) { try { const { type, id } = req.params; - - if (type !== 'internal' && type !== 'external') { + const userCompanyCode = req.user?.companyCode; + + if (type !== "internal" && type !== "external") { return res.status(400).json({ success: false, - message: "올바른 연결 타입을 지정해주세요. (internal 또는 external)" + message: "올바른 연결 타입을 지정해주세요. (internal 또는 external)", }); } - const connectionId = type === 'external' ? Number(id) : undefined; - const result = await BatchManagementService.getTablesFromConnection(type, connectionId); - + const connectionId = type === "external" ? Number(id) : undefined; + const result = await BatchManagementService.getTablesFromConnection( + type, + connectionId, + userCompanyCode + ); + if (result.success) { return res.json(result); } else { @@ -58,28 +76,34 @@ export class BatchManagementController { return res.status(500).json({ success: false, message: "테이블 목록 조회 중 오류가 발생했습니다.", - error: error instanceof Error ? error.message : "알 수 없는 오류" + error: error instanceof Error ? error.message : "알 수 없는 오류", }); } } /** - * 특정 테이블의 컬럼 정보 조회 + * 특정 테이블의 컬럼 정보 조회 (회사별) */ static async getTableColumns(req: AuthenticatedRequest, res: Response) { try { const { type, id, tableName } = req.params; - - if (type !== 'internal' && type !== 'external') { + const userCompanyCode = req.user?.companyCode; + + if (type !== "internal" && type !== "external") { return res.status(400).json({ success: false, - message: "올바른 연결 타입을 지정해주세요. (internal 또는 external)" + message: "올바른 연결 타입을 지정해주세요. (internal 또는 external)", }); } - const connectionId = type === 'external' ? Number(id) : undefined; - const result = await BatchManagementService.getTableColumns(type, connectionId, tableName); - + const connectionId = type === "external" ? Number(id) : undefined; + const result = await BatchManagementService.getTableColumns( + type, + connectionId, + tableName, + userCompanyCode + ); + if (result.success) { return res.json(result); } else { @@ -90,7 +114,7 @@ export class BatchManagementController { return res.status(500).json({ success: false, message: "컬럼 정보 조회 중 오류가 발생했습니다.", - error: error instanceof Error ? error.message : "알 수 없는 오류" + error: error instanceof Error ? error.message : "알 수 없는 오류", }); } } @@ -101,12 +125,19 @@ export class BatchManagementController { */ static async createBatchConfig(req: AuthenticatedRequest, res: Response) { try { - const { batchName, description, cronSchedule, mappings, isActive } = req.body; - - if (!batchName || !cronSchedule || !mappings || !Array.isArray(mappings)) { + const { batchName, description, cronSchedule, mappings, isActive } = + req.body; + + if ( + !batchName || + !cronSchedule || + !mappings || + !Array.isArray(mappings) + ) { return res.status(400).json({ success: false, - message: "필수 필드가 누락되었습니다. (batchName, cronSchedule, mappings)" + message: + "필수 필드가 누락되었습니다. (batchName, cronSchedule, mappings)", }); } @@ -115,20 +146,20 @@ export class BatchManagementController { description, cronSchedule, mappings, - isActive: isActive !== undefined ? isActive : true + isActive: isActive !== undefined ? isActive : true, } as CreateBatchConfigRequest); - + return res.status(201).json({ success: true, data: batchConfig, - message: "배치 설정이 성공적으로 생성되었습니다." + message: "배치 설정이 성공적으로 생성되었습니다.", }); } catch (error) { console.error("배치 설정 생성 오류:", error); return res.status(500).json({ success: false, message: "배치 설정 생성에 실패했습니다.", - error: error instanceof Error ? error.message : "알 수 없는 오류" + error: error instanceof Error ? error.message : "알 수 없는 오류", }); } } @@ -141,28 +172,28 @@ export class BatchManagementController { try { const { id } = req.params; console.log("🔍 배치 설정 조회 요청:", id); - + const result = await BatchService.getBatchConfigById(Number(id)); - + if (!result.success) { return res.status(404).json({ success: false, - message: result.message || "배치 설정을 찾을 수 없습니다." + message: result.message || "배치 설정을 찾을 수 없습니다.", }); } - + console.log("📋 조회된 배치 설정:", result.data); - + return res.json({ success: true, - data: result.data + data: result.data, }); } catch (error) { console.error("❌ 배치 설정 조회 오류:", error); return res.status(500).json({ success: false, message: "배치 설정 조회에 실패했습니다.", - error: error instanceof Error ? error.message : "알 수 없는 오류" + error: error instanceof Error ? error.message : "알 수 없는 오류", }); } } @@ -174,27 +205,27 @@ export class BatchManagementController { static async getBatchConfigs(req: AuthenticatedRequest, res: Response) { try { const { page = 1, limit = 10, search, isActive } = req.query; - + const filter = { page: Number(page), limit: Number(limit), search: search as string, - is_active: isActive as string + is_active: isActive as string, }; const result = await BatchService.getBatchConfigs(filter); - + res.json({ success: true, data: result.data, - pagination: result.pagination + pagination: result.pagination, }); } catch (error) { console.error("배치 설정 목록 조회 오류:", error); res.status(500).json({ success: false, message: "배치 설정 목록 조회에 실패했습니다.", - error: error instanceof Error ? error.message : "알 수 없는 오류" + error: error instanceof Error ? error.message : "알 수 없는 오류", }); } } @@ -206,20 +237,22 @@ export class BatchManagementController { static async executeBatchConfig(req: AuthenticatedRequest, res: Response) { try { const { id } = req.params; - + if (!id || isNaN(Number(id))) { return res.status(400).json({ success: false, - message: "올바른 배치 설정 ID를 제공해주세요." + message: "올바른 배치 설정 ID를 제공해주세요.", }); } // 배치 설정 조회 - const batchConfigResult = await BatchService.getBatchConfigById(Number(id)); + const batchConfigResult = await BatchService.getBatchConfigById( + Number(id) + ); if (!batchConfigResult.success || !batchConfigResult.data) { return res.status(404).json({ success: false, - message: "배치 설정을 찾을 수 없습니다." + message: "배치 설정을 찾을 수 없습니다.", }); } @@ -229,25 +262,28 @@ export class BatchManagementController { console.log(`배치 수동 실행 시작: ${batchConfig.batch_name} (ID: ${id})`); let executionLog: any = null; - + try { // 실행 로그 생성 executionLog = await BatchService.createExecutionLog({ batch_config_id: Number(id), - execution_status: 'RUNNING', + execution_status: "RUNNING", start_time: startTime, total_records: 0, success_records: 0, - failed_records: 0 + failed_records: 0, }); // BatchSchedulerService의 executeBatchConfig 메서드 사용 (중복 로직 제거) - const { BatchSchedulerService } = await import('../services/batchSchedulerService'); - const result = await BatchSchedulerService.executeBatchConfig(batchConfig); + const { BatchSchedulerService } = await import( + "../services/batchSchedulerService" + ); + const result = + await BatchSchedulerService.executeBatchConfig(batchConfig); // result가 undefined인 경우 처리 if (!result) { - throw new Error('배치 실행 결과를 받을 수 없습니다.'); + throw new Error("배치 실행 결과를 받을 수 없습니다."); } const endTime = new Date(); @@ -255,12 +291,12 @@ export class BatchManagementController { // 실행 로그 업데이트 (성공) await BatchService.updateExecutionLog(executionLog.id, { - execution_status: 'SUCCESS', + execution_status: "SUCCESS", end_time: endTime, duration_ms: duration, total_records: result.totalRecords, success_records: result.successRecords, - failed_records: result.failedRecords + failed_records: result.failedRecords, }); return res.json({ @@ -270,45 +306,49 @@ export class BatchManagementController { totalRecords: result.totalRecords, successRecords: result.successRecords, failedRecords: result.failedRecords, - executionTime: duration + executionTime: duration, }, - message: "배치가 성공적으로 실행되었습니다." + message: "배치가 성공적으로 실행되었습니다.", }); - } catch (batchError) { console.error(`배치 실행 실패: ${batchConfig.batch_name}`, batchError); - + // 실행 로그 업데이트 (실패) - executionLog가 생성되었을 경우에만 try { const endTime = new Date(); const duration = endTime.getTime() - startTime.getTime(); - + // executionLog가 정의되어 있는지 확인 - if (typeof executionLog !== 'undefined') { + if (typeof executionLog !== "undefined") { await BatchService.updateExecutionLog(executionLog.id, { - execution_status: 'FAILED', + execution_status: "FAILED", end_time: endTime, duration_ms: duration, - error_message: batchError instanceof Error ? batchError.message : "알 수 없는 오류" + error_message: + batchError instanceof Error + ? batchError.message + : "알 수 없는 오류", }); } } catch (logError) { - console.error('실행 로그 업데이트 실패:', logError); + console.error("실행 로그 업데이트 실패:", logError); } return res.status(500).json({ success: false, message: "배치 실행에 실패했습니다.", - error: batchError instanceof Error ? batchError.message : "알 수 없는 오류" + error: + batchError instanceof Error + ? batchError.message + : "알 수 없는 오류", }); } - } catch (error) { console.error(`배치 실행 오류 (ID: ${req.params.id}):`, error); return res.status(500).json({ success: false, message: "배치 실행 중 오류가 발생했습니다.", - error: error instanceof Error ? error.message : "Unknown error" + error: error instanceof Error ? error.message : "Unknown error", }); } } @@ -325,26 +365,29 @@ export class BatchManagementController { if (!id || isNaN(Number(id))) { return res.status(400).json({ success: false, - message: "올바른 배치 설정 ID를 제공해주세요." + message: "올바른 배치 설정 ID를 제공해주세요.", }); } - const batchConfig = await BatchService.updateBatchConfig(Number(id), updateData); - + const batchConfig = await BatchService.updateBatchConfig( + Number(id), + updateData + ); + // 스케줄러에서 배치 스케줄 업데이트 (즉시 실행 비활성화) await BatchSchedulerService.updateBatchSchedule(Number(id), false); - + return res.json({ success: true, data: batchConfig, - message: "배치 설정이 성공적으로 업데이트되었습니다." + message: "배치 설정이 성공적으로 업데이트되었습니다.", }); } catch (error) { console.error("배치 설정 업데이트 오류:", error); return res.status(500).json({ success: false, message: "배치 설정 업데이트에 실패했습니다.", - error: error instanceof Error ? error.message : "알 수 없는 오류" + error: error instanceof Error ? error.message : "알 수 없는 오류", }); } } @@ -354,21 +397,21 @@ export class BatchManagementController { */ static async previewRestApiData(req: AuthenticatedRequest, res: Response) { try { - const { - apiUrl, - apiKey, - endpoint, - method = 'GET', + const { + apiUrl, + apiKey, + endpoint, + method = "GET", paramType, paramName, paramValue, - paramSource + paramSource, } = req.body; if (!apiUrl || !apiKey || !endpoint) { return res.status(400).json({ success: false, - message: "API URL, API Key, 엔드포인트는 필수입니다." + message: "API URL, API Key, 엔드포인트는 필수입니다.", }); } @@ -378,16 +421,16 @@ export class BatchManagementController { paramType, paramName, paramValue, - paramSource + paramSource, }); // RestApiConnector 사용하여 데이터 조회 - const { RestApiConnector } = await import('../database/RestApiConnector'); - + const { RestApiConnector } = await import("../database/RestApiConnector"); + const connector = new RestApiConnector({ baseUrl: apiUrl, apiKey: apiKey, - timeout: 30000 + timeout: 30000, }); // 연결 테스트 @@ -396,7 +439,7 @@ export class BatchManagementController { // 파라미터가 있는 경우 엔드포인트 수정 let finalEndpoint = endpoint; if (paramType && paramName && paramValue) { - if (paramType === 'url') { + if (paramType === "url") { // URL 파라미터: /api/users/{userId} → /api/users/123 if (endpoint.includes(`{${paramName}}`)) { finalEndpoint = endpoint.replace(`{${paramName}}`, paramValue); @@ -404,9 +447,9 @@ export class BatchManagementController { // 엔드포인트에 {paramName}이 없으면 뒤에 추가 finalEndpoint = `${endpoint}/${paramValue}`; } - } else if (paramType === 'query') { + } else if (paramType === "query") { // 쿼리 파라미터: /api/users?userId=123 - const separator = endpoint.includes('?') ? '&' : '?'; + const separator = endpoint.includes("?") ? "&" : "?"; finalEndpoint = `${endpoint}${separator}${paramName}=${paramValue}`; } } @@ -417,10 +460,11 @@ export class BatchManagementController { const result = await connector.executeQuery(finalEndpoint, method); console.log(`[previewRestApiData] executeQuery 결과:`, { rowCount: result.rowCount, - rowsLength: result.rows ? result.rows.length : 'undefined', - firstRow: result.rows && result.rows.length > 0 ? result.rows[0] : 'no data' + rowsLength: result.rows ? result.rows.length : "undefined", + firstRow: + result.rows && result.rows.length > 0 ? result.rows[0] : "no data", }); - + const data = result.rows.slice(0, 5); // 최대 5개 샘플만 console.log(`[previewRestApiData] 슬라이스된 데이터:`, data); @@ -428,15 +472,15 @@ export class BatchManagementController { // 첫 번째 객체에서 필드명 추출 const fields = Object.keys(data[0]); console.log(`[previewRestApiData] 추출된 필드:`, fields); - + return res.json({ success: true, data: { fields: fields, samples: data, - totalCount: result.rowCount || data.length + totalCount: result.rowCount || data.length, }, - message: `${fields.length}개 필드, ${result.rowCount || data.length}개 레코드를 조회했습니다.` + message: `${fields.length}개 필드, ${result.rowCount || data.length}개 레코드를 조회했습니다.`, }); } else { return res.json({ @@ -444,9 +488,9 @@ export class BatchManagementController { data: { fields: [], samples: [], - totalCount: 0 + totalCount: 0, }, - message: "API에서 데이터를 가져올 수 없습니다." + message: "API에서 데이터를 가져올 수 없습니다.", }); } } catch (error) { @@ -454,7 +498,7 @@ export class BatchManagementController { return res.status(500).json({ success: false, message: "REST API 데이터 미리보기 중 오류가 발생했습니다.", - error: error instanceof Error ? error.message : "알 수 없는 오류" + error: error instanceof Error ? error.message : "알 수 없는 오류", }); } } @@ -464,18 +508,19 @@ export class BatchManagementController { */ static async saveRestApiBatch(req: AuthenticatedRequest, res: Response) { try { - const { - batchName, - batchType, - cronSchedule, - description, - apiMappings - } = req.body; + const { batchName, batchType, cronSchedule, description, apiMappings } = + req.body; - if (!batchName || !batchType || !cronSchedule || !apiMappings || apiMappings.length === 0) { + if ( + !batchName || + !batchType || + !cronSchedule || + !apiMappings || + apiMappings.length === 0 + ) { return res.status(400).json({ success: false, - message: "필수 필드가 누락되었습니다." + message: "필수 필드가 누락되었습니다.", }); } @@ -484,15 +529,15 @@ export class BatchManagementController { batchType, cronSchedule, description, - apiMappings + apiMappings, }); // BatchService를 사용하여 배치 설정 저장 const batchConfig: CreateBatchConfigRequest = { batchName: batchName, - description: description || '', + description: description || "", cronSchedule: cronSchedule, - mappings: apiMappings + mappings: apiMappings, }; const result = await BatchService.createBatchConfig(batchConfig); @@ -501,7 +546,9 @@ export class BatchManagementController { // 스케줄러에 자동 등록 ✅ try { await BatchSchedulerService.scheduleBatchConfig(result.data); - console.log(`✅ 새로운 배치가 스케줄러에 등록되었습니다: ${batchName} (ID: ${result.data.id})`); + console.log( + `✅ 새로운 배치가 스케줄러에 등록되었습니다: ${batchName} (ID: ${result.data.id})` + ); } catch (schedulerError) { console.error(`❌ 스케줄러 등록 실패: ${batchName}`, schedulerError); // 스케줄러 등록 실패해도 배치 저장은 성공으로 처리 @@ -510,19 +557,19 @@ export class BatchManagementController { return res.json({ success: true, message: "REST API 배치가 성공적으로 저장되었습니다.", - data: result.data + data: result.data, }); } else { return res.status(500).json({ success: false, - message: result.message || "배치 저장에 실패했습니다." + message: result.message || "배치 저장에 실패했습니다.", }); } } catch (error) { console.error("REST API 배치 저장 오류:", error); return res.status(500).json({ success: false, - message: "배치 저장 중 오류가 발생했습니다." + message: "배치 저장 중 오류가 발생했습니다.", }); } } diff --git a/backend-node/src/controllers/commonCodeController.ts b/backend-node/src/controllers/commonCodeController.ts index f31e55e1..616e0c6c 100644 --- a/backend-node/src/controllers/commonCodeController.ts +++ b/backend-node/src/controllers/commonCodeController.ts @@ -461,12 +461,13 @@ export class CommonCodeController { } /** - * 카테고리 중복 검사 + * 카테고리 중복 검사 (회사별) * GET /api/common-codes/categories/check-duplicate?field=categoryCode&value=USER_STATUS&excludeCode=OLD_CODE */ async checkCategoryDuplicate(req: AuthenticatedRequest, res: Response) { try { const { field, value, excludeCode } = req.query; + const userCompanyCode = req.user?.companyCode; // 입력값 검증 if (!field || !value) { @@ -488,7 +489,8 @@ export class CommonCodeController { const result = await this.commonCodeService.checkCategoryDuplicate( field as "categoryCode" | "categoryName" | "categoryNameEng", value as string, - excludeCode as string + excludeCode as string, + userCompanyCode ); return res.json({ @@ -511,13 +513,14 @@ export class CommonCodeController { } /** - * 코드 중복 검사 + * 코드 중복 검사 (회사별) * GET /api/common-codes/categories/:categoryCode/codes/check-duplicate?field=codeValue&value=ACTIVE&excludeCode=OLD_CODE */ async checkCodeDuplicate(req: AuthenticatedRequest, res: Response) { try { const { categoryCode } = req.params; const { field, value, excludeCode } = req.query; + const userCompanyCode = req.user?.companyCode; // 입력값 검증 if (!field || !value) { @@ -540,7 +543,8 @@ export class CommonCodeController { categoryCode, field as "codeValue" | "codeName" | "codeNameEng", value as string, - excludeCode as string + excludeCode as string, + userCompanyCode ); return res.json({ diff --git a/backend-node/src/controllers/flowController.ts b/backend-node/src/controllers/flowController.ts index 16668b24..85ad2259 100644 --- a/backend-node/src/controllers/flowController.ts +++ b/backend-node/src/controllers/flowController.ts @@ -557,9 +557,14 @@ export class FlowController { getStepColumnLabels = async (req: Request, res: Response): Promise => { try { const { flowId, stepId } = req.params; + console.log("🏷️ [FlowController] 컬럼 라벨 조회 요청:", { + flowId, + stepId, + }); - const step = await this.flowStepService.getById(parseInt(stepId)); + const step = await this.flowStepService.findById(parseInt(stepId)); if (!step) { + console.warn("⚠️ [FlowController] 스텝을 찾을 수 없음:", stepId); res.status(404).json({ success: false, message: "Step not found", @@ -567,10 +572,11 @@ export class FlowController { return; } - const flowDef = await this.flowDefinitionService.getById( + const flowDef = await this.flowDefinitionService.findById( parseInt(flowId) ); if (!flowDef) { + console.warn("⚠️ [FlowController] 플로우를 찾을 수 없음:", flowId); res.status(404).json({ success: false, message: "Flow definition not found", @@ -580,7 +586,14 @@ export class FlowController { // 테이블명 결정 (스텝 테이블 우선, 없으면 플로우 테이블) const tableName = step.tableName || flowDef.tableName; + console.log("📋 [FlowController] 테이블명 결정:", { + stepTableName: step.tableName, + flowTableName: flowDef.tableName, + selectedTableName: tableName, + }); + if (!tableName) { + console.warn("⚠️ [FlowController] 테이블명이 지정되지 않음"); res.json({ success: true, data: {}, @@ -589,7 +602,7 @@ export class FlowController { } // column_labels 테이블에서 라벨 정보 조회 - const { query } = await import("../config/database"); + const { query } = await import("../database/db"); const labelRows = await query<{ column_name: string; column_label: string | null; @@ -600,6 +613,15 @@ export class FlowController { [tableName] ); + console.log(`✅ [FlowController] column_labels 조회 완료:`, { + tableName, + rowCount: labelRows.length, + labels: labelRows.map((r) => ({ + col: r.column_name, + label: r.column_label, + })), + }); + // { columnName: label } 형태의 객체로 변환 const labels: Record = {}; labelRows.forEach((row) => { @@ -608,12 +630,14 @@ export class FlowController { } }); + console.log("📦 [FlowController] 반환할 라벨 객체:", labels); + res.json({ success: true, data: labels, }); } catch (error: any) { - console.error("Error getting step column labels:", error); + console.error("❌ [FlowController] 컬럼 라벨 조회 오류:", error); res.status(500).json({ success: false, message: error.message || "Failed to get step column labels", diff --git a/backend-node/src/routes/externalDbConnectionRoutes.ts b/backend-node/src/routes/externalDbConnectionRoutes.ts index 5ad87dab..c116a74d 100644 --- a/backend-node/src/routes/externalDbConnectionRoutes.ts +++ b/backend-node/src/routes/externalDbConnectionRoutes.ts @@ -87,7 +87,10 @@ router.get( filter, }); - const result = await ExternalDbConnectionService.getConnections(filter); + const result = await ExternalDbConnectionService.getConnections( + filter, + userCompanyCode + ); if (result.success) { return res.status(200).json(result); @@ -319,7 +322,12 @@ router.delete( }); } - const result = await ExternalDbConnectionService.deleteConnection(id); + const userCompanyCode = req.user?.companyCode; + + const result = await ExternalDbConnectionService.deleteConnection( + id, + userCompanyCode + ); if (result.success) { return res.status(200).json(result); @@ -517,7 +525,10 @@ router.get( }); const externalConnections = - await ExternalDbConnectionService.getConnections(filter); + await ExternalDbConnectionService.getConnections( + filter, + userCompanyCode + ); if (!externalConnections.success) { return res.status(400).json(externalConnections); diff --git a/backend-node/src/routes/externalRestApiConnectionRoutes.ts b/backend-node/src/routes/externalRestApiConnectionRoutes.ts index 0e2de684..9f577e52 100644 --- a/backend-node/src/routes/externalRestApiConnectionRoutes.ts +++ b/backend-node/src/routes/externalRestApiConnectionRoutes.ts @@ -29,8 +29,12 @@ router.get( company_code: req.query.company_code as string, }; - const result = - await ExternalRestApiConnectionService.getConnections(filter); + const userCompanyCode = req.user?.companyCode; + + const result = await ExternalRestApiConnectionService.getConnections( + filter, + userCompanyCode + ); return res.status(result.success ? 200 : 400).json(result); } catch (error) { @@ -62,8 +66,12 @@ router.get( }); } - const result = - await ExternalRestApiConnectionService.getConnectionById(id); + const userCompanyCode = req.user?.companyCode; + + const result = await ExternalRestApiConnectionService.getConnectionById( + id, + userCompanyCode + ); return res.status(result.success ? 200 : 404).json(result); } catch (error) { @@ -129,9 +137,12 @@ router.put( updated_by: req.user?.userId || "system", }; + const userCompanyCode = req.user?.companyCode; + const result = await ExternalRestApiConnectionService.updateConnection( id, - data + data, + userCompanyCode ); return res.status(result.success ? 200 : 400).json(result); @@ -164,8 +175,12 @@ router.delete( }); } - const result = - await ExternalRestApiConnectionService.deleteConnection(id); + const userCompanyCode = req.user?.companyCode; + + const result = await ExternalRestApiConnectionService.deleteConnection( + id, + userCompanyCode + ); return res.status(result.success ? 200 : 404).json(result); } catch (error) { diff --git a/backend-node/src/services/batchExecutionLogService.ts b/backend-node/src/services/batchExecutionLogService.ts index c134b0db..f2fc583c 100644 --- a/backend-node/src/services/batchExecutionLogService.ts +++ b/backend-node/src/services/batchExecutionLogService.ts @@ -13,10 +13,11 @@ import { ApiResponse } from "../types/batchTypes"; export class BatchExecutionLogService { /** - * 배치 실행 로그 목록 조회 + * 배치 실행 로그 목록 조회 (회사별) */ static async getExecutionLogs( - filter: BatchExecutionLogFilter = {} + filter: BatchExecutionLogFilter = {}, + userCompanyCode?: string ): Promise> { try { const { @@ -36,6 +37,12 @@ export class BatchExecutionLogService { const params: any[] = []; let paramIndex = 1; + // 회사별 필터링 (최고 관리자가 아닌 경우) + if (userCompanyCode && userCompanyCode !== "*") { + whereConditions.push(`bc.company_code = $${paramIndex++}`); + params.push(userCompanyCode); + } + if (batch_config_id) { whereConditions.push(`bel.batch_config_id = $${paramIndex++}`); params.push(batch_config_id); diff --git a/backend-node/src/services/batchManagementService.ts b/backend-node/src/services/batchManagementService.ts index 6bb452da..72ff52c4 100644 --- a/backend-node/src/services/batchManagementService.ts +++ b/backend-node/src/services/batchManagementService.ts @@ -35,11 +35,11 @@ export interface BatchApiResponse { export class BatchManagementService { /** - * 배치관리용 연결 목록 조회 + * 배치관리용 연결 목록 조회 (회사별) */ - static async getAvailableConnections(): Promise< - BatchApiResponse - > { + static async getAvailableConnections( + userCompanyCode?: string + ): Promise> { try { const connections: BatchConnectionInfo[] = []; @@ -50,19 +50,27 @@ export class BatchManagementService { db_type: "postgresql", }); - // 활성화된 외부 DB 연결 조회 + // 활성화된 외부 DB 연결 조회 (회사별 필터링) + let query_sql = `SELECT id, connection_name, db_type, description + FROM external_db_connections + WHERE is_active = 'Y'`; + + const params: any[] = []; + + // 회사별 필터링 (최고 관리자가 아닌 경우) + if (userCompanyCode && userCompanyCode !== "*") { + query_sql += ` AND company_code = $1`; + params.push(userCompanyCode); + } + + query_sql += ` ORDER BY connection_name ASC`; + const externalConnections = await query<{ id: number; connection_name: string; db_type: string; description: string; - }>( - `SELECT id, connection_name, db_type, description - FROM external_db_connections - WHERE is_active = 'Y' - ORDER BY connection_name ASC`, - [] - ); + }>(query_sql, params); // 외부 DB 연결 추가 externalConnections.forEach((conn) => { @@ -90,11 +98,12 @@ export class BatchManagementService { } /** - * 배치관리용 테이블 목록 조회 + * 배치관리용 테이블 목록 조회 (회사별) */ static async getTablesFromConnection( connectionType: "internal" | "external", - connectionId?: number + connectionId?: number, + userCompanyCode?: string ): Promise> { try { let tables: BatchTableInfo[] = []; @@ -115,8 +124,11 @@ export class BatchManagementService { columns: [], })); } else if (connectionType === "external" && connectionId) { - // 외부 DB 테이블 조회 - const tablesResult = await this.getExternalTables(connectionId); + // 외부 DB 테이블 조회 (회사별 필터링) + const tablesResult = await this.getExternalTables( + connectionId, + userCompanyCode + ); if (tablesResult.success && tablesResult.data) { tables = tablesResult.data; } @@ -138,12 +150,13 @@ export class BatchManagementService { } /** - * 배치관리용 테이블 컬럼 정보 조회 + * 배치관리용 테이블 컬럼 정보 조회 (회사별) */ static async getTableColumns( connectionType: "internal" | "external", connectionId: number | undefined, - tableName: string + tableName: string, + userCompanyCode?: string ): Promise> { try { console.log(`[BatchManagementService] getTableColumns 호출:`, { @@ -189,14 +202,15 @@ export class BatchManagementService { column_default: row.column_default, })); } else if (connectionType === "external" && connectionId) { - // 외부 DB 컬럼 조회 + // 외부 DB 컬럼 조회 (회사별 필터링) console.log( `[BatchManagementService] 외부 DB 컬럼 조회 시작: connectionId=${connectionId}, tableName=${tableName}` ); const columnsResult = await this.getExternalTableColumns( connectionId, - tableName + tableName, + userCompanyCode ); console.log( @@ -226,22 +240,29 @@ export class BatchManagementService { } /** - * 외부 DB 테이블 목록 조회 (내부 구현) + * 외부 DB 테이블 목록 조회 (내부 구현, 회사별) */ private static async getExternalTables( - connectionId: number + connectionId: number, + userCompanyCode?: string ): Promise> { try { - // 연결 정보 조회 - const connection = await queryOne( - `SELECT * FROM external_db_connections WHERE id = $1`, - [connectionId] - ); + // 연결 정보 조회 (회사별 필터링) + let query_sql = `SELECT * FROM external_db_connections WHERE id = $1`; + const params: any[] = [connectionId]; + + // 회사별 필터링 (최고 관리자가 아닌 경우) + if (userCompanyCode && userCompanyCode !== "*") { + query_sql += ` AND company_code = $2`; + params.push(userCompanyCode); + } + + const connection = await queryOne(query_sql, params); if (!connection) { return { success: false, - message: "연결 정보를 찾을 수 없습니다.", + message: "연결 정보를 찾을 수 없거나 권한이 없습니다.", }; } @@ -299,26 +320,33 @@ export class BatchManagementService { } /** - * 외부 DB 테이블 컬럼 정보 조회 (내부 구현) + * 외부 DB 테이블 컬럼 정보 조회 (내부 구현, 회사별) */ private static async getExternalTableColumns( connectionId: number, - tableName: string + tableName: string, + userCompanyCode?: string ): Promise> { try { console.log( `[BatchManagementService] getExternalTableColumns 호출: connectionId=${connectionId}, tableName=${tableName}` ); - // 연결 정보 조회 - const connection = await queryOne( - `SELECT * FROM external_db_connections WHERE id = $1`, - [connectionId] - ); + // 연결 정보 조회 (회사별 필터링) + let query_sql = `SELECT * FROM external_db_connections WHERE id = $1`; + const params: any[] = [connectionId]; + + // 회사별 필터링 (최고 관리자가 아닌 경우) + if (userCompanyCode && userCompanyCode !== "*") { + query_sql += ` AND company_code = $2`; + params.push(userCompanyCode); + } + + const connection = await queryOne(query_sql, params); if (!connection) { console.log( - `[BatchManagementService] 연결 정보를 찾을 수 없음: connectionId=${connectionId}` + `[BatchManagementService] 연결 정보를 찾을 수 없거나 권한이 없음: connectionId=${connectionId}` ); return { success: false, diff --git a/backend-node/src/services/batchService.ts b/backend-node/src/services/batchService.ts index 31368be9..07cdd61c 100644 --- a/backend-node/src/services/batchService.ts +++ b/backend-node/src/services/batchService.ts @@ -20,27 +20,33 @@ import { DbConnectionManager } from "./dbConnectionManager"; export class BatchService { /** - * 배치 설정 목록 조회 + * 배치 설정 목록 조회 (회사별) */ static async getBatchConfigs( - filter: BatchConfigFilter + filter: BatchConfigFilter, + userCompanyCode?: string ): Promise> { try { const whereConditions: string[] = []; const values: any[] = []; let paramIndex = 1; + // 회사별 필터링 (최고 관리자가 아닌 경우 필수) + if (userCompanyCode && userCompanyCode !== "*") { + whereConditions.push(`bc.company_code = $${paramIndex++}`); + values.push(userCompanyCode); + } else if (userCompanyCode === "*" && filter.company_code) { + // 최고 관리자: 필터가 있으면 적용 + whereConditions.push(`bc.company_code = $${paramIndex++}`); + values.push(filter.company_code); + } + // 필터 조건 적용 if (filter.is_active) { whereConditions.push(`bc.is_active = $${paramIndex++}`); values.push(filter.is_active); } - if (filter.company_code) { - whereConditions.push(`bc.company_code = $${paramIndex++}`); - values.push(filter.company_code); - } - // 검색 조건 적용 (OR) if (filter.search && filter.search.trim()) { whereConditions.push( @@ -122,14 +128,14 @@ export class BatchService { } /** - * 특정 배치 설정 조회 + * 특정 배치 설정 조회 (회사별) */ static async getBatchConfigById( - id: number + id: number, + userCompanyCode?: string ): Promise> { try { - const batchConfig = await queryOne( - `SELECT bc.id, bc.batch_name, bc.description, bc.cron_schedule, + let query = `SELECT bc.id, bc.batch_name, bc.description, bc.cron_schedule, bc.is_active, bc.company_code, bc.created_date, bc.created_by, bc.updated_date, bc.updated_by, COALESCE( @@ -155,15 +161,25 @@ export class BatchService { ) as batch_mappings FROM batch_configs bc LEFT JOIN batch_mappings bm ON bc.id = bm.batch_config_id - WHERE bc.id = $1 - GROUP BY bc.id`, - [id] - ); + WHERE bc.id = $1`; + + const params: any[] = [id]; + let paramIndex = 2; + + // 회사별 필터링 (최고 관리자가 아닌 경우) + if (userCompanyCode && userCompanyCode !== "*") { + query += ` AND bc.company_code = $${paramIndex}`; + params.push(userCompanyCode); + } + + query += ` GROUP BY bc.id`; + + const batchConfig = await queryOne(query, params); if (!batchConfig) { return { success: false, - message: "배치 설정을 찾을 수 없습니다.", + message: "배치 설정을 찾을 수 없거나 권한이 없습니다.", }; } @@ -267,15 +283,21 @@ export class BatchService { } /** - * 배치 설정 수정 + * 배치 설정 수정 (회사별) */ static async updateBatchConfig( id: number, data: UpdateBatchConfigRequest, - userId?: string + userId?: string, + userCompanyCode?: string ): Promise> { try { - // 기존 배치 설정 확인 + // 기존 배치 설정 확인 (회사 권한 체크 포함) + const existing = await this.getBatchConfigById(id, userCompanyCode); + if (!existing.success) { + return existing; + } + const existingConfig = await queryOne( `SELECT bc.*, COALESCE( @@ -416,13 +438,20 @@ export class BatchService { } /** - * 배치 설정 삭제 (논리 삭제) + * 배치 설정 삭제 (논리 삭제, 회사별) */ static async deleteBatchConfig( id: number, - userId?: string + userId?: string, + userCompanyCode?: string ): Promise> { try { + // 기존 배치 설정 확인 (회사 권한 체크 포함) + const existing = await this.getBatchConfigById(id, userCompanyCode); + if (!existing.success) { + return existing as ApiResponse; + } + const existingConfig = await queryOne( `SELECT * FROM batch_configs WHERE id = $1`, [id] diff --git a/backend-node/src/services/commonCodeService.ts b/backend-node/src/services/commonCodeService.ts index a823532d..8c02a60d 100644 --- a/backend-node/src/services/commonCodeService.ts +++ b/backend-node/src/services/commonCodeService.ts @@ -604,12 +604,13 @@ export class CommonCodeService { } /** - * 카테고리 중복 검사 + * 카테고리 중복 검사 (회사별) */ async checkCategoryDuplicate( field: "categoryCode" | "categoryName" | "categoryNameEng", value: string, - excludeCategoryCode?: string + excludeCategoryCode?: string, + userCompanyCode?: string ): Promise<{ isDuplicate: boolean; message: string }> { try { if (!value || !value.trim()) { @@ -655,6 +656,12 @@ export class CommonCodeService { break; } + // 회사별 필터링 (최고 관리자가 아닌 경우) + if (userCompanyCode && userCompanyCode !== "*") { + sql += ` AND company_code = $${paramIndex++}`; + values.push(userCompanyCode); + } + // 수정 시 자기 자신 제외 if (excludeCategoryCode) { sql += ` AND category_code != $${paramIndex++}`; @@ -675,6 +682,10 @@ export class CommonCodeService { categoryNameEng: "카테고리 영문명", }; + logger.info( + `카테고리 중복 검사: ${field}=${value}, 회사=${userCompanyCode}, 중복=${isDuplicate}` + ); + return { isDuplicate, message: isDuplicate @@ -688,13 +699,14 @@ export class CommonCodeService { } /** - * 코드 중복 검사 + * 코드 중복 검사 (회사별) */ async checkCodeDuplicate( categoryCode: string, field: "codeValue" | "codeName" | "codeNameEng", value: string, - excludeCodeValue?: string + excludeCodeValue?: string, + userCompanyCode?: string ): Promise<{ isDuplicate: boolean; message: string }> { try { if (!value || !value.trim()) { @@ -743,6 +755,12 @@ export class CommonCodeService { break; } + // 회사별 필터링 (최고 관리자가 아닌 경우) + if (userCompanyCode && userCompanyCode !== "*") { + sql += ` AND company_code = $${paramIndex++}`; + values.push(userCompanyCode); + } + // 수정 시 자기 자신 제외 if (excludeCodeValue) { sql += ` AND code_value != $${paramIndex++}`; @@ -760,6 +778,10 @@ export class CommonCodeService { codeNameEng: "코드 영문명", }; + logger.info( + `코드 중복 검사: ${categoryCode}.${field}=${value}, 회사=${userCompanyCode}, 중복=${isDuplicate}` + ); + return { isDuplicate, message: isDuplicate diff --git a/backend-node/src/services/externalDbConnectionService.ts b/backend-node/src/services/externalDbConnectionService.ts index d25aa64b..99164ae1 100644 --- a/backend-node/src/services/externalDbConnectionService.ts +++ b/backend-node/src/services/externalDbConnectionService.ts @@ -17,7 +17,8 @@ export class ExternalDbConnectionService { * 외부 DB 연결 목록 조회 */ static async getConnections( - filter: ExternalDbConnectionFilter + filter: ExternalDbConnectionFilter, + userCompanyCode?: string ): Promise> { try { // WHERE 조건 동적 생성 @@ -25,6 +26,26 @@ export class ExternalDbConnectionService { const params: any[] = []; let paramIndex = 1; + // 회사별 필터링 (최고 관리자가 아닌 경우 필수) + if (userCompanyCode && userCompanyCode !== "*") { + whereConditions.push(`company_code = $${paramIndex++}`); + params.push(userCompanyCode); + logger.info(`회사별 외부 DB 연결 필터링: ${userCompanyCode}`); + } else if (userCompanyCode === "*") { + logger.info(`최고 관리자: 모든 외부 DB 연결 조회`); + // 필터가 있으면 적용 + if (filter.company_code) { + whereConditions.push(`company_code = $${paramIndex++}`); + params.push(filter.company_code); + } + } else { + // userCompanyCode가 없는 경우 (하위 호환성) + if (filter.company_code) { + whereConditions.push(`company_code = $${paramIndex++}`); + params.push(filter.company_code); + } + } + // 필터 조건 적용 if (filter.db_type) { whereConditions.push(`db_type = $${paramIndex++}`); @@ -36,11 +57,6 @@ export class ExternalDbConnectionService { params.push(filter.is_active); } - if (filter.company_code) { - whereConditions.push(`company_code = $${paramIndex++}`); - params.push(filter.company_code); - } - // 검색 조건 적용 (연결명 또는 설명에서 검색) if (filter.search && filter.search.trim()) { whereConditions.push( @@ -496,23 +512,36 @@ export class ExternalDbConnectionService { /** * 외부 DB 연결 삭제 (물리 삭제) */ - static async deleteConnection(id: number): Promise> { + static async deleteConnection( + id: number, + userCompanyCode?: string + ): Promise> { try { - const existingConnection = await queryOne( - `SELECT id FROM external_db_connections WHERE id = $1`, - [id] - ); + let selectQuery = `SELECT id FROM external_db_connections WHERE id = $1`; + const selectParams: any[] = [id]; + + // 회사별 필터링 (최고 관리자가 아닌 경우) + if (userCompanyCode && userCompanyCode !== "*") { + selectQuery += ` AND company_code = $2`; + selectParams.push(userCompanyCode); + } + + const existingConnection = await queryOne(selectQuery, selectParams); if (!existingConnection) { return { success: false, - message: "해당 연결 설정을 찾을 수 없습니다.", + message: "해당 연결 설정을 찾을 수 없거나 권한이 없습니다.", }; } // 물리 삭제 (실제 데이터 삭제) await query(`DELETE FROM external_db_connections WHERE id = $1`, [id]); + logger.info( + `외부 DB 연결 삭제: ID ${id} (회사: ${userCompanyCode || "전체"})` + ); + return { success: true, message: "연결 설정이 삭제되었습니다.", @@ -747,8 +776,11 @@ export class ExternalDbConnectionService { try { // 보안 검증: SELECT 쿼리만 허용 const trimmedQuery = query.trim().toUpperCase(); - if (!trimmedQuery.startsWith('SELECT')) { - console.log("보안 오류: SELECT가 아닌 쿼리 시도:", { id, query: query.substring(0, 100) }); + if (!trimmedQuery.startsWith("SELECT")) { + console.log("보안 오류: SELECT가 아닌 쿼리 시도:", { + id, + query: query.substring(0, 100), + }); return { success: false, message: "외부 데이터베이스에서는 SELECT 쿼리만 실행할 수 있습니다.", @@ -756,16 +788,32 @@ export class ExternalDbConnectionService { } // 위험한 키워드 검사 - const dangerousKeywords = ['INSERT', 'UPDATE', 'DELETE', 'DROP', 'CREATE', 'ALTER', 'TRUNCATE', 'EXEC', 'EXECUTE', 'CALL', 'MERGE']; - const hasDangerousKeyword = dangerousKeywords.some(keyword => + const dangerousKeywords = [ + "INSERT", + "UPDATE", + "DELETE", + "DROP", + "CREATE", + "ALTER", + "TRUNCATE", + "EXEC", + "EXECUTE", + "CALL", + "MERGE", + ]; + const hasDangerousKeyword = dangerousKeywords.some((keyword) => trimmedQuery.includes(keyword) ); - + if (hasDangerousKeyword) { - console.log("보안 오류: 위험한 키워드 포함 쿼리 시도:", { id, query: query.substring(0, 100) }); + console.log("보안 오류: 위험한 키워드 포함 쿼리 시도:", { + id, + query: query.substring(0, 100), + }); return { success: false, - message: "데이터를 변경하거나 삭제하는 쿼리는 허용되지 않습니다. SELECT 쿼리만 사용해주세요.", + message: + "데이터를 변경하거나 삭제하는 쿼리는 허용되지 않습니다. SELECT 쿼리만 사용해주세요.", }; } diff --git a/backend-node/src/services/externalRestApiConnectionService.ts b/backend-node/src/services/externalRestApiConnectionService.ts index 4d0539b4..e5189530 100644 --- a/backend-node/src/services/externalRestApiConnectionService.ts +++ b/backend-node/src/services/externalRestApiConnectionService.ts @@ -23,7 +23,8 @@ export class ExternalRestApiConnectionService { * REST API 연결 목록 조회 */ static async getConnections( - filter: ExternalRestApiConnectionFilter = {} + filter: ExternalRestApiConnectionFilter = {}, + userCompanyCode?: string ): Promise> { try { let query = ` @@ -39,11 +40,27 @@ export class ExternalRestApiConnectionService { const params: any[] = []; let paramIndex = 1; - // 회사 코드 필터 - if (filter.company_code) { + // 회사별 필터링 (최고 관리자가 아닌 경우 필수) + if (userCompanyCode && userCompanyCode !== "*") { query += ` AND company_code = $${paramIndex}`; - params.push(filter.company_code); + params.push(userCompanyCode); paramIndex++; + logger.info(`회사별 REST API 연결 필터링: ${userCompanyCode}`); + } else if (userCompanyCode === "*") { + logger.info(`최고 관리자: 모든 REST API 연결 조회`); + // 필터가 있으면 적용 + if (filter.company_code) { + query += ` AND company_code = $${paramIndex}`; + params.push(filter.company_code); + paramIndex++; + } + } else { + // userCompanyCode가 없는 경우 (하위 호환성) + if (filter.company_code) { + query += ` AND company_code = $${paramIndex}`; + params.push(filter.company_code); + paramIndex++; + } } // 활성 상태 필터 @@ -105,10 +122,11 @@ export class ExternalRestApiConnectionService { * REST API 연결 상세 조회 */ static async getConnectionById( - id: number + id: number, + userCompanyCode?: string ): Promise> { try { - const query = ` + let query = ` SELECT id, connection_name, description, base_url, default_headers, auth_type, auth_config, timeout, retry_count, retry_delay, @@ -118,12 +136,20 @@ export class ExternalRestApiConnectionService { WHERE id = $1 `; - const result: QueryResult = await pool.query(query, [id]); + const params: any[] = [id]; + + // 회사별 필터링 (최고 관리자가 아닌 경우) + if (userCompanyCode && userCompanyCode !== "*") { + query += ` AND company_code = $2`; + params.push(userCompanyCode); + } + + const result: QueryResult = await pool.query(query, params); if (result.rows.length === 0) { return { success: false, - message: "연결을 찾을 수 없습니다.", + message: "연결을 찾을 수 없거나 권한이 없습니다.", }; } @@ -225,11 +251,12 @@ export class ExternalRestApiConnectionService { */ static async updateConnection( id: number, - data: Partial + data: Partial, + userCompanyCode?: string ): Promise> { try { - // 기존 연결 확인 - const existing = await this.getConnectionById(id); + // 기존 연결 확인 (회사 코드로 권한 체크) + const existing = await this.getConnectionById(id, userCompanyCode); if (!existing.success) { return existing; } @@ -353,24 +380,38 @@ export class ExternalRestApiConnectionService { /** * REST API 연결 삭제 */ - static async deleteConnection(id: number): Promise> { + static async deleteConnection( + id: number, + userCompanyCode?: string + ): Promise> { try { - const query = ` + let query = ` DELETE FROM external_rest_api_connections WHERE id = $1 - RETURNING connection_name `; - const result: QueryResult = await pool.query(query, [id]); + const params: any[] = [id]; + + // 회사별 필터링 (최고 관리자가 아닌 경우) + if (userCompanyCode && userCompanyCode !== "*") { + query += ` AND company_code = $2`; + params.push(userCompanyCode); + } + + query += ` RETURNING connection_name`; + + const result: QueryResult = await pool.query(query, params); if (result.rows.length === 0) { return { success: false, - message: "연결을 찾을 수 없습니다.", + message: "연결을 찾을 수 없거나 권한이 없습니다.", }; } - logger.info(`REST API 연결 삭제 성공: ${result.rows[0].connection_name}`); + logger.info( + `REST API 연결 삭제 성공: ${result.rows[0].connection_name} (회사: ${userCompanyCode || "전체"})` + ); return { success: true, diff --git a/frontend/components/screen/widgets/FlowWidget.tsx b/frontend/components/screen/widgets/FlowWidget.tsx index 69900000..7b10e071 100644 --- a/frontend/components/screen/widgets/FlowWidget.tsx +++ b/frontend/components/screen/widgets/FlowWidget.tsx @@ -127,6 +127,12 @@ export function FlowWidget({ // 컬럼 라벨 조회 const labelsResponse = await getStepColumnLabels(flowId, selectedStepId); + console.log("🔄 새로고침 시 컬럼 라벨 조회:", { + stepId: selectedStepId, + success: labelsResponse.success, + labelsCount: labelsResponse.data ? Object.keys(labelsResponse.data).length : 0, + labels: labelsResponse.data, + }); if (labelsResponse.success && labelsResponse.data) { setColumnLabels(labelsResponse.data); } @@ -220,6 +226,12 @@ export function FlowWidget({ try { // 컬럼 라벨 조회 const labelsResponse = await getStepColumnLabels(flowId!, firstStep.id); + console.log("🏷️ 첫 번째 스텝 컬럼 라벨 조회:", { + stepId: firstStep.id, + success: labelsResponse.success, + labelsCount: labelsResponse.data ? Object.keys(labelsResponse.data).length : 0, + labels: labelsResponse.data, + }); if (labelsResponse.success && labelsResponse.data) { setColumnLabels(labelsResponse.data); } @@ -297,9 +309,16 @@ export function FlowWidget({ try { // 컬럼 라벨 조회 const labelsResponse = await getStepColumnLabels(flowId!, stepId); + console.log("🏷️ 컬럼 라벨 조회 결과:", { + stepId, + success: labelsResponse.success, + labelsCount: labelsResponse.data ? Object.keys(labelsResponse.data).length : 0, + labels: labelsResponse.data, + }); if (labelsResponse.success && labelsResponse.data) { setColumnLabels(labelsResponse.data); } else { + console.warn("⚠️ 컬럼 라벨 조회 실패 또는 데이터 없음:", labelsResponse); setColumnLabels({}); }