배치 UPSERT 기능 및 고정값 매핑 버그 수정
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
// 배치관리 전용 컨트롤러 (기존 소스와 완전 분리)
|
||||
// 작성일: 2024-12-24
|
||||
|
||||
import { Response } from "express";
|
||||
import { Request, Response } from "express";
|
||||
import { AuthenticatedRequest } from "../types/auth";
|
||||
import {
|
||||
BatchManagementService,
|
||||
@@ -13,6 +13,7 @@ import { BatchService } from "../services/batchService";
|
||||
import { BatchSchedulerService } from "../services/batchSchedulerService";
|
||||
import { BatchExternalDbService } from "../services/batchExternalDbService";
|
||||
import { CreateBatchConfigRequest, BatchConfig } from "../types/batchTypes";
|
||||
import { query } from "../database/db";
|
||||
|
||||
export class BatchManagementController {
|
||||
/**
|
||||
@@ -422,6 +423,8 @@ export class BatchManagementController {
|
||||
paramValue,
|
||||
paramSource,
|
||||
requestBody,
|
||||
authServiceName, // DB에서 토큰 가져올 서비스명
|
||||
dataArrayPath, // 데이터 배열 경로 (예: response, data.items)
|
||||
} = req.body;
|
||||
|
||||
// apiUrl, endpoint는 항상 필수
|
||||
@@ -432,15 +435,36 @@ export class BatchManagementController {
|
||||
});
|
||||
}
|
||||
|
||||
// GET 요청일 때만 API Key 필수 (POST/PUT/DELETE는 선택)
|
||||
if ((!method || method === "GET") && !apiKey) {
|
||||
// 토큰 결정: authServiceName이 있으면 DB에서 조회, 없으면 apiKey 사용
|
||||
let finalApiKey = apiKey || "";
|
||||
if (authServiceName) {
|
||||
// DB에서 토큰 조회
|
||||
const tokenResult = await query<{ access_token: string }>(
|
||||
`SELECT access_token FROM auth_tokens
|
||||
WHERE service_name = $1
|
||||
ORDER BY created_date DESC LIMIT 1`,
|
||||
[authServiceName]
|
||||
);
|
||||
if (tokenResult.length > 0 && tokenResult[0].access_token) {
|
||||
finalApiKey = tokenResult[0].access_token;
|
||||
console.log(`auth_tokens에서 토큰 조회 성공: ${authServiceName}`);
|
||||
} else {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: `서비스 '${authServiceName}'의 토큰을 찾을 수 없습니다. 먼저 토큰 저장 배치를 실행하세요.`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 토큰이 없으면 에러 (직접 입력도 안 하고 DB 선택도 안 한 경우)
|
||||
if (!finalApiKey) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: "GET 메서드에서는 API Key가 필요합니다.",
|
||||
message: "인증 토큰이 필요합니다. 직접 입력하거나 DB에서 선택하세요.",
|
||||
});
|
||||
}
|
||||
|
||||
console.log("🔍 REST API 미리보기 요청:", {
|
||||
console.log("REST API 미리보기 요청:", {
|
||||
apiUrl,
|
||||
endpoint,
|
||||
method,
|
||||
@@ -449,6 +473,8 @@ export class BatchManagementController {
|
||||
paramValue,
|
||||
paramSource,
|
||||
requestBody: requestBody ? "Included" : "None",
|
||||
authServiceName: authServiceName || "직접 입력",
|
||||
dataArrayPath: dataArrayPath || "전체 응답",
|
||||
});
|
||||
|
||||
// RestApiConnector 사용하여 데이터 조회
|
||||
@@ -456,7 +482,7 @@ export class BatchManagementController {
|
||||
|
||||
const connector = new RestApiConnector({
|
||||
baseUrl: apiUrl,
|
||||
apiKey: apiKey || "",
|
||||
apiKey: finalApiKey,
|
||||
timeout: 30000,
|
||||
});
|
||||
|
||||
@@ -511,8 +537,50 @@ export class BatchManagementController {
|
||||
result.rows && result.rows.length > 0 ? result.rows[0] : "no data",
|
||||
});
|
||||
|
||||
const data = result.rows.slice(0, 5); // 최대 5개 샘플만
|
||||
console.log(`[previewRestApiData] 슬라이스된 데이터:`, data);
|
||||
// 데이터 배열 추출 헬퍼 함수
|
||||
const getValueByPath = (obj: any, path: string): any => {
|
||||
if (!path) return obj;
|
||||
const keys = path.split(".");
|
||||
let current = obj;
|
||||
for (const key of keys) {
|
||||
if (current === null || current === undefined) return undefined;
|
||||
current = current[key];
|
||||
}
|
||||
return current;
|
||||
};
|
||||
|
||||
// dataArrayPath가 있으면 해당 경로에서 배열 추출
|
||||
let extractedData: any[] = [];
|
||||
if (dataArrayPath) {
|
||||
// result.rows가 단일 객체일 수 있음 (API 응답 전체)
|
||||
const rawData = result.rows.length === 1 ? result.rows[0] : result.rows;
|
||||
const arrayData = getValueByPath(rawData, dataArrayPath);
|
||||
|
||||
if (Array.isArray(arrayData)) {
|
||||
extractedData = arrayData;
|
||||
console.log(
|
||||
`[previewRestApiData] '${dataArrayPath}' 경로에서 ${arrayData.length}개 항목 추출`
|
||||
);
|
||||
} else {
|
||||
console.warn(
|
||||
`[previewRestApiData] '${dataArrayPath}' 경로가 배열이 아님:`,
|
||||
typeof arrayData
|
||||
);
|
||||
// 배열이 아니면 단일 객체로 처리
|
||||
if (arrayData) {
|
||||
extractedData = [arrayData];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// dataArrayPath가 없으면 기존 로직 사용
|
||||
extractedData = result.rows;
|
||||
}
|
||||
|
||||
const data = extractedData.slice(0, 5); // 최대 5개 샘플만
|
||||
console.log(
|
||||
`[previewRestApiData] 슬라이스된 데이터 (${extractedData.length}개 중 ${data.length}개):`,
|
||||
data
|
||||
);
|
||||
|
||||
if (data.length > 0) {
|
||||
// 첫 번째 객체에서 필드명 추출
|
||||
@@ -524,9 +592,9 @@ export class BatchManagementController {
|
||||
data: {
|
||||
fields: fields,
|
||||
samples: data,
|
||||
totalCount: result.rowCount || data.length,
|
||||
totalCount: extractedData.length,
|
||||
},
|
||||
message: `${fields.length}개 필드, ${result.rowCount || data.length}개 레코드를 조회했습니다.`,
|
||||
message: `${fields.length}개 필드, ${extractedData.length}개 레코드를 조회했습니다.`,
|
||||
});
|
||||
} else {
|
||||
return res.json({
|
||||
@@ -554,8 +622,17 @@ 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,
|
||||
authServiceName,
|
||||
dataArrayPath,
|
||||
saveMode,
|
||||
conflictKey,
|
||||
} = req.body;
|
||||
|
||||
if (
|
||||
!batchName ||
|
||||
@@ -576,6 +653,10 @@ export class BatchManagementController {
|
||||
cronSchedule,
|
||||
description,
|
||||
apiMappings,
|
||||
authServiceName,
|
||||
dataArrayPath,
|
||||
saveMode,
|
||||
conflictKey,
|
||||
});
|
||||
|
||||
// 🔐 멀티테넌시: 현재 사용자 회사 코드 사용 (프론트에서 받지 않음)
|
||||
@@ -589,6 +670,10 @@ export class BatchManagementController {
|
||||
cronSchedule: cronSchedule,
|
||||
isActive: "Y",
|
||||
companyCode,
|
||||
authServiceName: authServiceName || undefined,
|
||||
dataArrayPath: dataArrayPath || undefined,
|
||||
saveMode: saveMode || "INSERT",
|
||||
conflictKey: conflictKey || undefined,
|
||||
mappings: apiMappings,
|
||||
};
|
||||
|
||||
@@ -625,4 +710,31 @@ export class BatchManagementController {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 인증 토큰 서비스명 목록 조회
|
||||
*/
|
||||
static async getAuthServiceNames(req: Request, res: Response) {
|
||||
try {
|
||||
const result = await query<{ service_name: string }>(
|
||||
`SELECT DISTINCT service_name
|
||||
FROM auth_tokens
|
||||
WHERE service_name IS NOT NULL
|
||||
ORDER BY service_name`
|
||||
);
|
||||
|
||||
const serviceNames = result.map((row) => row.service_name);
|
||||
|
||||
return res.json({
|
||||
success: true,
|
||||
data: serviceNames,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("인증 서비스 목록 조회 오류:", error);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: "인증 서비스 목록 조회 중 오류가 발생했습니다.",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user