feature/screen-management #154
|
|
@ -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: "배치 설정 삭제에 실패했습니다.",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 : "알 수 없는 오류",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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: "배치 저장 중 오류가 발생했습니다.",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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({
|
||||
|
|
|
|||
|
|
@ -557,9 +557,14 @@ export class FlowController {
|
|||
getStepColumnLabels = async (req: Request, res: Response): Promise<void> => {
|
||||
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<string, string> = {};
|
||||
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",
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -13,10 +13,11 @@ import { ApiResponse } from "../types/batchTypes";
|
|||
|
||||
export class BatchExecutionLogService {
|
||||
/**
|
||||
* 배치 실행 로그 목록 조회
|
||||
* 배치 실행 로그 목록 조회 (회사별)
|
||||
*/
|
||||
static async getExecutionLogs(
|
||||
filter: BatchExecutionLogFilter = {}
|
||||
filter: BatchExecutionLogFilter = {},
|
||||
userCompanyCode?: string
|
||||
): Promise<ApiResponse<BatchExecutionLogWithConfig[]>> {
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -35,11 +35,11 @@ export interface BatchApiResponse<T = unknown> {
|
|||
|
||||
export class BatchManagementService {
|
||||
/**
|
||||
* 배치관리용 연결 목록 조회
|
||||
* 배치관리용 연결 목록 조회 (회사별)
|
||||
*/
|
||||
static async getAvailableConnections(): Promise<
|
||||
BatchApiResponse<BatchConnectionInfo[]>
|
||||
> {
|
||||
static async getAvailableConnections(
|
||||
userCompanyCode?: string
|
||||
): Promise<BatchApiResponse<BatchConnectionInfo[]>> {
|
||||
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<BatchApiResponse<BatchTableInfo[]>> {
|
||||
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<BatchApiResponse<BatchColumnInfo[]>> {
|
||||
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<BatchApiResponse<BatchTableInfo[]>> {
|
||||
try {
|
||||
// 연결 정보 조회
|
||||
const connection = await queryOne<any>(
|
||||
`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<any>(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<BatchApiResponse<BatchColumnInfo[]>> {
|
||||
try {
|
||||
console.log(
|
||||
`[BatchManagementService] getExternalTableColumns 호출: connectionId=${connectionId}, tableName=${tableName}`
|
||||
);
|
||||
|
||||
// 연결 정보 조회
|
||||
const connection = await queryOne<any>(
|
||||
`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<any>(query_sql, params);
|
||||
|
||||
if (!connection) {
|
||||
console.log(
|
||||
`[BatchManagementService] 연결 정보를 찾을 수 없음: connectionId=${connectionId}`
|
||||
`[BatchManagementService] 연결 정보를 찾을 수 없거나 권한이 없음: connectionId=${connectionId}`
|
||||
);
|
||||
return {
|
||||
success: false,
|
||||
|
|
|
|||
|
|
@ -20,27 +20,33 @@ import { DbConnectionManager } from "./dbConnectionManager";
|
|||
|
||||
export class BatchService {
|
||||
/**
|
||||
* 배치 설정 목록 조회
|
||||
* 배치 설정 목록 조회 (회사별)
|
||||
*/
|
||||
static async getBatchConfigs(
|
||||
filter: BatchConfigFilter
|
||||
filter: BatchConfigFilter,
|
||||
userCompanyCode?: string
|
||||
): Promise<ApiResponse<BatchConfig[]>> {
|
||||
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<ApiResponse<BatchConfig>> {
|
||||
try {
|
||||
const batchConfig = await queryOne<any>(
|
||||
`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<any>(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<ApiResponse<BatchConfig>> {
|
||||
try {
|
||||
// 기존 배치 설정 확인
|
||||
// 기존 배치 설정 확인 (회사 권한 체크 포함)
|
||||
const existing = await this.getBatchConfigById(id, userCompanyCode);
|
||||
if (!existing.success) {
|
||||
return existing;
|
||||
}
|
||||
|
||||
const existingConfig = await queryOne<any>(
|
||||
`SELECT bc.*,
|
||||
COALESCE(
|
||||
|
|
@ -416,13 +438,20 @@ export class BatchService {
|
|||
}
|
||||
|
||||
/**
|
||||
* 배치 설정 삭제 (논리 삭제)
|
||||
* 배치 설정 삭제 (논리 삭제, 회사별)
|
||||
*/
|
||||
static async deleteBatchConfig(
|
||||
id: number,
|
||||
userId?: string
|
||||
userId?: string,
|
||||
userCompanyCode?: string
|
||||
): Promise<ApiResponse<void>> {
|
||||
try {
|
||||
// 기존 배치 설정 확인 (회사 권한 체크 포함)
|
||||
const existing = await this.getBatchConfigById(id, userCompanyCode);
|
||||
if (!existing.success) {
|
||||
return existing as ApiResponse<void>;
|
||||
}
|
||||
|
||||
const existingConfig = await queryOne<any>(
|
||||
`SELECT * FROM batch_configs WHERE id = $1`,
|
||||
[id]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -17,7 +17,8 @@ export class ExternalDbConnectionService {
|
|||
* 외부 DB 연결 목록 조회
|
||||
*/
|
||||
static async getConnections(
|
||||
filter: ExternalDbConnectionFilter
|
||||
filter: ExternalDbConnectionFilter,
|
||||
userCompanyCode?: string
|
||||
): Promise<ApiResponse<ExternalDbConnection[]>> {
|
||||
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<ApiResponse<void>> {
|
||||
static async deleteConnection(
|
||||
id: number,
|
||||
userCompanyCode?: string
|
||||
): Promise<ApiResponse<void>> {
|
||||
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 쿼리만 사용해주세요.",
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,8 @@ export class ExternalRestApiConnectionService {
|
|||
* REST API 연결 목록 조회
|
||||
*/
|
||||
static async getConnections(
|
||||
filter: ExternalRestApiConnectionFilter = {}
|
||||
filter: ExternalRestApiConnectionFilter = {},
|
||||
userCompanyCode?: string
|
||||
): Promise<ApiResponse<ExternalRestApiConnection[]>> {
|
||||
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<ApiResponse<ExternalRestApiConnection>> {
|
||||
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<any> = await pool.query(query, [id]);
|
||||
const params: any[] = [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) {
|
||||
return {
|
||||
success: false,
|
||||
message: "연결을 찾을 수 없습니다.",
|
||||
message: "연결을 찾을 수 없거나 권한이 없습니다.",
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -225,11 +251,12 @@ export class ExternalRestApiConnectionService {
|
|||
*/
|
||||
static async updateConnection(
|
||||
id: number,
|
||||
data: Partial<ExternalRestApiConnection>
|
||||
data: Partial<ExternalRestApiConnection>,
|
||||
userCompanyCode?: string
|
||||
): Promise<ApiResponse<ExternalRestApiConnection>> {
|
||||
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<ApiResponse<void>> {
|
||||
static async deleteConnection(
|
||||
id: number,
|
||||
userCompanyCode?: string
|
||||
): Promise<ApiResponse<void>> {
|
||||
try {
|
||||
const query = `
|
||||
let query = `
|
||||
DELETE FROM external_rest_api_connections
|
||||
WHERE id = $1
|
||||
RETURNING connection_name
|
||||
`;
|
||||
|
||||
const result: QueryResult<any> = 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<any> = 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,
|
||||
|
|
|
|||
|
|
@ -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({});
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue