get요청 db 저장기능
This commit is contained in:
parent
c9afdec09f
commit
0fca8cd90b
|
|
@ -92,7 +92,7 @@ app.use(
|
||||||
// Rate Limiting (개발 환경에서는 완화)
|
// Rate Limiting (개발 환경에서는 완화)
|
||||||
const limiter = rateLimit({
|
const limiter = rateLimit({
|
||||||
windowMs: 1 * 60 * 1000, // 1분
|
windowMs: 1 * 60 * 1000, // 1분
|
||||||
max: config.nodeEnv === "development" ? 10000 : 1000, // 개발환경에서는 10000으로 증가, 운영환경에서는 100
|
max: config.nodeEnv === "development" ? 10000 : 100, // 개발환경에서는 10000으로 증가, 운영환경에서는 100
|
||||||
message: {
|
message: {
|
||||||
error: "너무 많은 요청이 발생했습니다. 잠시 후 다시 시도해주세요.",
|
error: "너무 많은 요청이 발생했습니다. 잠시 후 다시 시도해주세요.",
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ interface DataMappingSettingsProps {
|
||||||
httpMethod: string;
|
httpMethod: string;
|
||||||
availableTables?: TableInfo[];
|
availableTables?: TableInfo[];
|
||||||
readonly?: boolean;
|
readonly?: boolean;
|
||||||
|
tablesLoading?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DataMappingSettings: React.FC<DataMappingSettingsProps> = ({
|
export const DataMappingSettings: React.FC<DataMappingSettingsProps> = ({
|
||||||
|
|
@ -38,6 +39,7 @@ export const DataMappingSettings: React.FC<DataMappingSettingsProps> = ({
|
||||||
httpMethod,
|
httpMethod,
|
||||||
availableTables = [],
|
availableTables = [],
|
||||||
readonly = false,
|
readonly = false,
|
||||||
|
tablesLoading = false,
|
||||||
}) => {
|
}) => {
|
||||||
const [localConfig, setLocalConfig] = useState<DataMappingConfig>(config);
|
const [localConfig, setLocalConfig] = useState<DataMappingConfig>(config);
|
||||||
|
|
||||||
|
|
@ -228,17 +230,27 @@ export const DataMappingSettings: React.FC<DataMappingSettingsProps> = ({
|
||||||
<Select
|
<Select
|
||||||
value={localConfig.inboundMapping?.targetTable || ""}
|
value={localConfig.inboundMapping?.targetTable || ""}
|
||||||
onValueChange={(value) => handleInboundMappingChange({ targetTable: value })}
|
onValueChange={(value) => handleInboundMappingChange({ targetTable: value })}
|
||||||
disabled={readonly}
|
disabled={readonly || tablesLoading}
|
||||||
>
|
>
|
||||||
<SelectTrigger>
|
<SelectTrigger>
|
||||||
<SelectValue placeholder="저장할 테이블을 선택하세요" />
|
<SelectValue placeholder={tablesLoading ? "테이블 목록 로딩 중..." : "저장할 테이블을 선택하세요"} />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
{availableTables.map((table) => (
|
{tablesLoading ? (
|
||||||
<SelectItem key={table.name} value={table.name}>
|
<SelectItem value="" disabled>
|
||||||
{table.displayName || table.name}
|
테이블 목록 로딩 중...
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
) : availableTables.length === 0 ? (
|
||||||
|
<SelectItem value="" disabled>
|
||||||
|
사용 가능한 테이블이 없습니다
|
||||||
|
</SelectItem>
|
||||||
|
) : (
|
||||||
|
availableTables.map((table) => (
|
||||||
|
<SelectItem key={table.name} value={table.name}>
|
||||||
|
{table.displayName || table.name}
|
||||||
|
</SelectItem>
|
||||||
|
))
|
||||||
|
)}
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,10 @@ import {
|
||||||
} from "@/types/external-call/ExternalCallTypes";
|
} from "@/types/external-call/ExternalCallTypes";
|
||||||
import { DataMappingConfig, TableInfo } from "@/types/external-call/DataMappingTypes";
|
import { DataMappingConfig, TableInfo } from "@/types/external-call/DataMappingTypes";
|
||||||
|
|
||||||
|
// API import
|
||||||
|
import { DataFlowAPI } from "@/lib/api/dataflow";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
|
||||||
// 하위 컴포넌트 import
|
// 하위 컴포넌트 import
|
||||||
import RestApiSettings from "./RestApiSettings";
|
import RestApiSettings from "./RestApiSettings";
|
||||||
import ExternalCallTestPanel from "./ExternalCallTestPanel";
|
import ExternalCallTestPanel from "./ExternalCallTestPanel";
|
||||||
|
|
@ -41,8 +45,14 @@ const ExternalCallPanel: React.FC<ExternalCallPanelProps> = ({
|
||||||
});
|
});
|
||||||
// 상태 관리
|
// 상태 관리
|
||||||
const [config, setConfig] = useState<ExternalCallConfig>(
|
const [config, setConfig] = useState<ExternalCallConfig>(
|
||||||
() =>
|
() => {
|
||||||
initialSettings || {
|
if (initialSettings) {
|
||||||
|
console.log("🔄 [ExternalCallPanel] 기존 설정 로드:", initialSettings);
|
||||||
|
return initialSettings;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("🔄 [ExternalCallPanel] 기본 설정 사용");
|
||||||
|
return {
|
||||||
callType: "rest-api",
|
callType: "rest-api",
|
||||||
restApiSettings: {
|
restApiSettings: {
|
||||||
apiUrl: "",
|
apiUrl: "",
|
||||||
|
|
@ -63,7 +73,8 @@ const ExternalCallPanel: React.FC<ExternalCallPanelProps> = ({
|
||||||
timeout: 30000, // 30초
|
timeout: 30000, // 30초
|
||||||
retryCount: 3,
|
retryCount: 3,
|
||||||
},
|
},
|
||||||
},
|
};
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const [activeTab, setActiveTab] = useState<string>("settings");
|
const [activeTab, setActiveTab] = useState<string>("settings");
|
||||||
|
|
@ -71,48 +82,69 @@ const ExternalCallPanel: React.FC<ExternalCallPanelProps> = ({
|
||||||
const [isConfigValid, setIsConfigValid] = useState<boolean>(false);
|
const [isConfigValid, setIsConfigValid] = useState<boolean>(false);
|
||||||
|
|
||||||
// 데이터 매핑 상태
|
// 데이터 매핑 상태
|
||||||
const [dataMappingConfig, setDataMappingConfig] = useState<DataMappingConfig>(() => ({
|
const [dataMappingConfig, setDataMappingConfig] = useState<DataMappingConfig>(() => {
|
||||||
direction: "none",
|
// initialSettings에서 데이터 매핑 정보 불러오기
|
||||||
}));
|
if (initialSettings?.dataMappingConfig) {
|
||||||
|
console.log("🔄 [ExternalCallPanel] 기존 데이터 매핑 설정 로드:", initialSettings.dataMappingConfig);
|
||||||
|
return initialSettings.dataMappingConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("🔄 [ExternalCallPanel] 기본 데이터 매핑 설정 사용");
|
||||||
|
return {
|
||||||
|
direction: "none",
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
// 사용 가능한 테이블 목록 (임시 데이터)
|
// 사용 가능한 테이블 목록 (실제 API에서 로드)
|
||||||
const [availableTables] = useState<TableInfo[]>([
|
const [availableTables, setAvailableTables] = useState<TableInfo[]>([]);
|
||||||
{
|
const [tablesLoading, setTablesLoading] = useState(false);
|
||||||
name: "customers",
|
|
||||||
displayName: "고객",
|
// 테이블 목록 로드
|
||||||
fields: [
|
useEffect(() => {
|
||||||
{ name: "id", dataType: "number", nullable: false, isPrimaryKey: true },
|
const loadTables = async () => {
|
||||||
{ name: "name", dataType: "string", nullable: false },
|
try {
|
||||||
{ name: "email", dataType: "string", nullable: true },
|
setTablesLoading(true);
|
||||||
{ name: "phone", dataType: "string", nullable: true },
|
const tables = await DataFlowAPI.getTables();
|
||||||
{ name: "created_at", dataType: "date", nullable: false },
|
|
||||||
],
|
// 테이블 정보를 TableInfo 형식으로 변환
|
||||||
},
|
const tableInfos: TableInfo[] = await Promise.all(
|
||||||
{
|
tables.map(async (table) => {
|
||||||
name: "orders",
|
try {
|
||||||
displayName: "주문",
|
const columns = await DataFlowAPI.getTableColumns(table.tableName);
|
||||||
fields: [
|
return {
|
||||||
{ name: "id", dataType: "number", nullable: false, isPrimaryKey: true },
|
name: table.tableName,
|
||||||
{ name: "customer_id", dataType: "number", nullable: false },
|
displayName: table.displayName || table.tableName,
|
||||||
{ name: "product_name", dataType: "string", nullable: false },
|
fields: columns.map((col) => ({
|
||||||
{ name: "quantity", dataType: "number", nullable: false },
|
name: col.columnName,
|
||||||
{ name: "price", dataType: "number", nullable: false },
|
dataType: col.dataType,
|
||||||
{ name: "status", dataType: "string", nullable: false },
|
nullable: col.nullable,
|
||||||
{ name: "order_date", dataType: "date", nullable: false },
|
isPrimaryKey: col.isPrimaryKey || false,
|
||||||
],
|
})),
|
||||||
},
|
};
|
||||||
{
|
} catch (error) {
|
||||||
name: "products",
|
console.warn(`테이블 ${table.tableName} 컬럼 정보 로드 실패:`, error);
|
||||||
displayName: "제품",
|
return {
|
||||||
fields: [
|
name: table.tableName,
|
||||||
{ name: "id", dataType: "number", nullable: false, isPrimaryKey: true },
|
displayName: table.displayName || table.tableName,
|
||||||
{ name: "name", dataType: "string", nullable: false },
|
fields: [],
|
||||||
{ name: "price", dataType: "number", nullable: false },
|
};
|
||||||
{ name: "stock", dataType: "number", nullable: false },
|
}
|
||||||
{ name: "category", dataType: "string", nullable: true },
|
})
|
||||||
],
|
);
|
||||||
},
|
|
||||||
]);
|
setAvailableTables(tableInfos);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("테이블 목록 로드 실패:", error);
|
||||||
|
toast.error("테이블 목록을 불러오는데 실패했습니다.");
|
||||||
|
// 실패 시 빈 배열로 설정
|
||||||
|
setAvailableTables([]);
|
||||||
|
} finally {
|
||||||
|
setTablesLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
loadTables();
|
||||||
|
}, []);
|
||||||
|
|
||||||
// 설정 변경 핸들러
|
// 설정 변경 핸들러
|
||||||
const handleRestApiSettingsChange = useCallback(
|
const handleRestApiSettingsChange = useCallback(
|
||||||
|
|
@ -136,6 +168,22 @@ const ExternalCallPanel: React.FC<ExternalCallPanelProps> = ({
|
||||||
[config, onSettingsChange, dataMappingConfig],
|
[config, onSettingsChange, dataMappingConfig],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// 데이터 매핑 설정 변경 핸들러
|
||||||
|
const handleDataMappingConfigChange = useCallback(
|
||||||
|
(newMappingConfig: DataMappingConfig) => {
|
||||||
|
console.log("🔄 [ExternalCallPanel] 데이터 매핑 설정 변경:", newMappingConfig);
|
||||||
|
|
||||||
|
setDataMappingConfig(newMappingConfig);
|
||||||
|
|
||||||
|
// 전체 설정에 데이터 매핑 정보 포함하여 상위로 전달
|
||||||
|
onSettingsChange({
|
||||||
|
...config,
|
||||||
|
dataMappingConfig: newMappingConfig,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[config, onSettingsChange],
|
||||||
|
);
|
||||||
|
|
||||||
// 테스트 결과 핸들러
|
// 테스트 결과 핸들러
|
||||||
const handleTestResult = useCallback((result: ApiTestResult) => {
|
const handleTestResult = useCallback((result: ApiTestResult) => {
|
||||||
setLastTestResult(result);
|
setLastTestResult(result);
|
||||||
|
|
@ -225,10 +273,11 @@ const ExternalCallPanel: React.FC<ExternalCallPanelProps> = ({
|
||||||
<TabsContent value="mapping" className="flex-1 space-y-2 overflow-y-auto">
|
<TabsContent value="mapping" className="flex-1 space-y-2 overflow-y-auto">
|
||||||
<DataMappingSettings
|
<DataMappingSettings
|
||||||
config={dataMappingConfig}
|
config={dataMappingConfig}
|
||||||
onConfigChange={setDataMappingConfig}
|
onConfigChange={handleDataMappingConfigChange}
|
||||||
httpMethod={config.restApiSettings?.httpMethod || "GET"}
|
httpMethod={config.restApiSettings?.httpMethod || "GET"}
|
||||||
availableTables={availableTables}
|
availableTables={availableTables}
|
||||||
readonly={readonly}
|
readonly={readonly}
|
||||||
|
tablesLoading={tablesLoading}
|
||||||
/>
|
/>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -271,6 +271,14 @@ export class ImprovedButtonActionExecutor {
|
||||||
// 2. 관계 타입에 따른 실행
|
// 2. 관계 타입에 따른 실행
|
||||||
const relationships = relationshipData.relationships;
|
const relationships = relationshipData.relationships;
|
||||||
const connectionType = relationships.connectionType;
|
const connectionType = relationships.connectionType;
|
||||||
|
|
||||||
|
console.log(`🔍 관계 상세 정보:`, {
|
||||||
|
connectionType,
|
||||||
|
hasExternalCallConfig: !!relationships.externalCallConfig,
|
||||||
|
externalCallConfig: relationships.externalCallConfig,
|
||||||
|
hasDataSaveConfig: !!relationships.dataSaveConfig,
|
||||||
|
dataSaveConfig: relationships.dataSaveConfig,
|
||||||
|
});
|
||||||
|
|
||||||
let result: ExecutionResult;
|
let result: ExecutionResult;
|
||||||
|
|
||||||
|
|
@ -339,8 +347,13 @@ export class ImprovedButtonActionExecutor {
|
||||||
context: ButtonExecutionContext
|
context: ButtonExecutionContext
|
||||||
): Promise<ExecutionResult> {
|
): Promise<ExecutionResult> {
|
||||||
try {
|
try {
|
||||||
|
console.log(`🔍 외부 호출 실행 시작 - relationships 구조:`, relationships);
|
||||||
|
|
||||||
const externalCallConfig = relationships.externalCallConfig;
|
const externalCallConfig = relationships.externalCallConfig;
|
||||||
|
console.log(`🔍 externalCallConfig:`, externalCallConfig);
|
||||||
|
|
||||||
if (!externalCallConfig) {
|
if (!externalCallConfig) {
|
||||||
|
console.error('❌ 외부 호출 설정이 없습니다. relationships 구조:', relationships);
|
||||||
throw new Error('외부 호출 설정이 없습니다');
|
throw new Error('외부 호출 설정이 없습니다');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -394,7 +407,7 @@ export class ImprovedButtonActionExecutor {
|
||||||
timeout: restApiSettings.timeout || 30000,
|
timeout: restApiSettings.timeout || 30000,
|
||||||
retryCount: restApiSettings.retryCount || 3,
|
retryCount: restApiSettings.retryCount || 3,
|
||||||
},
|
},
|
||||||
templateData: restApiSettings.httpMethod !== 'GET' ? JSON.parse(requestBody) : {},
|
templateData: restApiSettings.httpMethod !== 'GET' && requestBody ? JSON.parse(requestBody) : formData,
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log(`📤 백엔드로 전송할 데이터:`, requestPayload);
|
console.log(`📤 백엔드로 전송할 데이터:`, requestPayload);
|
||||||
|
|
@ -412,11 +425,17 @@ export class ImprovedButtonActionExecutor {
|
||||||
|
|
||||||
// 데이터 매핑 처리 (inbound mapping)
|
// 데이터 매핑 처리 (inbound mapping)
|
||||||
if (externalCallConfig.dataMappingConfig?.inboundMapping) {
|
if (externalCallConfig.dataMappingConfig?.inboundMapping) {
|
||||||
|
console.log(`📥 데이터 매핑 설정 발견 - HTTP 메서드: ${restApiSettings.httpMethod}`);
|
||||||
|
console.log(`📥 매핑 설정:`, externalCallConfig.dataMappingConfig.inboundMapping);
|
||||||
|
console.log(`📥 응답 데이터:`, responseData);
|
||||||
|
|
||||||
await this.processInboundMapping(
|
await this.processInboundMapping(
|
||||||
externalCallConfig.dataMappingConfig.inboundMapping,
|
externalCallConfig.dataMappingConfig.inboundMapping,
|
||||||
responseData,
|
responseData,
|
||||||
context
|
context
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
console.log(`ℹ️ 데이터 매핑 설정이 없습니다 - HTTP 메서드: ${restApiSettings.httpMethod}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
@ -596,28 +615,24 @@ export class ImprovedButtonActionExecutor {
|
||||||
connection?: any
|
connection?: any
|
||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
try {
|
try {
|
||||||
// 데이터 저장 API 호출
|
console.log(`💾 테이블 데이터 저장 시작: ${tableName}`, {
|
||||||
const response = await fetch('/api/dataflow/execute-data-action', {
|
actionType,
|
||||||
method: 'POST',
|
data,
|
||||||
headers: {
|
connection
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'Authorization': `Bearer ${localStorage.getItem('token')}`,
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
tableName,
|
|
||||||
data,
|
|
||||||
actionType,
|
|
||||||
connection,
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
// 데이터 저장 API 호출 (apiClient 사용)
|
||||||
throw new Error(`데이터 저장 API 호출 실패: ${response.status}`);
|
const response = await apiClient.post('/dataflow/execute-data-action', {
|
||||||
}
|
tableName,
|
||||||
|
data,
|
||||||
|
actionType,
|
||||||
|
connection,
|
||||||
|
});
|
||||||
|
|
||||||
return await response.json();
|
console.log(`✅ 테이블 데이터 저장 성공: ${tableName}`, response.data);
|
||||||
|
return response.data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('데이터 저장 오류:', error);
|
console.error('테이블 데이터 저장 오류:', error);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -670,6 +685,111 @@ export class ImprovedButtonActionExecutor {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 다양한 API 응답 구조에서 실제 데이터 추출
|
||||||
|
*/
|
||||||
|
private static extractActualData(responseData: any): any {
|
||||||
|
console.log(`🔍 데이터 추출 시작 - 원본 타입: ${typeof responseData}`);
|
||||||
|
|
||||||
|
// null이나 undefined인 경우
|
||||||
|
if (!responseData) {
|
||||||
|
console.log(`⚠️ 응답 데이터가 null 또는 undefined`);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 이미 배열인 경우 (직접 배열 응답)
|
||||||
|
if (Array.isArray(responseData)) {
|
||||||
|
console.log(`✅ 직접 배열 응답 감지`);
|
||||||
|
return responseData;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 문자열인 경우 JSON 파싱 시도
|
||||||
|
if (typeof responseData === 'string') {
|
||||||
|
console.log(`🔄 JSON 문자열 파싱 시도`);
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(responseData);
|
||||||
|
console.log(`✅ JSON 파싱 성공, 재귀 호출`);
|
||||||
|
return this.extractActualData(parsed);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(`⚠️ JSON 파싱 실패, 원본 문자열 반환:`, error);
|
||||||
|
return [responseData];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 객체가 아닌 경우 (숫자 등)
|
||||||
|
if (typeof responseData !== 'object') {
|
||||||
|
console.log(`⚠️ 객체가 아닌 응답: ${typeof responseData}`);
|
||||||
|
return [responseData];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 일반적인 데이터 필드명들을 우선순위대로 확인
|
||||||
|
const commonDataFields = [
|
||||||
|
'data', // { data: [...] }
|
||||||
|
'result', // { result: [...] }
|
||||||
|
'results', // { results: [...] }
|
||||||
|
'items', // { items: [...] }
|
||||||
|
'list', // { list: [...] }
|
||||||
|
'records', // { records: [...] }
|
||||||
|
'rows', // { rows: [...] }
|
||||||
|
'content', // { content: [...] }
|
||||||
|
'payload', // { payload: [...] }
|
||||||
|
'response', // { response: [...] }
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const field of commonDataFields) {
|
||||||
|
if (responseData[field] !== undefined) {
|
||||||
|
console.log(`✅ '${field}' 필드에서 데이터 추출`);
|
||||||
|
|
||||||
|
const extractedData = responseData[field];
|
||||||
|
|
||||||
|
// 추출된 데이터가 문자열인 경우 JSON 파싱 시도
|
||||||
|
if (typeof extractedData === 'string') {
|
||||||
|
console.log(`🔄 추출된 데이터가 JSON 문자열, 파싱 시도`);
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(extractedData);
|
||||||
|
console.log(`✅ JSON 파싱 성공, 재귀 호출`);
|
||||||
|
return this.extractActualData(parsed);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(`⚠️ JSON 파싱 실패, 원본 문자열 반환:`, error);
|
||||||
|
return [extractedData];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 추출된 데이터가 객체이고 또 다른 중첩 구조일 수 있으므로 재귀 호출
|
||||||
|
if (typeof extractedData === 'object' && !Array.isArray(extractedData)) {
|
||||||
|
console.log(`🔄 중첩된 객체 감지, 재귀 추출 시도`);
|
||||||
|
return this.extractActualData(extractedData);
|
||||||
|
}
|
||||||
|
|
||||||
|
return extractedData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 특별한 필드가 없는 경우, 객체의 값들 중에서 배열을 찾기
|
||||||
|
const objectValues = Object.values(responseData);
|
||||||
|
const arrayValue = objectValues.find(value => Array.isArray(value));
|
||||||
|
|
||||||
|
if (arrayValue) {
|
||||||
|
console.log(`✅ 객체 값 중 배열 발견`);
|
||||||
|
return arrayValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 객체의 값들 중에서 객체를 찾아서 재귀 탐색
|
||||||
|
for (const value of objectValues) {
|
||||||
|
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
||||||
|
console.log(`🔄 객체 값에서 재귀 탐색`);
|
||||||
|
const nestedResult = this.extractActualData(value);
|
||||||
|
if (Array.isArray(nestedResult) && nestedResult.length > 0) {
|
||||||
|
return nestedResult;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 모든 시도가 실패한 경우, 원본 객체를 단일 항목 배열로 반환
|
||||||
|
console.log(`📦 원본 객체를 단일 항목으로 처리`);
|
||||||
|
return [responseData];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 인바운드 데이터 매핑 처리
|
* 인바운드 데이터 매핑 처리
|
||||||
*/
|
*/
|
||||||
|
|
@ -680,29 +800,56 @@ export class ImprovedButtonActionExecutor {
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
try {
|
try {
|
||||||
console.log(`📥 인바운드 데이터 매핑 처리 시작`);
|
console.log(`📥 인바운드 데이터 매핑 처리 시작`);
|
||||||
|
console.log(`📥 원본 응답 데이터:`, responseData);
|
||||||
|
|
||||||
const targetTable = inboundMapping.targetTable;
|
const targetTable = inboundMapping.targetTable;
|
||||||
const fieldMappings = inboundMapping.fieldMappings || [];
|
const fieldMappings = inboundMapping.fieldMappings || [];
|
||||||
const insertMode = inboundMapping.insertMode || 'insert';
|
const insertMode = inboundMapping.insertMode || 'insert';
|
||||||
|
|
||||||
// 응답 데이터가 배열인 경우 각 항목 처리
|
console.log(`📥 매핑 설정:`, {
|
||||||
const dataArray = Array.isArray(responseData) ? responseData : [responseData];
|
targetTable,
|
||||||
|
fieldMappings,
|
||||||
|
insertMode
|
||||||
|
});
|
||||||
|
|
||||||
|
// 응답 데이터에서 실제 데이터 추출 (다양한 구조 지원)
|
||||||
|
let actualData = this.extractActualData(responseData);
|
||||||
|
|
||||||
|
console.log(`📥 추출된 실제 데이터:`, actualData);
|
||||||
|
|
||||||
|
// 배열이 아닌 경우 배열로 변환
|
||||||
|
const dataArray = Array.isArray(actualData) ? actualData : [actualData];
|
||||||
|
|
||||||
|
console.log(`📥 처리할 데이터 배열:`, dataArray);
|
||||||
|
|
||||||
|
if (dataArray.length === 0) {
|
||||||
|
console.log(`⚠️ 처리할 데이터가 없습니다`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
for (const item of dataArray) {
|
for (const item of dataArray) {
|
||||||
const mappedData: Record<string, any> = {};
|
const mappedData: Record<string, any> = {};
|
||||||
|
|
||||||
|
console.log(`📥 개별 아이템 처리:`, item);
|
||||||
|
|
||||||
// 필드 매핑 적용
|
// 필드 매핑 적용
|
||||||
for (const mapping of fieldMappings) {
|
for (const mapping of fieldMappings) {
|
||||||
const sourceValue = item[mapping.sourceField];
|
const sourceValue = item[mapping.sourceField];
|
||||||
if (sourceValue !== undefined) {
|
console.log(`📥 필드 매핑: ${mapping.sourceField} -> ${mapping.targetField} = ${sourceValue}`);
|
||||||
|
|
||||||
|
if (sourceValue !== undefined && sourceValue !== null) {
|
||||||
mappedData[mapping.targetField] = sourceValue;
|
mappedData[mapping.targetField] = sourceValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`📋 매핑된 데이터:`, mappedData);
|
console.log(`📋 매핑된 데이터:`, mappedData);
|
||||||
|
|
||||||
// 데이터 저장
|
// 매핑된 데이터가 비어있지 않은 경우에만 저장
|
||||||
await this.saveDataToTable(targetTable, mappedData, insertMode);
|
if (Object.keys(mappedData).length > 0) {
|
||||||
|
await this.saveDataToTable(targetTable, mappedData, insertMode);
|
||||||
|
} else {
|
||||||
|
console.log(`⚠️ 매핑된 데이터가 비어있어 저장을 건너뜁니다`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`✅ 인바운드 데이터 매핑 완료`);
|
console.log(`✅ 인바운드 데이터 매핑 완료`);
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,13 @@
|
||||||
* 데이터 저장 기능과 완전히 분리된 독립적인 타입 구조
|
* 데이터 저장 기능과 완전히 분리된 독립적인 타입 구조
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { DataMappingConfig } from "./DataMappingTypes";
|
||||||
|
|
||||||
// 외부호출 메인 설정 타입
|
// 외부호출 메인 설정 타입
|
||||||
export interface ExternalCallConfig {
|
export interface ExternalCallConfig {
|
||||||
callType: "rest-api"; // 향후 "webhook", "email", "ftp" 등 확장 가능
|
callType: "rest-api"; // 향후 "webhook", "email", "ftp" 등 확장 가능
|
||||||
restApiSettings: RestApiSettings;
|
restApiSettings: RestApiSettings;
|
||||||
|
dataMappingConfig?: DataMappingConfig; // 데이터 매핑 설정 추가
|
||||||
metadata?: {
|
metadata?: {
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue