Compare commits

..

No commits in common. "9eed3eb710f6cede55eaa348828a5d3e6522cb0d" and "63db466504c455accfdf466e0a15423b4df05547" have entirely different histories.

14 changed files with 324 additions and 681 deletions

View File

@ -4,11 +4,7 @@
import { Request, Response } from "express"; import { Request, Response } from "express";
import { BatchService } from "../services/batchService"; import { BatchService } from "../services/batchService";
import { BatchSchedulerService } from "../services/batchSchedulerService"; import { BatchSchedulerService } from "../services/batchSchedulerService";
import { import { BatchConfigFilter, CreateBatchConfigRequest, UpdateBatchConfigRequest } from "../types/batchTypes";
BatchConfigFilter,
CreateBatchConfigRequest,
UpdateBatchConfigRequest,
} from "../types/batchTypes";
export interface AuthenticatedRequest extends Request { export interface AuthenticatedRequest extends Request {
user?: { user?: {
@ -20,36 +16,32 @@ export interface AuthenticatedRequest extends Request {
export class BatchController { export class BatchController {
/** /**
* () *
* GET /api/batch-configs * GET /api/batch-configs
*/ */
static async getBatchConfigs(req: AuthenticatedRequest, res: Response) { static async getBatchConfigs(req: AuthenticatedRequest, res: Response) {
try { try {
const { page = 1, limit = 10, search, isActive } = req.query; const { page = 1, limit = 10, search, isActive } = req.query;
const userCompanyCode = req.user?.companyCode;
const filter: BatchConfigFilter = { const filter: BatchConfigFilter = {
page: Number(page), page: Number(page),
limit: Number(limit), limit: Number(limit),
search: search as string, search: search as string,
is_active: isActive as string, is_active: isActive as string
}; };
const result = await BatchService.getBatchConfigs( const result = await BatchService.getBatchConfigs(filter);
filter,
userCompanyCode
);
res.json({ res.json({
success: true, success: true,
data: result.data, data: result.data,
pagination: result.pagination, pagination: result.pagination
}); });
} catch (error) { } catch (error) {
console.error("배치 설정 목록 조회 오류:", error); console.error("배치 설정 목록 조회 오류:", error);
res.status(500).json({ res.status(500).json({
success: false, success: false,
message: "배치 설정 목록 조회에 실패했습니다.", message: "배치 설정 목록 조회에 실패했습니다."
}); });
} }
} }
@ -58,10 +50,7 @@ export class BatchController {
* *
* GET /api/batch-configs/connections * GET /api/batch-configs/connections
*/ */
static async getAvailableConnections( static async getAvailableConnections(req: AuthenticatedRequest, res: Response) {
req: AuthenticatedRequest,
res: Response
) {
try { try {
const result = await BatchService.getAvailableConnections(); const result = await BatchService.getAvailableConnections();
@ -74,7 +63,7 @@ export class BatchController {
console.error("커넥션 목록 조회 오류:", error); console.error("커넥션 목록 조회 오류:", error);
res.status(500).json({ res.status(500).json({
success: false, success: false,
message: "커넥션 목록 조회에 실패했습니다.", message: "커넥션 목록 조회에 실패했습니다."
}); });
} }
} }
@ -84,25 +73,19 @@ export class BatchController {
* GET /api/batch-configs/connections/:type/tables * GET /api/batch-configs/connections/:type/tables
* GET /api/batch-configs/connections/:type/:id/tables * GET /api/batch-configs/connections/:type/:id/tables
*/ */
static async getTablesFromConnection( static async getTablesFromConnection(req: AuthenticatedRequest, res: Response) {
req: AuthenticatedRequest,
res: Response
) {
try { try {
const { type, id } = req.params; const { type, id } = req.params;
if (!type || (type !== "internal" && type !== "external")) { if (!type || (type !== 'internal' && type !== 'external')) {
return res.status(400).json({ return res.status(400).json({
success: false, success: false,
message: "올바른 연결 타입을 지정해주세요. (internal 또는 external)", message: "올바른 연결 타입을 지정해주세요. (internal 또는 external)"
}); });
} }
const connectionId = type === "external" ? Number(id) : undefined; const connectionId = type === 'external' ? Number(id) : undefined;
const result = await BatchService.getTablesFromConnection( const result = await BatchService.getTablesFromConnection(type, connectionId);
type,
connectionId
);
if (result.success) { if (result.success) {
return res.json(result); return res.json(result);
@ -113,7 +96,7 @@ export class BatchController {
console.error("테이블 목록 조회 오류:", error); console.error("테이블 목록 조회 오류:", error);
return res.status(500).json({ return res.status(500).json({
success: false, success: false,
message: "테이블 목록 조회에 실패했습니다.", message: "테이블 목록 조회에 실패했습니다."
}); });
} }
} }
@ -130,23 +113,19 @@ export class BatchController {
if (!type || !tableName) { if (!type || !tableName) {
return res.status(400).json({ return res.status(400).json({
success: false, success: false,
message: "연결 타입과 테이블명을 모두 지정해주세요.", message: "연결 타입과 테이블명을 모두 지정해주세요."
}); });
} }
if (type !== "internal" && type !== "external") { if (type !== 'internal' && type !== 'external') {
return res.status(400).json({ return res.status(400).json({
success: false, success: false,
message: "올바른 연결 타입을 지정해주세요. (internal 또는 external)", message: "올바른 연결 타입을 지정해주세요. (internal 또는 external)"
}); });
} }
const connectionId = type === "external" ? Number(id) : undefined; const connectionId = type === 'external' ? Number(id) : undefined;
const result = await BatchService.getTableColumns( const result = await BatchService.getTableColumns(type, connectionId, tableName);
type,
connectionId,
tableName
);
if (result.success) { if (result.success) {
return res.json(result); return res.json(result);
@ -157,40 +136,36 @@ export class BatchController {
console.error("컬럼 정보 조회 오류:", error); console.error("컬럼 정보 조회 오류:", error);
return res.status(500).json({ return res.status(500).json({
success: false, success: false,
message: "컬럼 정보 조회에 실패했습니다.", message: "컬럼 정보 조회에 실패했습니다."
}); });
} }
} }
/** /**
* () *
* GET /api/batch-configs/:id * GET /api/batch-configs/:id
*/ */
static async getBatchConfigById(req: AuthenticatedRequest, res: Response) { static async getBatchConfigById(req: AuthenticatedRequest, res: Response) {
try { try {
const { id } = req.params; const { id } = req.params;
const userCompanyCode = req.user?.companyCode; const batchConfig = await BatchService.getBatchConfigById(Number(id));
const batchConfig = await BatchService.getBatchConfigById(
Number(id),
userCompanyCode
);
if (!batchConfig) { if (!batchConfig) {
return res.status(404).json({ return res.status(404).json({
success: false, success: false,
message: "배치 설정을 찾을 수 없습니다.", message: "배치 설정을 찾을 수 없습니다."
}); });
} }
return res.json({ return res.json({
success: true, success: true,
data: batchConfig, data: batchConfig
}); });
} catch (error) { } catch (error) {
console.error("배치 설정 조회 오류:", error); console.error("배치 설정 조회 오류:", error);
return res.status(500).json({ return res.status(500).json({
success: false, success: false,
message: "배치 설정 조회에 실패했습니다.", message: "배치 설정 조회에 실패했습니다."
}); });
} }
} }
@ -203,16 +178,10 @@ export class BatchController {
try { try {
const { batchName, description, cronSchedule, mappings } = req.body; const { batchName, description, cronSchedule, mappings } = req.body;
if ( if (!batchName || !cronSchedule || !mappings || !Array.isArray(mappings)) {
!batchName ||
!cronSchedule ||
!mappings ||
!Array.isArray(mappings)
) {
return res.status(400).json({ return res.status(400).json({
success: false, success: false,
message: message: "필수 필드가 누락되었습니다. (batchName, cronSchedule, mappings)"
"필수 필드가 누락되었습니다. (batchName, cronSchedule, mappings)",
}); });
} }
@ -220,71 +189,56 @@ export class BatchController {
batchName, batchName,
description, description,
cronSchedule, cronSchedule,
mappings, mappings
} as CreateBatchConfigRequest); } as CreateBatchConfigRequest);
// 생성된 배치가 활성화 상태라면 스케줄러에 등록 (즉시 실행 비활성화) // 생성된 배치가 활성화 상태라면 스케줄러에 등록 (즉시 실행 비활성화)
if ( if (batchConfig.data && batchConfig.data.is_active === 'Y' && batchConfig.data.id) {
batchConfig.data && await BatchSchedulerService.updateBatchSchedule(batchConfig.data.id, false);
batchConfig.data.is_active === "Y" &&
batchConfig.data.id
) {
await BatchSchedulerService.updateBatchSchedule(
batchConfig.data.id,
false
);
} }
return res.status(201).json({ return res.status(201).json({
success: true, success: true,
data: batchConfig, data: batchConfig,
message: "배치 설정이 성공적으로 생성되었습니다.", message: "배치 설정이 성공적으로 생성되었습니다."
}); });
} catch (error) { } catch (error) {
console.error("배치 설정 생성 오류:", error); console.error("배치 설정 생성 오류:", error);
return res.status(500).json({ return res.status(500).json({
success: false, success: false,
message: "배치 설정 생성에 실패했습니다.", message: "배치 설정 생성에 실패했습니다."
}); });
} }
} }
/** /**
* () *
* PUT /api/batch-configs/:id * PUT /api/batch-configs/:id
*/ */
static async updateBatchConfig(req: AuthenticatedRequest, res: Response) { static async updateBatchConfig(req: AuthenticatedRequest, res: Response) {
try { try {
const { id } = req.params; const { id } = req.params;
const { batchName, description, cronSchedule, mappings, isActive } = const { batchName, description, cronSchedule, mappings, isActive } = req.body;
req.body;
const userId = req.user?.userId;
const userCompanyCode = req.user?.companyCode;
if (!batchName || !cronSchedule) { if (!batchName || !cronSchedule) {
return res.status(400).json({ return res.status(400).json({
success: false, success: false,
message: "필수 필드가 누락되었습니다. (batchName, cronSchedule)", message: "필수 필드가 누락되었습니다. (batchName, cronSchedule)"
}); });
} }
const batchConfig = await BatchService.updateBatchConfig( const batchConfig = await BatchService.updateBatchConfig(Number(id), {
Number(id),
{
batchName, batchName,
description, description,
cronSchedule, cronSchedule,
mappings, mappings,
isActive, isActive
} as UpdateBatchConfigRequest, } as UpdateBatchConfigRequest);
userId,
userCompanyCode
);
if (!batchConfig) { if (!batchConfig) {
return res.status(404).json({ return res.status(404).json({
success: false, success: false,
message: "배치 설정을 찾을 수 없습니다.", message: "배치 설정을 찾을 수 없습니다."
}); });
} }
@ -294,48 +248,42 @@ export class BatchController {
return res.json({ return res.json({
success: true, success: true,
data: batchConfig, data: batchConfig,
message: "배치 설정이 성공적으로 수정되었습니다.", message: "배치 설정이 성공적으로 수정되었습니다."
}); });
} catch (error) { } catch (error) {
console.error("배치 설정 수정 오류:", error); console.error("배치 설정 수정 오류:", error);
return res.status(500).json({ return res.status(500).json({
success: false, success: false,
message: "배치 설정 수정에 실패했습니다.", message: "배치 설정 수정에 실패했습니다."
}); });
} }
} }
/** /**
* ( , ) * ( )
* DELETE /api/batch-configs/:id * DELETE /api/batch-configs/:id
*/ */
static async deleteBatchConfig(req: AuthenticatedRequest, res: Response) { static async deleteBatchConfig(req: AuthenticatedRequest, res: Response) {
try { try {
const { id } = req.params; const { id } = req.params;
const userId = req.user?.userId; const result = await BatchService.deleteBatchConfig(Number(id));
const userCompanyCode = req.user?.companyCode;
const result = await BatchService.deleteBatchConfig(
Number(id),
userId,
userCompanyCode
);
if (!result) { if (!result) {
return res.status(404).json({ return res.status(404).json({
success: false, success: false,
message: "배치 설정을 찾을 수 없습니다.", message: "배치 설정을 찾을 수 없습니다."
}); });
} }
return res.json({ return res.json({
success: true, success: true,
message: "배치 설정이 성공적으로 삭제되었습니다.", message: "배치 설정이 성공적으로 삭제되었습니다."
}); });
} catch (error) { } catch (error) {
console.error("배치 설정 삭제 오류:", error); console.error("배치 설정 삭제 오류:", error);
return res.status(500).json({ return res.status(500).json({
success: false, success: false,
message: "배치 설정 삭제에 실패했습니다.", message: "배치 설정 삭제에 실패했습니다."
}); });
} }
} }

View File

@ -4,11 +4,7 @@
import { Request, Response } from "express"; import { Request, Response } from "express";
import { AuthenticatedRequest } from "../types/auth"; import { AuthenticatedRequest } from "../types/auth";
import { BatchExecutionLogService } from "../services/batchExecutionLogService"; import { BatchExecutionLogService } from "../services/batchExecutionLogService";
import { import { BatchExecutionLogFilter, CreateBatchExecutionLogRequest, UpdateBatchExecutionLogRequest } from "../types/batchExecutionLogTypes";
BatchExecutionLogFilter,
CreateBatchExecutionLogRequest,
UpdateBatchExecutionLogRequest,
} from "../types/batchExecutionLogTypes";
export class BatchExecutionLogController { export class BatchExecutionLogController {
/** /**
@ -22,7 +18,7 @@ export class BatchExecutionLogController {
start_date, start_date,
end_date, end_date,
page, page,
limit, limit
} = req.query; } = req.query;
const filter: BatchExecutionLogFilter = { const filter: BatchExecutionLogFilter = {
@ -31,14 +27,10 @@ export class BatchExecutionLogController {
start_date: start_date ? new Date(start_date as string) : undefined, start_date: start_date ? new Date(start_date as string) : undefined,
end_date: end_date ? new Date(end_date as string) : undefined, end_date: end_date ? new Date(end_date as string) : undefined,
page: page ? Number(page) : undefined, page: page ? Number(page) : undefined,
limit: limit ? Number(limit) : undefined, limit: limit ? Number(limit) : undefined
}; };
const userCompanyCode = req.user?.companyCode; const result = await BatchExecutionLogService.getExecutionLogs(filter);
const result = await BatchExecutionLogService.getExecutionLogs(
filter,
userCompanyCode
);
if (result.success) { if (result.success) {
res.json(result); res.json(result);
@ -50,7 +42,7 @@ export class BatchExecutionLogController {
res.status(500).json({ res.status(500).json({
success: false, success: false,
message: "배치 실행 로그 조회 중 오류가 발생했습니다.", message: "배치 실행 로그 조회 중 오류가 발생했습니다.",
error: error instanceof Error ? error.message : "알 수 없는 오류", error: error instanceof Error ? error.message : "알 수 없는 오류"
}); });
} }
} }
@ -74,7 +66,7 @@ export class BatchExecutionLogController {
res.status(500).json({ res.status(500).json({
success: false, success: false,
message: "배치 실행 로그 생성 중 오류가 발생했습니다.", message: "배치 실행 로그 생성 중 오류가 발생했습니다.",
error: error instanceof Error ? error.message : "알 수 없는 오류", error: error instanceof Error ? error.message : "알 수 없는 오류"
}); });
} }
} }
@ -87,10 +79,7 @@ export class BatchExecutionLogController {
const { id } = req.params; const { id } = req.params;
const data: UpdateBatchExecutionLogRequest = req.body; const data: UpdateBatchExecutionLogRequest = req.body;
const result = await BatchExecutionLogService.updateExecutionLog( const result = await BatchExecutionLogService.updateExecutionLog(Number(id), data);
Number(id),
data
);
if (result.success) { if (result.success) {
res.json(result); res.json(result);
@ -102,7 +91,7 @@ export class BatchExecutionLogController {
res.status(500).json({ res.status(500).json({
success: false, success: false,
message: "배치 실행 로그 업데이트 중 오류가 발생했습니다.", message: "배치 실행 로그 업데이트 중 오류가 발생했습니다.",
error: error instanceof Error ? error.message : "알 수 없는 오류", error: error instanceof Error ? error.message : "알 수 없는 오류"
}); });
} }
} }
@ -114,9 +103,7 @@ export class BatchExecutionLogController {
try { try {
const { id } = req.params; const { id } = req.params;
const result = await BatchExecutionLogService.deleteExecutionLog( const result = await BatchExecutionLogService.deleteExecutionLog(Number(id));
Number(id)
);
if (result.success) { if (result.success) {
res.json(result); res.json(result);
@ -128,7 +115,7 @@ export class BatchExecutionLogController {
res.status(500).json({ res.status(500).json({
success: false, success: false,
message: "배치 실행 로그 삭제 중 오류가 발생했습니다.", message: "배치 실행 로그 삭제 중 오류가 발생했습니다.",
error: error instanceof Error ? error.message : "알 수 없는 오류", error: error instanceof Error ? error.message : "알 수 없는 오류"
}); });
} }
} }
@ -140,9 +127,7 @@ export class BatchExecutionLogController {
try { try {
const { batchConfigId } = req.params; const { batchConfigId } = req.params;
const result = await BatchExecutionLogService.getLatestExecutionLog( const result = await BatchExecutionLogService.getLatestExecutionLog(Number(batchConfigId));
Number(batchConfigId)
);
if (result.success) { if (result.success) {
res.json(result); res.json(result);
@ -154,7 +139,7 @@ export class BatchExecutionLogController {
res.status(500).json({ res.status(500).json({
success: false, success: false,
message: "최신 배치 실행 로그 조회 중 오류가 발생했습니다.", message: "최신 배치 실행 로그 조회 중 오류가 발생했습니다.",
error: error instanceof Error ? error.message : "알 수 없는 오류", error: error instanceof Error ? error.message : "알 수 없는 오류"
}); });
} }
} }
@ -164,7 +149,11 @@ export class BatchExecutionLogController {
*/ */
static async getExecutionStats(req: AuthenticatedRequest, res: Response) { static async getExecutionStats(req: AuthenticatedRequest, res: Response) {
try { 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( const result = await BatchExecutionLogService.getExecutionStats(
batch_config_id ? Number(batch_config_id) : undefined, batch_config_id ? Number(batch_config_id) : undefined,
@ -182,8 +171,9 @@ export class BatchExecutionLogController {
res.status(500).json({ res.status(500).json({
success: false, success: false,
message: "배치 실행 통계 조회 중 오류가 발생했습니다.", message: "배치 실행 통계 조회 중 오류가 발생했습니다.",
error: error instanceof Error ? error.message : "알 수 없는 오류", error: error instanceof Error ? error.message : "알 수 없는 오류"
}); });
} }
} }
} }

View File

@ -3,12 +3,7 @@
import { Response } from "express"; import { Response } from "express";
import { AuthenticatedRequest } from "../types/auth"; import { AuthenticatedRequest } from "../types/auth";
import { import { BatchManagementService, BatchConnectionInfo, BatchTableInfo, BatchColumnInfo } from "../services/batchManagementService";
BatchManagementService,
BatchConnectionInfo,
BatchTableInfo,
BatchColumnInfo,
} from "../services/batchManagementService";
import { BatchService } from "../services/batchService"; import { BatchService } from "../services/batchService";
import { BatchSchedulerService } from "../services/batchSchedulerService"; import { BatchSchedulerService } from "../services/batchSchedulerService";
import { BatchExternalDbService } from "../services/batchExternalDbService"; import { BatchExternalDbService } from "../services/batchExternalDbService";
@ -16,16 +11,11 @@ import { CreateBatchConfigRequest, BatchConfig } from "../types/batchTypes";
export class BatchManagementController { export class BatchManagementController {
/** /**
* () *
*/ */
static async getAvailableConnections( static async getAvailableConnections(req: AuthenticatedRequest, res: Response) {
req: AuthenticatedRequest,
res: Response
) {
try { try {
const userCompanyCode = req.user?.companyCode; const result = await BatchManagementService.getAvailableConnections();
const result =
await BatchManagementService.getAvailableConnections(userCompanyCode);
if (result.success) { if (result.success) {
res.json(result); res.json(result);
} else { } else {
@ -36,35 +26,27 @@ export class BatchManagementController {
res.status(500).json({ res.status(500).json({
success: false, success: false,
message: "커넥션 목록 조회 중 오류가 발생했습니다.", message: "커넥션 목록 조회 중 오류가 발생했습니다.",
error: error instanceof Error ? error.message : "알 수 없는 오류", error: error instanceof Error ? error.message : "알 수 없는 오류"
}); });
} }
} }
/** /**
* () *
*/ */
static async getTablesFromConnection( static async getTablesFromConnection(req: AuthenticatedRequest, res: Response) {
req: AuthenticatedRequest,
res: Response
) {
try { try {
const { type, id } = req.params; const { type, id } = req.params;
const userCompanyCode = req.user?.companyCode;
if (type !== "internal" && type !== "external") { if (type !== 'internal' && type !== 'external') {
return res.status(400).json({ return res.status(400).json({
success: false, success: false,
message: "올바른 연결 타입을 지정해주세요. (internal 또는 external)", message: "올바른 연결 타입을 지정해주세요. (internal 또는 external)"
}); });
} }
const connectionId = type === "external" ? Number(id) : undefined; const connectionId = type === 'external' ? Number(id) : undefined;
const result = await BatchManagementService.getTablesFromConnection( const result = await BatchManagementService.getTablesFromConnection(type, connectionId);
type,
connectionId,
userCompanyCode
);
if (result.success) { if (result.success) {
return res.json(result); return res.json(result);
@ -76,33 +58,27 @@ export class BatchManagementController {
return res.status(500).json({ return res.status(500).json({
success: false, success: false,
message: "테이블 목록 조회 중 오류가 발생했습니다.", message: "테이블 목록 조회 중 오류가 발생했습니다.",
error: error instanceof Error ? error.message : "알 수 없는 오류", error: error instanceof Error ? error.message : "알 수 없는 오류"
}); });
} }
} }
/** /**
* () *
*/ */
static async getTableColumns(req: AuthenticatedRequest, res: Response) { static async getTableColumns(req: AuthenticatedRequest, res: Response) {
try { try {
const { type, id, tableName } = req.params; const { type, id, tableName } = req.params;
const userCompanyCode = req.user?.companyCode;
if (type !== "internal" && type !== "external") { if (type !== 'internal' && type !== 'external') {
return res.status(400).json({ return res.status(400).json({
success: false, success: false,
message: "올바른 연결 타입을 지정해주세요. (internal 또는 external)", message: "올바른 연결 타입을 지정해주세요. (internal 또는 external)"
}); });
} }
const connectionId = type === "external" ? Number(id) : undefined; const connectionId = type === 'external' ? Number(id) : undefined;
const result = await BatchManagementService.getTableColumns( const result = await BatchManagementService.getTableColumns(type, connectionId, tableName);
type,
connectionId,
tableName,
userCompanyCode
);
if (result.success) { if (result.success) {
return res.json(result); return res.json(result);
@ -114,7 +90,7 @@ export class BatchManagementController {
return res.status(500).json({ return res.status(500).json({
success: false, success: false,
message: "컬럼 정보 조회 중 오류가 발생했습니다.", message: "컬럼 정보 조회 중 오류가 발생했습니다.",
error: error instanceof Error ? error.message : "알 수 없는 오류", error: error instanceof Error ? error.message : "알 수 없는 오류"
}); });
} }
} }
@ -125,19 +101,12 @@ export class BatchManagementController {
*/ */
static async createBatchConfig(req: AuthenticatedRequest, res: Response) { static async createBatchConfig(req: AuthenticatedRequest, res: Response) {
try { try {
const { batchName, description, cronSchedule, mappings, isActive } = const { batchName, description, cronSchedule, mappings, isActive } = req.body;
req.body;
if ( if (!batchName || !cronSchedule || !mappings || !Array.isArray(mappings)) {
!batchName ||
!cronSchedule ||
!mappings ||
!Array.isArray(mappings)
) {
return res.status(400).json({ return res.status(400).json({
success: false, success: false,
message: message: "필수 필드가 누락되었습니다. (batchName, cronSchedule, mappings)"
"필수 필드가 누락되었습니다. (batchName, cronSchedule, mappings)",
}); });
} }
@ -146,20 +115,20 @@ export class BatchManagementController {
description, description,
cronSchedule, cronSchedule,
mappings, mappings,
isActive: isActive !== undefined ? isActive : true, isActive: isActive !== undefined ? isActive : true
} as CreateBatchConfigRequest); } as CreateBatchConfigRequest);
return res.status(201).json({ return res.status(201).json({
success: true, success: true,
data: batchConfig, data: batchConfig,
message: "배치 설정이 성공적으로 생성되었습니다.", message: "배치 설정이 성공적으로 생성되었습니다."
}); });
} catch (error) { } catch (error) {
console.error("배치 설정 생성 오류:", error); console.error("배치 설정 생성 오류:", error);
return res.status(500).json({ return res.status(500).json({
success: false, success: false,
message: "배치 설정 생성에 실패했습니다.", message: "배치 설정 생성에 실패했습니다.",
error: error instanceof Error ? error.message : "알 수 없는 오류", error: error instanceof Error ? error.message : "알 수 없는 오류"
}); });
} }
} }
@ -178,7 +147,7 @@ export class BatchManagementController {
if (!result.success) { if (!result.success) {
return res.status(404).json({ return res.status(404).json({
success: false, success: false,
message: result.message || "배치 설정을 찾을 수 없습니다.", message: result.message || "배치 설정을 찾을 수 없습니다."
}); });
} }
@ -186,14 +155,14 @@ export class BatchManagementController {
return res.json({ return res.json({
success: true, success: true,
data: result.data, data: result.data
}); });
} catch (error) { } catch (error) {
console.error("❌ 배치 설정 조회 오류:", error); console.error("❌ 배치 설정 조회 오류:", error);
return res.status(500).json({ return res.status(500).json({
success: false, success: false,
message: "배치 설정 조회에 실패했습니다.", message: "배치 설정 조회에 실패했습니다.",
error: error instanceof Error ? error.message : "알 수 없는 오류", error: error instanceof Error ? error.message : "알 수 없는 오류"
}); });
} }
} }
@ -210,7 +179,7 @@ export class BatchManagementController {
page: Number(page), page: Number(page),
limit: Number(limit), limit: Number(limit),
search: search as string, 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);
@ -218,14 +187,14 @@ export class BatchManagementController {
res.json({ res.json({
success: true, success: true,
data: result.data, data: result.data,
pagination: result.pagination, pagination: result.pagination
}); });
} catch (error) { } catch (error) {
console.error("배치 설정 목록 조회 오류:", error); console.error("배치 설정 목록 조회 오류:", error);
res.status(500).json({ res.status(500).json({
success: false, success: false,
message: "배치 설정 목록 조회에 실패했습니다.", message: "배치 설정 목록 조회에 실패했습니다.",
error: error instanceof Error ? error.message : "알 수 없는 오류", error: error instanceof Error ? error.message : "알 수 없는 오류"
}); });
} }
} }
@ -241,18 +210,16 @@ export class BatchManagementController {
if (!id || isNaN(Number(id))) { if (!id || isNaN(Number(id))) {
return res.status(400).json({ return res.status(400).json({
success: false, success: false,
message: "올바른 배치 설정 ID를 제공해주세요.", message: "올바른 배치 설정 ID를 제공해주세요."
}); });
} }
// 배치 설정 조회 // 배치 설정 조회
const batchConfigResult = await BatchService.getBatchConfigById( const batchConfigResult = await BatchService.getBatchConfigById(Number(id));
Number(id)
);
if (!batchConfigResult.success || !batchConfigResult.data) { if (!batchConfigResult.success || !batchConfigResult.data) {
return res.status(404).json({ return res.status(404).json({
success: false, success: false,
message: "배치 설정을 찾을 수 없습니다.", message: "배치 설정을 찾을 수 없습니다."
}); });
} }
@ -267,23 +234,20 @@ export class BatchManagementController {
// 실행 로그 생성 // 실행 로그 생성
executionLog = await BatchService.createExecutionLog({ executionLog = await BatchService.createExecutionLog({
batch_config_id: Number(id), batch_config_id: Number(id),
execution_status: "RUNNING", execution_status: 'RUNNING',
start_time: startTime, start_time: startTime,
total_records: 0, total_records: 0,
success_records: 0, success_records: 0,
failed_records: 0, failed_records: 0
}); });
// BatchSchedulerService의 executeBatchConfig 메서드 사용 (중복 로직 제거) // BatchSchedulerService의 executeBatchConfig 메서드 사용 (중복 로직 제거)
const { BatchSchedulerService } = await import( const { BatchSchedulerService } = await import('../services/batchSchedulerService');
"../services/batchSchedulerService" const result = await BatchSchedulerService.executeBatchConfig(batchConfig);
);
const result =
await BatchSchedulerService.executeBatchConfig(batchConfig);
// result가 undefined인 경우 처리 // result가 undefined인 경우 처리
if (!result) { if (!result) {
throw new Error("배치 실행 결과를 받을 수 없습니다."); throw new Error('배치 실행 결과를 받을 수 없습니다.');
} }
const endTime = new Date(); const endTime = new Date();
@ -291,12 +255,12 @@ export class BatchManagementController {
// 실행 로그 업데이트 (성공) // 실행 로그 업데이트 (성공)
await BatchService.updateExecutionLog(executionLog.id, { await BatchService.updateExecutionLog(executionLog.id, {
execution_status: "SUCCESS", execution_status: 'SUCCESS',
end_time: endTime, end_time: endTime,
duration_ms: duration, duration_ms: duration,
total_records: result.totalRecords, total_records: result.totalRecords,
success_records: result.successRecords, success_records: result.successRecords,
failed_records: result.failedRecords, failed_records: result.failedRecords
}); });
return res.json({ return res.json({
@ -306,10 +270,11 @@ export class BatchManagementController {
totalRecords: result.totalRecords, totalRecords: result.totalRecords,
successRecords: result.successRecords, successRecords: result.successRecords,
failedRecords: result.failedRecords, failedRecords: result.failedRecords,
executionTime: duration, executionTime: duration
}, },
message: "배치가 성공적으로 실행되었습니다.", message: "배치가 성공적으로 실행되었습니다."
}); });
} catch (batchError) { } catch (batchError) {
console.error(`배치 실행 실패: ${batchConfig.batch_name}`, batchError); console.error(`배치 실행 실패: ${batchConfig.batch_name}`, batchError);
@ -319,36 +284,31 @@ export class BatchManagementController {
const duration = endTime.getTime() - startTime.getTime(); const duration = endTime.getTime() - startTime.getTime();
// executionLog가 정의되어 있는지 확인 // executionLog가 정의되어 있는지 확인
if (typeof executionLog !== "undefined") { if (typeof executionLog !== 'undefined') {
await BatchService.updateExecutionLog(executionLog.id, { await BatchService.updateExecutionLog(executionLog.id, {
execution_status: "FAILED", execution_status: 'FAILED',
end_time: endTime, end_time: endTime,
duration_ms: duration, duration_ms: duration,
error_message: error_message: batchError instanceof Error ? batchError.message : "알 수 없는 오류"
batchError instanceof Error
? batchError.message
: "알 수 없는 오류",
}); });
} }
} catch (logError) { } catch (logError) {
console.error("실행 로그 업데이트 실패:", logError); console.error('실행 로그 업데이트 실패:', logError);
} }
return res.status(500).json({ return res.status(500).json({
success: false, success: false,
message: "배치 실행에 실패했습니다.", message: "배치 실행에 실패했습니다.",
error: error: batchError instanceof Error ? batchError.message : "알 수 없는 오류"
batchError instanceof Error
? batchError.message
: "알 수 없는 오류",
}); });
} }
} catch (error) { } catch (error) {
console.error(`배치 실행 오류 (ID: ${req.params.id}):`, error); console.error(`배치 실행 오류 (ID: ${req.params.id}):`, error);
return res.status(500).json({ return res.status(500).json({
success: false, success: false,
message: "배치 실행 중 오류가 발생했습니다.", message: "배치 실행 중 오류가 발생했습니다.",
error: error instanceof Error ? error.message : "Unknown error", error: error instanceof Error ? error.message : "Unknown error"
}); });
} }
} }
@ -365,14 +325,11 @@ export class BatchManagementController {
if (!id || isNaN(Number(id))) { if (!id || isNaN(Number(id))) {
return res.status(400).json({ return res.status(400).json({
success: false, success: false,
message: "올바른 배치 설정 ID를 제공해주세요.", message: "올바른 배치 설정 ID를 제공해주세요."
}); });
} }
const batchConfig = await BatchService.updateBatchConfig( const batchConfig = await BatchService.updateBatchConfig(Number(id), updateData);
Number(id),
updateData
);
// 스케줄러에서 배치 스케줄 업데이트 (즉시 실행 비활성화) // 스케줄러에서 배치 스케줄 업데이트 (즉시 실행 비활성화)
await BatchSchedulerService.updateBatchSchedule(Number(id), false); await BatchSchedulerService.updateBatchSchedule(Number(id), false);
@ -380,14 +337,14 @@ export class BatchManagementController {
return res.json({ return res.json({
success: true, success: true,
data: batchConfig, data: batchConfig,
message: "배치 설정이 성공적으로 업데이트되었습니다.", message: "배치 설정이 성공적으로 업데이트되었습니다."
}); });
} catch (error) { } catch (error) {
console.error("배치 설정 업데이트 오류:", error); console.error("배치 설정 업데이트 오류:", error);
return res.status(500).json({ return res.status(500).json({
success: false, success: false,
message: "배치 설정 업데이트에 실패했습니다.", message: "배치 설정 업데이트에 실패했습니다.",
error: error instanceof Error ? error.message : "알 수 없는 오류", error: error instanceof Error ? error.message : "알 수 없는 오류"
}); });
} }
} }
@ -401,17 +358,17 @@ export class BatchManagementController {
apiUrl, apiUrl,
apiKey, apiKey,
endpoint, endpoint,
method = "GET", method = 'GET',
paramType, paramType,
paramName, paramName,
paramValue, paramValue,
paramSource, paramSource
} = req.body; } = req.body;
if (!apiUrl || !apiKey || !endpoint) { if (!apiUrl || !apiKey || !endpoint) {
return res.status(400).json({ return res.status(400).json({
success: false, success: false,
message: "API URL, API Key, 엔드포인트는 필수입니다.", message: "API URL, API Key, 엔드포인트는 필수입니다."
}); });
} }
@ -421,16 +378,16 @@ export class BatchManagementController {
paramType, paramType,
paramName, paramName,
paramValue, paramValue,
paramSource, paramSource
}); });
// RestApiConnector 사용하여 데이터 조회 // RestApiConnector 사용하여 데이터 조회
const { RestApiConnector } = await import("../database/RestApiConnector"); const { RestApiConnector } = await import('../database/RestApiConnector');
const connector = new RestApiConnector({ const connector = new RestApiConnector({
baseUrl: apiUrl, baseUrl: apiUrl,
apiKey: apiKey, apiKey: apiKey,
timeout: 30000, timeout: 30000
}); });
// 연결 테스트 // 연결 테스트
@ -439,7 +396,7 @@ export class BatchManagementController {
// 파라미터가 있는 경우 엔드포인트 수정 // 파라미터가 있는 경우 엔드포인트 수정
let finalEndpoint = endpoint; let finalEndpoint = endpoint;
if (paramType && paramName && paramValue) { if (paramType && paramName && paramValue) {
if (paramType === "url") { if (paramType === 'url') {
// URL 파라미터: /api/users/{userId} → /api/users/123 // URL 파라미터: /api/users/{userId} → /api/users/123
if (endpoint.includes(`{${paramName}}`)) { if (endpoint.includes(`{${paramName}}`)) {
finalEndpoint = endpoint.replace(`{${paramName}}`, paramValue); finalEndpoint = endpoint.replace(`{${paramName}}`, paramValue);
@ -447,9 +404,9 @@ export class BatchManagementController {
// 엔드포인트에 {paramName}이 없으면 뒤에 추가 // 엔드포인트에 {paramName}이 없으면 뒤에 추가
finalEndpoint = `${endpoint}/${paramValue}`; finalEndpoint = `${endpoint}/${paramValue}`;
} }
} else if (paramType === "query") { } else if (paramType === 'query') {
// 쿼리 파라미터: /api/users?userId=123 // 쿼리 파라미터: /api/users?userId=123
const separator = endpoint.includes("?") ? "&" : "?"; const separator = endpoint.includes('?') ? '&' : '?';
finalEndpoint = `${endpoint}${separator}${paramName}=${paramValue}`; finalEndpoint = `${endpoint}${separator}${paramName}=${paramValue}`;
} }
} }
@ -460,9 +417,8 @@ export class BatchManagementController {
const result = await connector.executeQuery(finalEndpoint, method); const result = await connector.executeQuery(finalEndpoint, method);
console.log(`[previewRestApiData] executeQuery 결과:`, { console.log(`[previewRestApiData] executeQuery 결과:`, {
rowCount: result.rowCount, rowCount: result.rowCount,
rowsLength: result.rows ? result.rows.length : "undefined", rowsLength: result.rows ? result.rows.length : 'undefined',
firstRow: firstRow: result.rows && result.rows.length > 0 ? result.rows[0] : 'no data'
result.rows && result.rows.length > 0 ? result.rows[0] : "no data",
}); });
const data = result.rows.slice(0, 5); // 최대 5개 샘플만 const data = result.rows.slice(0, 5); // 최대 5개 샘플만
@ -478,9 +434,9 @@ export class BatchManagementController {
data: { data: {
fields: fields, fields: fields,
samples: data, 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 { } else {
return res.json({ return res.json({
@ -488,9 +444,9 @@ export class BatchManagementController {
data: { data: {
fields: [], fields: [],
samples: [], samples: [],
totalCount: 0, totalCount: 0
}, },
message: "API에서 데이터를 가져올 수 없습니다.", message: "API에서 데이터를 가져올 수 없습니다."
}); });
} }
} catch (error) { } catch (error) {
@ -498,7 +454,7 @@ export class BatchManagementController {
return res.status(500).json({ return res.status(500).json({
success: false, success: false,
message: "REST API 데이터 미리보기 중 오류가 발생했습니다.", message: "REST API 데이터 미리보기 중 오류가 발생했습니다.",
error: error instanceof Error ? error.message : "알 수 없는 오류", error: error instanceof Error ? error.message : "알 수 없는 오류"
}); });
} }
} }
@ -508,19 +464,18 @@ export class BatchManagementController {
*/ */
static async saveRestApiBatch(req: AuthenticatedRequest, res: Response) { static async saveRestApiBatch(req: AuthenticatedRequest, res: Response) {
try { try {
const { batchName, batchType, cronSchedule, description, apiMappings } = const {
req.body; batchName,
batchType,
cronSchedule,
description,
apiMappings
} = req.body;
if ( if (!batchName || !batchType || !cronSchedule || !apiMappings || apiMappings.length === 0) {
!batchName ||
!batchType ||
!cronSchedule ||
!apiMappings ||
apiMappings.length === 0
) {
return res.status(400).json({ return res.status(400).json({
success: false, success: false,
message: "필수 필드가 누락되었습니다.", message: "필수 필드가 누락되었습니다."
}); });
} }
@ -529,15 +484,15 @@ export class BatchManagementController {
batchType, batchType,
cronSchedule, cronSchedule,
description, description,
apiMappings, apiMappings
}); });
// BatchService를 사용하여 배치 설정 저장 // BatchService를 사용하여 배치 설정 저장
const batchConfig: CreateBatchConfigRequest = { const batchConfig: CreateBatchConfigRequest = {
batchName: batchName, batchName: batchName,
description: description || "", description: description || '',
cronSchedule: cronSchedule, cronSchedule: cronSchedule,
mappings: apiMappings, mappings: apiMappings
}; };
const result = await BatchService.createBatchConfig(batchConfig); const result = await BatchService.createBatchConfig(batchConfig);
@ -546,9 +501,7 @@ export class BatchManagementController {
// 스케줄러에 자동 등록 ✅ // 스케줄러에 자동 등록 ✅
try { try {
await BatchSchedulerService.scheduleBatchConfig(result.data); await BatchSchedulerService.scheduleBatchConfig(result.data);
console.log( console.log(`✅ 새로운 배치가 스케줄러에 등록되었습니다: ${batchName} (ID: ${result.data.id})`);
`✅ 새로운 배치가 스케줄러에 등록되었습니다: ${batchName} (ID: ${result.data.id})`
);
} catch (schedulerError) { } catch (schedulerError) {
console.error(`❌ 스케줄러 등록 실패: ${batchName}`, schedulerError); console.error(`❌ 스케줄러 등록 실패: ${batchName}`, schedulerError);
// 스케줄러 등록 실패해도 배치 저장은 성공으로 처리 // 스케줄러 등록 실패해도 배치 저장은 성공으로 처리
@ -557,19 +510,19 @@ export class BatchManagementController {
return res.json({ return res.json({
success: true, success: true,
message: "REST API 배치가 성공적으로 저장되었습니다.", message: "REST API 배치가 성공적으로 저장되었습니다.",
data: result.data, data: result.data
}); });
} else { } else {
return res.status(500).json({ return res.status(500).json({
success: false, success: false,
message: result.message || "배치 저장에 실패했습니다.", message: result.message || "배치 저장에 실패했습니다."
}); });
} }
} catch (error) { } catch (error) {
console.error("REST API 배치 저장 오류:", error); console.error("REST API 배치 저장 오류:", error);
return res.status(500).json({ return res.status(500).json({
success: false, success: false,
message: "배치 저장 중 오류가 발생했습니다.", message: "배치 저장 중 오류가 발생했습니다."
}); });
} }
} }

View File

@ -461,13 +461,12 @@ export class CommonCodeController {
} }
/** /**
* () *
* GET /api/common-codes/categories/check-duplicate?field=categoryCode&value=USER_STATUS&excludeCode=OLD_CODE * GET /api/common-codes/categories/check-duplicate?field=categoryCode&value=USER_STATUS&excludeCode=OLD_CODE
*/ */
async checkCategoryDuplicate(req: AuthenticatedRequest, res: Response) { async checkCategoryDuplicate(req: AuthenticatedRequest, res: Response) {
try { try {
const { field, value, excludeCode } = req.query; const { field, value, excludeCode } = req.query;
const userCompanyCode = req.user?.companyCode;
// 입력값 검증 // 입력값 검증
if (!field || !value) { if (!field || !value) {
@ -489,8 +488,7 @@ export class CommonCodeController {
const result = await this.commonCodeService.checkCategoryDuplicate( const result = await this.commonCodeService.checkCategoryDuplicate(
field as "categoryCode" | "categoryName" | "categoryNameEng", field as "categoryCode" | "categoryName" | "categoryNameEng",
value as string, value as string,
excludeCode as string, excludeCode as string
userCompanyCode
); );
return res.json({ return res.json({
@ -513,14 +511,13 @@ export class CommonCodeController {
} }
/** /**
* () *
* GET /api/common-codes/categories/:categoryCode/codes/check-duplicate?field=codeValue&value=ACTIVE&excludeCode=OLD_CODE * GET /api/common-codes/categories/:categoryCode/codes/check-duplicate?field=codeValue&value=ACTIVE&excludeCode=OLD_CODE
*/ */
async checkCodeDuplicate(req: AuthenticatedRequest, res: Response) { async checkCodeDuplicate(req: AuthenticatedRequest, res: Response) {
try { try {
const { categoryCode } = req.params; const { categoryCode } = req.params;
const { field, value, excludeCode } = req.query; const { field, value, excludeCode } = req.query;
const userCompanyCode = req.user?.companyCode;
// 입력값 검증 // 입력값 검증
if (!field || !value) { if (!field || !value) {
@ -543,8 +540,7 @@ export class CommonCodeController {
categoryCode, categoryCode,
field as "codeValue" | "codeName" | "codeNameEng", field as "codeValue" | "codeName" | "codeNameEng",
value as string, value as string,
excludeCode as string, excludeCode as string
userCompanyCode
); );
return res.json({ return res.json({

View File

@ -557,14 +557,9 @@ export class FlowController {
getStepColumnLabels = async (req: Request, res: Response): Promise<void> => { getStepColumnLabels = async (req: Request, res: Response): Promise<void> => {
try { try {
const { flowId, stepId } = req.params; const { flowId, stepId } = req.params;
console.log("🏷️ [FlowController] 컬럼 라벨 조회 요청:", {
flowId,
stepId,
});
const step = await this.flowStepService.findById(parseInt(stepId)); const step = await this.flowStepService.getById(parseInt(stepId));
if (!step) { if (!step) {
console.warn("⚠️ [FlowController] 스텝을 찾을 수 없음:", stepId);
res.status(404).json({ res.status(404).json({
success: false, success: false,
message: "Step not found", message: "Step not found",
@ -572,11 +567,10 @@ export class FlowController {
return; return;
} }
const flowDef = await this.flowDefinitionService.findById( const flowDef = await this.flowDefinitionService.getById(
parseInt(flowId) parseInt(flowId)
); );
if (!flowDef) { if (!flowDef) {
console.warn("⚠️ [FlowController] 플로우를 찾을 수 없음:", flowId);
res.status(404).json({ res.status(404).json({
success: false, success: false,
message: "Flow definition not found", message: "Flow definition not found",
@ -586,14 +580,7 @@ export class FlowController {
// 테이블명 결정 (스텝 테이블 우선, 없으면 플로우 테이블) // 테이블명 결정 (스텝 테이블 우선, 없으면 플로우 테이블)
const tableName = step.tableName || flowDef.tableName; const tableName = step.tableName || flowDef.tableName;
console.log("📋 [FlowController] 테이블명 결정:", {
stepTableName: step.tableName,
flowTableName: flowDef.tableName,
selectedTableName: tableName,
});
if (!tableName) { if (!tableName) {
console.warn("⚠️ [FlowController] 테이블명이 지정되지 않음");
res.json({ res.json({
success: true, success: true,
data: {}, data: {},
@ -602,7 +589,7 @@ export class FlowController {
} }
// column_labels 테이블에서 라벨 정보 조회 // column_labels 테이블에서 라벨 정보 조회
const { query } = await import("../database/db"); const { query } = await import("../config/database");
const labelRows = await query<{ const labelRows = await query<{
column_name: string; column_name: string;
column_label: string | null; column_label: string | null;
@ -613,15 +600,6 @@ export class FlowController {
[tableName] [tableName]
); );
console.log(`✅ [FlowController] column_labels 조회 완료:`, {
tableName,
rowCount: labelRows.length,
labels: labelRows.map((r) => ({
col: r.column_name,
label: r.column_label,
})),
});
// { columnName: label } 형태의 객체로 변환 // { columnName: label } 형태의 객체로 변환
const labels: Record<string, string> = {}; const labels: Record<string, string> = {};
labelRows.forEach((row) => { labelRows.forEach((row) => {
@ -630,14 +608,12 @@ export class FlowController {
} }
}); });
console.log("📦 [FlowController] 반환할 라벨 객체:", labels);
res.json({ res.json({
success: true, success: true,
data: labels, data: labels,
}); });
} catch (error: any) { } catch (error: any) {
console.error("❌ [FlowController] 컬럼 라벨 조회 오류:", error); console.error("Error getting step column labels:", error);
res.status(500).json({ res.status(500).json({
success: false, success: false,
message: error.message || "Failed to get step column labels", message: error.message || "Failed to get step column labels",

View File

@ -87,10 +87,7 @@ router.get(
filter, filter,
}); });
const result = await ExternalDbConnectionService.getConnections( const result = await ExternalDbConnectionService.getConnections(filter);
filter,
userCompanyCode
);
if (result.success) { if (result.success) {
return res.status(200).json(result); return res.status(200).json(result);
@ -322,12 +319,7 @@ router.delete(
}); });
} }
const userCompanyCode = req.user?.companyCode; const result = await ExternalDbConnectionService.deleteConnection(id);
const result = await ExternalDbConnectionService.deleteConnection(
id,
userCompanyCode
);
if (result.success) { if (result.success) {
return res.status(200).json(result); return res.status(200).json(result);
@ -525,10 +517,7 @@ router.get(
}); });
const externalConnections = const externalConnections =
await ExternalDbConnectionService.getConnections( await ExternalDbConnectionService.getConnections(filter);
filter,
userCompanyCode
);
if (!externalConnections.success) { if (!externalConnections.success) {
return res.status(400).json(externalConnections); return res.status(400).json(externalConnections);

View File

@ -29,12 +29,8 @@ router.get(
company_code: req.query.company_code as string, company_code: req.query.company_code as string,
}; };
const userCompanyCode = req.user?.companyCode; const result =
await ExternalRestApiConnectionService.getConnections(filter);
const result = await ExternalRestApiConnectionService.getConnections(
filter,
userCompanyCode
);
return res.status(result.success ? 200 : 400).json(result); return res.status(result.success ? 200 : 400).json(result);
} catch (error) { } catch (error) {
@ -66,12 +62,8 @@ router.get(
}); });
} }
const userCompanyCode = req.user?.companyCode; const result =
await ExternalRestApiConnectionService.getConnectionById(id);
const result = await ExternalRestApiConnectionService.getConnectionById(
id,
userCompanyCode
);
return res.status(result.success ? 200 : 404).json(result); return res.status(result.success ? 200 : 404).json(result);
} catch (error) { } catch (error) {
@ -137,12 +129,9 @@ router.put(
updated_by: req.user?.userId || "system", updated_by: req.user?.userId || "system",
}; };
const userCompanyCode = req.user?.companyCode;
const result = await ExternalRestApiConnectionService.updateConnection( const result = await ExternalRestApiConnectionService.updateConnection(
id, id,
data, data
userCompanyCode
); );
return res.status(result.success ? 200 : 400).json(result); return res.status(result.success ? 200 : 400).json(result);
@ -175,12 +164,8 @@ router.delete(
}); });
} }
const userCompanyCode = req.user?.companyCode; const result =
await ExternalRestApiConnectionService.deleteConnection(id);
const result = await ExternalRestApiConnectionService.deleteConnection(
id,
userCompanyCode
);
return res.status(result.success ? 200 : 404).json(result); return res.status(result.success ? 200 : 404).json(result);
} catch (error) { } catch (error) {

View File

@ -13,11 +13,10 @@ import { ApiResponse } from "../types/batchTypes";
export class BatchExecutionLogService { export class BatchExecutionLogService {
/** /**
* () *
*/ */
static async getExecutionLogs( static async getExecutionLogs(
filter: BatchExecutionLogFilter = {}, filter: BatchExecutionLogFilter = {}
userCompanyCode?: string
): Promise<ApiResponse<BatchExecutionLogWithConfig[]>> { ): Promise<ApiResponse<BatchExecutionLogWithConfig[]>> {
try { try {
const { const {
@ -37,12 +36,6 @@ export class BatchExecutionLogService {
const params: any[] = []; const params: any[] = [];
let paramIndex = 1; let paramIndex = 1;
// 회사별 필터링 (최고 관리자가 아닌 경우)
if (userCompanyCode && userCompanyCode !== "*") {
whereConditions.push(`bc.company_code = $${paramIndex++}`);
params.push(userCompanyCode);
}
if (batch_config_id) { if (batch_config_id) {
whereConditions.push(`bel.batch_config_id = $${paramIndex++}`); whereConditions.push(`bel.batch_config_id = $${paramIndex++}`);
params.push(batch_config_id); params.push(batch_config_id);

View File

@ -35,11 +35,11 @@ export interface BatchApiResponse<T = unknown> {
export class BatchManagementService { export class BatchManagementService {
/** /**
* () *
*/ */
static async getAvailableConnections( static async getAvailableConnections(): Promise<
userCompanyCode?: string BatchApiResponse<BatchConnectionInfo[]>
): Promise<BatchApiResponse<BatchConnectionInfo[]>> { > {
try { try {
const connections: BatchConnectionInfo[] = []; const connections: BatchConnectionInfo[] = [];
@ -50,27 +50,19 @@ export class BatchManagementService {
db_type: "postgresql", 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<{ const externalConnections = await query<{
id: number; id: number;
connection_name: string; connection_name: string;
db_type: string; db_type: string;
description: string; description: string;
}>(query_sql, params); }>(
`SELECT id, connection_name, db_type, description
FROM external_db_connections
WHERE is_active = 'Y'
ORDER BY connection_name ASC`,
[]
);
// 외부 DB 연결 추가 // 외부 DB 연결 추가
externalConnections.forEach((conn) => { externalConnections.forEach((conn) => {
@ -98,12 +90,11 @@ export class BatchManagementService {
} }
/** /**
* () *
*/ */
static async getTablesFromConnection( static async getTablesFromConnection(
connectionType: "internal" | "external", connectionType: "internal" | "external",
connectionId?: number, connectionId?: number
userCompanyCode?: string
): Promise<BatchApiResponse<BatchTableInfo[]>> { ): Promise<BatchApiResponse<BatchTableInfo[]>> {
try { try {
let tables: BatchTableInfo[] = []; let tables: BatchTableInfo[] = [];
@ -124,11 +115,8 @@ export class BatchManagementService {
columns: [], columns: [],
})); }));
} else if (connectionType === "external" && connectionId) { } else if (connectionType === "external" && connectionId) {
// 외부 DB 테이블 조회 (회사별 필터링) // 외부 DB 테이블 조회
const tablesResult = await this.getExternalTables( const tablesResult = await this.getExternalTables(connectionId);
connectionId,
userCompanyCode
);
if (tablesResult.success && tablesResult.data) { if (tablesResult.success && tablesResult.data) {
tables = tablesResult.data; tables = tablesResult.data;
} }
@ -150,13 +138,12 @@ export class BatchManagementService {
} }
/** /**
* () *
*/ */
static async getTableColumns( static async getTableColumns(
connectionType: "internal" | "external", connectionType: "internal" | "external",
connectionId: number | undefined, connectionId: number | undefined,
tableName: string, tableName: string
userCompanyCode?: string
): Promise<BatchApiResponse<BatchColumnInfo[]>> { ): Promise<BatchApiResponse<BatchColumnInfo[]>> {
try { try {
console.log(`[BatchManagementService] getTableColumns 호출:`, { console.log(`[BatchManagementService] getTableColumns 호출:`, {
@ -202,15 +189,14 @@ export class BatchManagementService {
column_default: row.column_default, column_default: row.column_default,
})); }));
} else if (connectionType === "external" && connectionId) { } else if (connectionType === "external" && connectionId) {
// 외부 DB 컬럼 조회 (회사별 필터링) // 외부 DB 컬럼 조회
console.log( console.log(
`[BatchManagementService] 외부 DB 컬럼 조회 시작: connectionId=${connectionId}, tableName=${tableName}` `[BatchManagementService] 외부 DB 컬럼 조회 시작: connectionId=${connectionId}, tableName=${tableName}`
); );
const columnsResult = await this.getExternalTableColumns( const columnsResult = await this.getExternalTableColumns(
connectionId, connectionId,
tableName, tableName
userCompanyCode
); );
console.log( console.log(
@ -240,29 +226,22 @@ export class BatchManagementService {
} }
/** /**
* DB ( , ) * DB ( )
*/ */
private static async getExternalTables( private static async getExternalTables(
connectionId: number, connectionId: number
userCompanyCode?: string
): Promise<BatchApiResponse<BatchTableInfo[]>> { ): Promise<BatchApiResponse<BatchTableInfo[]>> {
try { try {
// 연결 정보 조회 (회사별 필터링) // 연결 정보 조회
let query_sql = `SELECT * FROM external_db_connections WHERE id = $1`; const connection = await queryOne<any>(
const params: any[] = [connectionId]; `SELECT * FROM external_db_connections WHERE id = $1`,
[connectionId]
// 회사별 필터링 (최고 관리자가 아닌 경우) );
if (userCompanyCode && userCompanyCode !== "*") {
query_sql += ` AND company_code = $2`;
params.push(userCompanyCode);
}
const connection = await queryOne<any>(query_sql, params);
if (!connection) { if (!connection) {
return { return {
success: false, success: false,
message: "연결 정보를 찾을 수 없거나 권한이 없습니다.", message: "연결 정보를 찾을 수 없습니다.",
}; };
} }
@ -320,33 +299,26 @@ export class BatchManagementService {
} }
/** /**
* DB ( , ) * DB ( )
*/ */
private static async getExternalTableColumns( private static async getExternalTableColumns(
connectionId: number, connectionId: number,
tableName: string, tableName: string
userCompanyCode?: string
): Promise<BatchApiResponse<BatchColumnInfo[]>> { ): Promise<BatchApiResponse<BatchColumnInfo[]>> {
try { try {
console.log( console.log(
`[BatchManagementService] getExternalTableColumns 호출: connectionId=${connectionId}, tableName=${tableName}` `[BatchManagementService] getExternalTableColumns 호출: connectionId=${connectionId}, tableName=${tableName}`
); );
// 연결 정보 조회 (회사별 필터링) // 연결 정보 조회
let query_sql = `SELECT * FROM external_db_connections WHERE id = $1`; const connection = await queryOne<any>(
const params: any[] = [connectionId]; `SELECT * FROM external_db_connections WHERE id = $1`,
[connectionId]
// 회사별 필터링 (최고 관리자가 아닌 경우) );
if (userCompanyCode && userCompanyCode !== "*") {
query_sql += ` AND company_code = $2`;
params.push(userCompanyCode);
}
const connection = await queryOne<any>(query_sql, params);
if (!connection) { if (!connection) {
console.log( console.log(
`[BatchManagementService] 연결 정보를 찾을 수 없거나 권한이 없음: connectionId=${connectionId}` `[BatchManagementService] 연결 정보를 찾을 수 없음: connectionId=${connectionId}`
); );
return { return {
success: false, success: false,

View File

@ -20,33 +20,27 @@ import { DbConnectionManager } from "./dbConnectionManager";
export class BatchService { export class BatchService {
/** /**
* () *
*/ */
static async getBatchConfigs( static async getBatchConfigs(
filter: BatchConfigFilter, filter: BatchConfigFilter
userCompanyCode?: string
): Promise<ApiResponse<BatchConfig[]>> { ): Promise<ApiResponse<BatchConfig[]>> {
try { try {
const whereConditions: string[] = []; const whereConditions: string[] = [];
const values: any[] = []; const values: any[] = [];
let paramIndex = 1; 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) { if (filter.is_active) {
whereConditions.push(`bc.is_active = $${paramIndex++}`); whereConditions.push(`bc.is_active = $${paramIndex++}`);
values.push(filter.is_active); values.push(filter.is_active);
} }
if (filter.company_code) {
whereConditions.push(`bc.company_code = $${paramIndex++}`);
values.push(filter.company_code);
}
// 검색 조건 적용 (OR) // 검색 조건 적용 (OR)
if (filter.search && filter.search.trim()) { if (filter.search && filter.search.trim()) {
whereConditions.push( whereConditions.push(
@ -128,14 +122,14 @@ export class BatchService {
} }
/** /**
* () *
*/ */
static async getBatchConfigById( static async getBatchConfigById(
id: number, id: number
userCompanyCode?: string
): Promise<ApiResponse<BatchConfig>> { ): Promise<ApiResponse<BatchConfig>> {
try { try {
let query = `SELECT bc.id, bc.batch_name, bc.description, bc.cron_schedule, const batchConfig = await queryOne<any>(
`SELECT bc.id, bc.batch_name, bc.description, bc.cron_schedule,
bc.is_active, bc.company_code, bc.created_date, bc.created_by, bc.is_active, bc.company_code, bc.created_date, bc.created_by,
bc.updated_date, bc.updated_by, bc.updated_date, bc.updated_by,
COALESCE( COALESCE(
@ -161,25 +155,15 @@ export class BatchService {
) as batch_mappings ) as batch_mappings
FROM batch_configs bc FROM batch_configs bc
LEFT JOIN batch_mappings bm ON bc.id = bm.batch_config_id LEFT JOIN batch_mappings bm ON bc.id = bm.batch_config_id
WHERE bc.id = $1`; WHERE bc.id = $1
GROUP BY bc.id`,
const params: any[] = [id]; [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<any>(query, params);
if (!batchConfig) { if (!batchConfig) {
return { return {
success: false, success: false,
message: "배치 설정을 찾을 수 없거나 권한이 없습니다.", message: "배치 설정을 찾을 수 없습니다.",
}; };
} }
@ -283,21 +267,15 @@ export class BatchService {
} }
/** /**
* () *
*/ */
static async updateBatchConfig( static async updateBatchConfig(
id: number, id: number,
data: UpdateBatchConfigRequest, data: UpdateBatchConfigRequest,
userId?: string, userId?: string
userCompanyCode?: string
): Promise<ApiResponse<BatchConfig>> { ): Promise<ApiResponse<BatchConfig>> {
try { try {
// 기존 배치 설정 확인 (회사 권한 체크 포함) // 기존 배치 설정 확인
const existing = await this.getBatchConfigById(id, userCompanyCode);
if (!existing.success) {
return existing;
}
const existingConfig = await queryOne<any>( const existingConfig = await queryOne<any>(
`SELECT bc.*, `SELECT bc.*,
COALESCE( COALESCE(
@ -438,20 +416,13 @@ export class BatchService {
} }
/** /**
* ( , ) * ( )
*/ */
static async deleteBatchConfig( static async deleteBatchConfig(
id: number, id: number,
userId?: string, userId?: string
userCompanyCode?: string
): Promise<ApiResponse<void>> { ): Promise<ApiResponse<void>> {
try { try {
// 기존 배치 설정 확인 (회사 권한 체크 포함)
const existing = await this.getBatchConfigById(id, userCompanyCode);
if (!existing.success) {
return existing as ApiResponse<void>;
}
const existingConfig = await queryOne<any>( const existingConfig = await queryOne<any>(
`SELECT * FROM batch_configs WHERE id = $1`, `SELECT * FROM batch_configs WHERE id = $1`,
[id] [id]

View File

@ -604,13 +604,12 @@ export class CommonCodeService {
} }
/** /**
* () *
*/ */
async checkCategoryDuplicate( async checkCategoryDuplicate(
field: "categoryCode" | "categoryName" | "categoryNameEng", field: "categoryCode" | "categoryName" | "categoryNameEng",
value: string, value: string,
excludeCategoryCode?: string, excludeCategoryCode?: string
userCompanyCode?: string
): Promise<{ isDuplicate: boolean; message: string }> { ): Promise<{ isDuplicate: boolean; message: string }> {
try { try {
if (!value || !value.trim()) { if (!value || !value.trim()) {
@ -656,12 +655,6 @@ export class CommonCodeService {
break; break;
} }
// 회사별 필터링 (최고 관리자가 아닌 경우)
if (userCompanyCode && userCompanyCode !== "*") {
sql += ` AND company_code = $${paramIndex++}`;
values.push(userCompanyCode);
}
// 수정 시 자기 자신 제외 // 수정 시 자기 자신 제외
if (excludeCategoryCode) { if (excludeCategoryCode) {
sql += ` AND category_code != $${paramIndex++}`; sql += ` AND category_code != $${paramIndex++}`;
@ -682,10 +675,6 @@ export class CommonCodeService {
categoryNameEng: "카테고리 영문명", categoryNameEng: "카테고리 영문명",
}; };
logger.info(
`카테고리 중복 검사: ${field}=${value}, 회사=${userCompanyCode}, 중복=${isDuplicate}`
);
return { return {
isDuplicate, isDuplicate,
message: isDuplicate message: isDuplicate
@ -699,14 +688,13 @@ export class CommonCodeService {
} }
/** /**
* () *
*/ */
async checkCodeDuplicate( async checkCodeDuplicate(
categoryCode: string, categoryCode: string,
field: "codeValue" | "codeName" | "codeNameEng", field: "codeValue" | "codeName" | "codeNameEng",
value: string, value: string,
excludeCodeValue?: string, excludeCodeValue?: string
userCompanyCode?: string
): Promise<{ isDuplicate: boolean; message: string }> { ): Promise<{ isDuplicate: boolean; message: string }> {
try { try {
if (!value || !value.trim()) { if (!value || !value.trim()) {
@ -755,12 +743,6 @@ export class CommonCodeService {
break; break;
} }
// 회사별 필터링 (최고 관리자가 아닌 경우)
if (userCompanyCode && userCompanyCode !== "*") {
sql += ` AND company_code = $${paramIndex++}`;
values.push(userCompanyCode);
}
// 수정 시 자기 자신 제외 // 수정 시 자기 자신 제외
if (excludeCodeValue) { if (excludeCodeValue) {
sql += ` AND code_value != $${paramIndex++}`; sql += ` AND code_value != $${paramIndex++}`;
@ -778,10 +760,6 @@ export class CommonCodeService {
codeNameEng: "코드 영문명", codeNameEng: "코드 영문명",
}; };
logger.info(
`코드 중복 검사: ${categoryCode}.${field}=${value}, 회사=${userCompanyCode}, 중복=${isDuplicate}`
);
return { return {
isDuplicate, isDuplicate,
message: isDuplicate message: isDuplicate

View File

@ -17,8 +17,7 @@ export class ExternalDbConnectionService {
* DB * DB
*/ */
static async getConnections( static async getConnections(
filter: ExternalDbConnectionFilter, filter: ExternalDbConnectionFilter
userCompanyCode?: string
): Promise<ApiResponse<ExternalDbConnection[]>> { ): Promise<ApiResponse<ExternalDbConnection[]>> {
try { try {
// WHERE 조건 동적 생성 // WHERE 조건 동적 생성
@ -26,26 +25,6 @@ export class ExternalDbConnectionService {
const params: any[] = []; const params: any[] = [];
let paramIndex = 1; 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) { if (filter.db_type) {
whereConditions.push(`db_type = $${paramIndex++}`); whereConditions.push(`db_type = $${paramIndex++}`);
@ -57,6 +36,11 @@ export class ExternalDbConnectionService {
params.push(filter.is_active); 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()) { if (filter.search && filter.search.trim()) {
whereConditions.push( whereConditions.push(
@ -512,36 +496,23 @@ export class ExternalDbConnectionService {
/** /**
* DB ( ) * DB ( )
*/ */
static async deleteConnection( static async deleteConnection(id: number): Promise<ApiResponse<void>> {
id: number,
userCompanyCode?: string
): Promise<ApiResponse<void>> {
try { try {
let selectQuery = `SELECT id FROM external_db_connections WHERE id = $1`; const existingConnection = await queryOne(
const selectParams: any[] = [id]; `SELECT id FROM external_db_connections WHERE id = $1`,
[id]
// 회사별 필터링 (최고 관리자가 아닌 경우) );
if (userCompanyCode && userCompanyCode !== "*") {
selectQuery += ` AND company_code = $2`;
selectParams.push(userCompanyCode);
}
const existingConnection = await queryOne(selectQuery, selectParams);
if (!existingConnection) { if (!existingConnection) {
return { return {
success: false, success: false,
message: "해당 연결 설정을 찾을 수 없거나 권한이 없습니다.", message: "해당 연결 설정을 찾을 수 없습니다.",
}; };
} }
// 물리 삭제 (실제 데이터 삭제) // 물리 삭제 (실제 데이터 삭제)
await query(`DELETE FROM external_db_connections WHERE id = $1`, [id]); await query(`DELETE FROM external_db_connections WHERE id = $1`, [id]);
logger.info(
`외부 DB 연결 삭제: ID ${id} (회사: ${userCompanyCode || "전체"})`
);
return { return {
success: true, success: true,
message: "연결 설정이 삭제되었습니다.", message: "연결 설정이 삭제되었습니다.",
@ -776,11 +747,8 @@ export class ExternalDbConnectionService {
try { try {
// 보안 검증: SELECT 쿼리만 허용 // 보안 검증: SELECT 쿼리만 허용
const trimmedQuery = query.trim().toUpperCase(); const trimmedQuery = query.trim().toUpperCase();
if (!trimmedQuery.startsWith("SELECT")) { if (!trimmedQuery.startsWith('SELECT')) {
console.log("보안 오류: SELECT가 아닌 쿼리 시도:", { console.log("보안 오류: SELECT가 아닌 쿼리 시도:", { id, query: query.substring(0, 100) });
id,
query: query.substring(0, 100),
});
return { return {
success: false, success: false,
message: "외부 데이터베이스에서는 SELECT 쿼리만 실행할 수 있습니다.", message: "외부 데이터베이스에서는 SELECT 쿼리만 실행할 수 있습니다.",
@ -788,32 +756,16 @@ export class ExternalDbConnectionService {
} }
// 위험한 키워드 검사 // 위험한 키워드 검사
const dangerousKeywords = [ const dangerousKeywords = ['INSERT', 'UPDATE', 'DELETE', 'DROP', 'CREATE', 'ALTER', 'TRUNCATE', 'EXEC', 'EXECUTE', 'CALL', 'MERGE'];
"INSERT", const hasDangerousKeyword = dangerousKeywords.some(keyword =>
"UPDATE",
"DELETE",
"DROP",
"CREATE",
"ALTER",
"TRUNCATE",
"EXEC",
"EXECUTE",
"CALL",
"MERGE",
];
const hasDangerousKeyword = dangerousKeywords.some((keyword) =>
trimmedQuery.includes(keyword) trimmedQuery.includes(keyword)
); );
if (hasDangerousKeyword) { if (hasDangerousKeyword) {
console.log("보안 오류: 위험한 키워드 포함 쿼리 시도:", { console.log("보안 오류: 위험한 키워드 포함 쿼리 시도:", { id, query: query.substring(0, 100) });
id,
query: query.substring(0, 100),
});
return { return {
success: false, success: false,
message: message: "데이터를 변경하거나 삭제하는 쿼리는 허용되지 않습니다. SELECT 쿼리만 사용해주세요.",
"데이터를 변경하거나 삭제하는 쿼리는 허용되지 않습니다. SELECT 쿼리만 사용해주세요.",
}; };
} }

View File

@ -23,8 +23,7 @@ export class ExternalRestApiConnectionService {
* REST API * REST API
*/ */
static async getConnections( static async getConnections(
filter: ExternalRestApiConnectionFilter = {}, filter: ExternalRestApiConnectionFilter = {}
userCompanyCode?: string
): Promise<ApiResponse<ExternalRestApiConnection[]>> { ): Promise<ApiResponse<ExternalRestApiConnection[]>> {
try { try {
let query = ` let query = `
@ -40,28 +39,12 @@ export class ExternalRestApiConnectionService {
const params: any[] = []; const params: any[] = [];
let paramIndex = 1; let paramIndex = 1;
// 회사별 필터링 (최고 관리자가 아닌 경우 필수) // 회사 코드 필터
if (userCompanyCode && userCompanyCode !== "*") {
query += ` AND company_code = $${paramIndex}`;
params.push(userCompanyCode);
paramIndex++;
logger.info(`회사별 REST API 연결 필터링: ${userCompanyCode}`);
} else if (userCompanyCode === "*") {
logger.info(`최고 관리자: 모든 REST API 연결 조회`);
// 필터가 있으면 적용
if (filter.company_code) { if (filter.company_code) {
query += ` AND company_code = $${paramIndex}`; query += ` AND company_code = $${paramIndex}`;
params.push(filter.company_code); params.push(filter.company_code);
paramIndex++; paramIndex++;
} }
} else {
// userCompanyCode가 없는 경우 (하위 호환성)
if (filter.company_code) {
query += ` AND company_code = $${paramIndex}`;
params.push(filter.company_code);
paramIndex++;
}
}
// 활성 상태 필터 // 활성 상태 필터
if (filter.is_active) { if (filter.is_active) {
@ -122,11 +105,10 @@ export class ExternalRestApiConnectionService {
* REST API * REST API
*/ */
static async getConnectionById( static async getConnectionById(
id: number, id: number
userCompanyCode?: string
): Promise<ApiResponse<ExternalRestApiConnection>> { ): Promise<ApiResponse<ExternalRestApiConnection>> {
try { try {
let query = ` const query = `
SELECT SELECT
id, connection_name, description, base_url, default_headers, id, connection_name, description, base_url, default_headers,
auth_type, auth_config, timeout, retry_count, retry_delay, auth_type, auth_config, timeout, retry_count, retry_delay,
@ -136,20 +118,12 @@ export class ExternalRestApiConnectionService {
WHERE id = $1 WHERE id = $1
`; `;
const params: any[] = [id]; const result: QueryResult<any> = await pool.query(query, [id]);
// 회사별 필터링 (최고 관리자가 아닌 경우)
if (userCompanyCode && userCompanyCode !== "*") {
query += ` AND company_code = $2`;
params.push(userCompanyCode);
}
const result: QueryResult<any> = await pool.query(query, params);
if (result.rows.length === 0) { if (result.rows.length === 0) {
return { return {
success: false, success: false,
message: "연결을 찾을 수 없거나 권한이 없습니다.", message: "연결을 찾을 수 없습니다.",
}; };
} }
@ -251,12 +225,11 @@ export class ExternalRestApiConnectionService {
*/ */
static async updateConnection( static async updateConnection(
id: number, id: number,
data: Partial<ExternalRestApiConnection>, data: Partial<ExternalRestApiConnection>
userCompanyCode?: string
): Promise<ApiResponse<ExternalRestApiConnection>> { ): Promise<ApiResponse<ExternalRestApiConnection>> {
try { try {
// 기존 연결 확인 (회사 코드로 권한 체크) // 기존 연결 확인
const existing = await this.getConnectionById(id, userCompanyCode); const existing = await this.getConnectionById(id);
if (!existing.success) { if (!existing.success) {
return existing; return existing;
} }
@ -380,38 +353,24 @@ export class ExternalRestApiConnectionService {
/** /**
* REST API * REST API
*/ */
static async deleteConnection( static async deleteConnection(id: number): Promise<ApiResponse<void>> {
id: number,
userCompanyCode?: string
): Promise<ApiResponse<void>> {
try { try {
let query = ` const query = `
DELETE FROM external_rest_api_connections DELETE FROM external_rest_api_connections
WHERE id = $1 WHERE id = $1
RETURNING connection_name
`; `;
const params: any[] = [id]; const result: QueryResult<any> = await pool.query(query, [id]);
// 회사별 필터링 (최고 관리자가 아닌 경우)
if (userCompanyCode && userCompanyCode !== "*") {
query += ` AND company_code = $2`;
params.push(userCompanyCode);
}
query += ` RETURNING connection_name`;
const result: QueryResult<any> = await pool.query(query, params);
if (result.rows.length === 0) { if (result.rows.length === 0) {
return { return {
success: false, success: false,
message: "연결을 찾을 수 없거나 권한이 없습니다.", message: "연결을 찾을 수 없습니다.",
}; };
} }
logger.info( logger.info(`REST API 연결 삭제 성공: ${result.rows[0].connection_name}`);
`REST API 연결 삭제 성공: ${result.rows[0].connection_name} (회사: ${userCompanyCode || "전체"})`
);
return { return {
success: true, success: true,

View File

@ -127,12 +127,6 @@ export function FlowWidget({
// 컬럼 라벨 조회 // 컬럼 라벨 조회
const labelsResponse = await getStepColumnLabels(flowId, selectedStepId); 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) { if (labelsResponse.success && labelsResponse.data) {
setColumnLabels(labelsResponse.data); setColumnLabels(labelsResponse.data);
} }
@ -226,12 +220,6 @@ export function FlowWidget({
try { try {
// 컬럼 라벨 조회 // 컬럼 라벨 조회
const labelsResponse = await getStepColumnLabels(flowId!, firstStep.id); 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) { if (labelsResponse.success && labelsResponse.data) {
setColumnLabels(labelsResponse.data); setColumnLabels(labelsResponse.data);
} }
@ -309,16 +297,9 @@ export function FlowWidget({
try { try {
// 컬럼 라벨 조회 // 컬럼 라벨 조회
const labelsResponse = await getStepColumnLabels(flowId!, stepId); 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) { if (labelsResponse.success && labelsResponse.data) {
setColumnLabels(labelsResponse.data); setColumnLabels(labelsResponse.data);
} else { } else {
console.warn("⚠️ 컬럼 라벨 조회 실패 또는 데이터 없음:", labelsResponse);
setColumnLabels({}); setColumnLabels({});
} }