2025-09-17 11:47:57 +09:00
|
|
|
import {
|
|
|
|
|
ExternalCallConfig,
|
|
|
|
|
ExternalCallResult,
|
|
|
|
|
ExternalCallRequest,
|
|
|
|
|
SlackSettings,
|
|
|
|
|
KakaoTalkSettings,
|
|
|
|
|
DiscordSettings,
|
|
|
|
|
GenericApiSettings,
|
|
|
|
|
EmailSettings,
|
|
|
|
|
SupportedExternalCallSettings,
|
|
|
|
|
TemplateOptions,
|
|
|
|
|
} from "../types/externalCallTypes";
|
2025-09-26 17:52:11 +09:00
|
|
|
import { DataMappingService } from "./dataMappingService";
|
|
|
|
|
import {
|
|
|
|
|
DataMappingConfig,
|
|
|
|
|
DataMappingResult,
|
|
|
|
|
} from "../types/dataMappingTypes";
|
2025-09-17 11:47:57 +09:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 외부 호출 서비스
|
|
|
|
|
* REST API, 웹훅, 이메일 등 다양한 외부 시스템 호출을 처리
|
|
|
|
|
*/
|
|
|
|
|
export class ExternalCallService {
|
|
|
|
|
private readonly DEFAULT_TIMEOUT = 30000; // 30초
|
|
|
|
|
private readonly DEFAULT_RETRY_COUNT = 3;
|
2025-09-26 17:52:11 +09:00
|
|
|
private dataMappingService: DataMappingService;
|
2025-09-17 11:47:57 +09:00
|
|
|
private readonly DEFAULT_RETRY_DELAY = 1000; // 1초
|
|
|
|
|
|
2025-09-26 17:52:11 +09:00
|
|
|
constructor() {
|
|
|
|
|
this.dataMappingService = new DataMappingService();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 데이터 매핑과 함께 외부 호출 실행
|
|
|
|
|
*/
|
|
|
|
|
async executeWithDataMapping(
|
|
|
|
|
config: ExternalCallConfig,
|
|
|
|
|
dataMappingConfig?: DataMappingConfig,
|
|
|
|
|
triggerData?: any
|
|
|
|
|
): Promise<{
|
|
|
|
|
callResult: ExternalCallResult;
|
|
|
|
|
mappingResult?: DataMappingResult;
|
|
|
|
|
}> {
|
|
|
|
|
const startTime = Date.now();
|
|
|
|
|
|
|
|
|
|
console.log(`🚀 [ExternalCallService] 데이터 매핑 포함 외부 호출 시작:`, {
|
|
|
|
|
callType: config.callType,
|
|
|
|
|
hasMappingConfig: !!dataMappingConfig,
|
|
|
|
|
mappingDirection: dataMappingConfig?.direction,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
let requestData = config;
|
|
|
|
|
|
|
|
|
|
// Outbound 매핑 처리 (내부 → 외부)
|
|
|
|
|
if (
|
|
|
|
|
dataMappingConfig?.direction === "outbound" &&
|
|
|
|
|
dataMappingConfig.outboundMapping
|
|
|
|
|
) {
|
|
|
|
|
console.log(`📤 [ExternalCallService] Outbound 매핑 처리 시작`);
|
|
|
|
|
|
|
|
|
|
const outboundData = await this.dataMappingService.processOutboundData(
|
|
|
|
|
dataMappingConfig.outboundMapping,
|
|
|
|
|
triggerData
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// API 요청 바디에 매핑된 데이터 포함
|
|
|
|
|
if (config.callType === "rest-api") {
|
|
|
|
|
// GenericApiSettings로 타입 캐스팅
|
|
|
|
|
const apiConfig = config as GenericApiSettings;
|
|
|
|
|
const bodyTemplate = apiConfig.body || "{}";
|
|
|
|
|
|
|
|
|
|
// 템플릿에 데이터 삽입
|
|
|
|
|
const processedBody = this.processTemplate(bodyTemplate, {
|
|
|
|
|
mappedData: outboundData,
|
|
|
|
|
triggerData,
|
|
|
|
|
...outboundData,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
requestData = {
|
|
|
|
|
...config,
|
|
|
|
|
body: processedBody,
|
|
|
|
|
} as GenericApiSettings;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 외부 호출 실행
|
|
|
|
|
const callRequest: ExternalCallRequest = {
|
|
|
|
|
diagramId: 0, // 임시값
|
|
|
|
|
relationshipId: "data-mapping", // 임시값
|
|
|
|
|
settings: requestData,
|
|
|
|
|
templateData: triggerData,
|
|
|
|
|
};
|
|
|
|
|
const callResult = await this.executeExternalCall(callRequest);
|
|
|
|
|
|
|
|
|
|
let mappingResult: DataMappingResult | undefined;
|
|
|
|
|
|
|
|
|
|
// Inbound 매핑 처리 (외부 → 내부)
|
|
|
|
|
if (
|
|
|
|
|
callResult.success &&
|
|
|
|
|
dataMappingConfig?.direction === "inbound" &&
|
|
|
|
|
dataMappingConfig.inboundMapping
|
|
|
|
|
) {
|
|
|
|
|
console.log(`📥 [ExternalCallService] Inbound 매핑 처리 시작`);
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
// 응답 데이터 파싱
|
|
|
|
|
let responseData = callResult.response;
|
|
|
|
|
if (typeof responseData === "string") {
|
|
|
|
|
try {
|
|
|
|
|
responseData = JSON.parse(responseData);
|
|
|
|
|
} catch {
|
|
|
|
|
console.warn(
|
|
|
|
|
`⚠️ [ExternalCallService] 응답 데이터 JSON 파싱 실패, 문자열로 처리`
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
mappingResult = await this.dataMappingService.processInboundData(
|
|
|
|
|
responseData,
|
|
|
|
|
dataMappingConfig.inboundMapping
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
console.log(`✅ [ExternalCallService] Inbound 매핑 완료:`, {
|
|
|
|
|
recordsProcessed: mappingResult.recordsProcessed,
|
|
|
|
|
recordsInserted: mappingResult.recordsInserted,
|
|
|
|
|
});
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error(`❌ [ExternalCallService] Inbound 매핑 실패:`, error);
|
|
|
|
|
mappingResult = {
|
|
|
|
|
success: false,
|
|
|
|
|
direction: "inbound",
|
|
|
|
|
errors: [error instanceof Error ? error.message : String(error)],
|
|
|
|
|
executionTime: Date.now() - startTime,
|
|
|
|
|
timestamp: new Date().toISOString(),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 양방향 매핑 처리
|
|
|
|
|
if (dataMappingConfig?.direction === "bidirectional") {
|
|
|
|
|
// 필요한 경우 양방향 매핑 로직 구현
|
|
|
|
|
console.log(`🔄 [ExternalCallService] 양방향 매핑은 향후 구현 예정`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const result = {
|
|
|
|
|
callResult,
|
|
|
|
|
mappingResult,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
console.log(`✅ [ExternalCallService] 데이터 매핑 포함 외부 호출 완료:`, {
|
|
|
|
|
callSuccess: callResult.success,
|
|
|
|
|
mappingSuccess: mappingResult?.success,
|
|
|
|
|
totalExecutionTime: Date.now() - startTime,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error(
|
|
|
|
|
`❌ [ExternalCallService] 데이터 매핑 포함 외부 호출 실패:`,
|
|
|
|
|
error
|
|
|
|
|
);
|
|
|
|
|
throw error;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-17 11:47:57 +09:00
|
|
|
/**
|
2025-09-26 17:52:11 +09:00
|
|
|
* 기존 외부 호출 실행 (매핑 없음)
|
2025-09-17 11:47:57 +09:00
|
|
|
*/
|
|
|
|
|
async executeExternalCall(
|
|
|
|
|
request: ExternalCallRequest
|
|
|
|
|
): Promise<ExternalCallResult> {
|
|
|
|
|
const startTime = Date.now();
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
let result: ExternalCallResult;
|
|
|
|
|
|
|
|
|
|
switch (request.settings.callType) {
|
|
|
|
|
case "rest-api":
|
|
|
|
|
result = await this.executeRestApiCall(request);
|
|
|
|
|
break;
|
|
|
|
|
case "email":
|
|
|
|
|
result = await this.executeEmailCall(request);
|
|
|
|
|
break;
|
|
|
|
|
case "ftp":
|
|
|
|
|
throw new Error("FTP 호출은 아직 구현되지 않았습니다.");
|
|
|
|
|
case "queue":
|
|
|
|
|
throw new Error("메시지 큐 호출은 아직 구현되지 않았습니다.");
|
|
|
|
|
default:
|
|
|
|
|
throw new Error(
|
|
|
|
|
`지원되지 않는 호출 타입: ${request.settings.callType}`
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
result.executionTime = Date.now() - startTime;
|
|
|
|
|
result.timestamp = new Date();
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
} catch (error) {
|
|
|
|
|
return {
|
|
|
|
|
success: false,
|
|
|
|
|
error: error instanceof Error ? error.message : String(error),
|
|
|
|
|
executionTime: Date.now() - startTime,
|
|
|
|
|
timestamp: new Date(),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* REST API 호출 실행
|
|
|
|
|
*/
|
|
|
|
|
private async executeRestApiCall(
|
|
|
|
|
request: ExternalCallRequest
|
|
|
|
|
): Promise<ExternalCallResult> {
|
|
|
|
|
const settings = request.settings as any; // 임시로 any 사용
|
|
|
|
|
|
|
|
|
|
switch (settings.apiType) {
|
|
|
|
|
case "slack":
|
|
|
|
|
return await this.executeSlackWebhook(
|
|
|
|
|
settings as SlackSettings,
|
|
|
|
|
request.templateData
|
|
|
|
|
);
|
|
|
|
|
case "kakao-talk":
|
|
|
|
|
return await this.executeKakaoTalkApi(
|
|
|
|
|
settings as KakaoTalkSettings,
|
|
|
|
|
request.templateData
|
|
|
|
|
);
|
|
|
|
|
case "discord":
|
|
|
|
|
return await this.executeDiscordWebhook(
|
|
|
|
|
settings as DiscordSettings,
|
|
|
|
|
request.templateData
|
|
|
|
|
);
|
|
|
|
|
case "generic":
|
|
|
|
|
default:
|
|
|
|
|
return await this.executeGenericApi(
|
|
|
|
|
settings as GenericApiSettings,
|
|
|
|
|
request.templateData
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 슬랙 웹훅 실행
|
|
|
|
|
*/
|
|
|
|
|
private async executeSlackWebhook(
|
|
|
|
|
settings: SlackSettings,
|
|
|
|
|
templateData?: Record<string, unknown>
|
|
|
|
|
): Promise<ExternalCallResult> {
|
|
|
|
|
const payload = {
|
|
|
|
|
text: this.processTemplate(settings.message, templateData),
|
|
|
|
|
channel: settings.channel,
|
|
|
|
|
username: settings.username || "DataFlow Bot",
|
|
|
|
|
icon_emoji: settings.iconEmoji || ":robot_face:",
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return await this.makeHttpRequest({
|
|
|
|
|
url: settings.webhookUrl,
|
|
|
|
|
method: "POST",
|
|
|
|
|
headers: { "Content-Type": "application/json" },
|
|
|
|
|
body: JSON.stringify(payload),
|
|
|
|
|
timeout: settings.timeout || this.DEFAULT_TIMEOUT,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 카카오톡 API 실행
|
|
|
|
|
*/
|
|
|
|
|
private async executeKakaoTalkApi(
|
|
|
|
|
settings: KakaoTalkSettings,
|
|
|
|
|
templateData?: Record<string, unknown>
|
|
|
|
|
): Promise<ExternalCallResult> {
|
|
|
|
|
const payload = {
|
|
|
|
|
object_type: "text",
|
|
|
|
|
text: this.processTemplate(settings.message, templateData),
|
|
|
|
|
link: {
|
|
|
|
|
web_url: "https://developers.kakao.com",
|
|
|
|
|
mobile_web_url: "https://developers.kakao.com",
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return await this.makeHttpRequest({
|
|
|
|
|
url: "https://kapi.kakao.com/v2/api/talk/memo/default/send",
|
|
|
|
|
method: "POST",
|
|
|
|
|
headers: {
|
|
|
|
|
Authorization: `Bearer ${settings.accessToken}`,
|
|
|
|
|
"Content-Type": "application/x-www-form-urlencoded",
|
|
|
|
|
},
|
|
|
|
|
body: `template_object=${encodeURIComponent(JSON.stringify(payload))}`,
|
|
|
|
|
timeout: settings.timeout || this.DEFAULT_TIMEOUT,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 디스코드 웹훅 실행
|
|
|
|
|
*/
|
|
|
|
|
private async executeDiscordWebhook(
|
|
|
|
|
settings: DiscordSettings,
|
|
|
|
|
templateData?: Record<string, unknown>
|
|
|
|
|
): Promise<ExternalCallResult> {
|
|
|
|
|
const payload = {
|
|
|
|
|
content: this.processTemplate(settings.message, templateData),
|
|
|
|
|
username: settings.username || "시스템 알리미",
|
|
|
|
|
avatar_url: settings.avatarUrl,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return await this.makeHttpRequest({
|
|
|
|
|
url: settings.webhookUrl,
|
|
|
|
|
method: "POST",
|
|
|
|
|
headers: { "Content-Type": "application/json" },
|
|
|
|
|
body: JSON.stringify(payload),
|
|
|
|
|
timeout: settings.timeout || this.DEFAULT_TIMEOUT,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 일반 REST API 실행
|
|
|
|
|
*/
|
|
|
|
|
private async executeGenericApi(
|
|
|
|
|
settings: GenericApiSettings,
|
|
|
|
|
templateData?: Record<string, unknown>
|
|
|
|
|
): Promise<ExternalCallResult> {
|
|
|
|
|
let body = settings.body;
|
|
|
|
|
if (body && templateData) {
|
|
|
|
|
body = this.processTemplate(body, templateData);
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-26 17:11:18 +09:00
|
|
|
// 기본 헤더 준비
|
|
|
|
|
const headers = { ...(settings.headers || {}) };
|
|
|
|
|
|
|
|
|
|
// 인증 정보 처리
|
|
|
|
|
if (settings.authentication) {
|
|
|
|
|
switch (settings.authentication.type) {
|
|
|
|
|
case "api-key":
|
|
|
|
|
if (settings.authentication.apiKey) {
|
|
|
|
|
headers["X-API-Key"] = settings.authentication.apiKey;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case "basic":
|
|
|
|
|
if (
|
|
|
|
|
settings.authentication.username &&
|
|
|
|
|
settings.authentication.password
|
|
|
|
|
) {
|
|
|
|
|
const credentials = Buffer.from(
|
|
|
|
|
`${settings.authentication.username}:${settings.authentication.password}`
|
|
|
|
|
).toString("base64");
|
|
|
|
|
headers["Authorization"] = `Basic ${credentials}`;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case "bearer":
|
|
|
|
|
if (settings.authentication.token) {
|
|
|
|
|
headers["Authorization"] =
|
|
|
|
|
`Bearer ${settings.authentication.token}`;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case "custom":
|
|
|
|
|
if (
|
|
|
|
|
settings.authentication.headerName &&
|
|
|
|
|
settings.authentication.headerValue
|
|
|
|
|
) {
|
|
|
|
|
headers[settings.authentication.headerName] =
|
|
|
|
|
settings.authentication.headerValue;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
// 'none' 타입은 아무것도 하지 않음
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
console.log(`🔐 [ExternalCallService] 인증 처리 완료:`, {
|
|
|
|
|
authType: settings.authentication?.type || "none",
|
|
|
|
|
hasAuthHeader: !!headers["Authorization"],
|
|
|
|
|
headers: Object.keys(headers),
|
|
|
|
|
});
|
|
|
|
|
|
2025-09-17 11:47:57 +09:00
|
|
|
return await this.makeHttpRequest({
|
|
|
|
|
url: settings.url,
|
|
|
|
|
method: settings.method,
|
2025-09-26 17:11:18 +09:00
|
|
|
headers: headers,
|
2025-09-17 11:47:57 +09:00
|
|
|
body: body,
|
|
|
|
|
timeout: settings.timeout || this.DEFAULT_TIMEOUT,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 이메일 호출 실행 (향후 구현)
|
|
|
|
|
*/
|
|
|
|
|
private async executeEmailCall(
|
|
|
|
|
request: ExternalCallRequest
|
|
|
|
|
): Promise<ExternalCallResult> {
|
|
|
|
|
// TODO: 이메일 발송 구현 (Java MailUtil 연동)
|
|
|
|
|
throw new Error("이메일 발송 기능은 아직 구현되지 않았습니다.");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* HTTP 요청 실행 (공통)
|
|
|
|
|
*/
|
|
|
|
|
private async makeHttpRequest(options: {
|
|
|
|
|
url: string;
|
|
|
|
|
method: string;
|
|
|
|
|
headers?: Record<string, string>;
|
|
|
|
|
body?: string;
|
|
|
|
|
timeout: number;
|
|
|
|
|
}): Promise<ExternalCallResult> {
|
|
|
|
|
try {
|
|
|
|
|
const controller = new AbortController();
|
|
|
|
|
const timeoutId = setTimeout(() => controller.abort(), options.timeout);
|
|
|
|
|
|
2025-09-26 17:11:18 +09:00
|
|
|
// GET, HEAD 메서드는 body를 가질 수 없음
|
|
|
|
|
const method = options.method.toUpperCase();
|
|
|
|
|
const requestOptions: RequestInit = {
|
2025-09-17 11:47:57 +09:00
|
|
|
method: options.method,
|
|
|
|
|
headers: options.headers,
|
|
|
|
|
signal: controller.signal,
|
2025-09-26 17:11:18 +09:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// GET, HEAD 메서드가 아닌 경우에만 body 추가
|
|
|
|
|
if (method !== "GET" && method !== "HEAD" && options.body) {
|
|
|
|
|
requestOptions.body = options.body;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const response = await fetch(options.url, requestOptions);
|
2025-09-17 11:47:57 +09:00
|
|
|
|
|
|
|
|
clearTimeout(timeoutId);
|
|
|
|
|
|
|
|
|
|
const responseText = await response.text();
|
|
|
|
|
|
2025-09-26 17:11:18 +09:00
|
|
|
// 디버깅을 위한 로그 추가
|
|
|
|
|
console.log(`🔍 [ExternalCallService] HTTP 응답:`, {
|
|
|
|
|
url: options.url,
|
|
|
|
|
method: options.method,
|
|
|
|
|
status: response.status,
|
|
|
|
|
statusText: response.statusText,
|
|
|
|
|
ok: response.ok,
|
|
|
|
|
headers: Object.fromEntries(response.headers.entries()),
|
|
|
|
|
responseText: responseText.substring(0, 500), // 처음 500자만 로그
|
|
|
|
|
});
|
|
|
|
|
|
2025-09-17 11:47:57 +09:00
|
|
|
return {
|
|
|
|
|
success: response.ok,
|
|
|
|
|
statusCode: response.status,
|
|
|
|
|
response: responseText,
|
|
|
|
|
executionTime: 0, // 상위에서 설정됨
|
|
|
|
|
timestamp: new Date(),
|
|
|
|
|
};
|
|
|
|
|
} catch (error) {
|
|
|
|
|
if (error instanceof Error) {
|
|
|
|
|
if (error.name === "AbortError") {
|
|
|
|
|
throw new Error(`요청 시간 초과 (${options.timeout}ms)`);
|
|
|
|
|
}
|
|
|
|
|
throw error;
|
|
|
|
|
}
|
|
|
|
|
throw new Error(`HTTP 요청 실패: ${String(error)}`);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 템플릿 문자열 처리
|
|
|
|
|
*/
|
|
|
|
|
private processTemplate(
|
|
|
|
|
template: string,
|
|
|
|
|
data?: Record<string, unknown>,
|
|
|
|
|
options: TemplateOptions = {}
|
|
|
|
|
): string {
|
|
|
|
|
if (!data || Object.keys(data).length === 0) {
|
|
|
|
|
return template;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const startDelimiter = options.startDelimiter || "{{";
|
|
|
|
|
const endDelimiter = options.endDelimiter || "}}";
|
|
|
|
|
|
|
|
|
|
let result = template;
|
|
|
|
|
|
|
|
|
|
Object.entries(data).forEach(([key, value]) => {
|
|
|
|
|
const placeholder = `${startDelimiter}${key}${endDelimiter}`;
|
|
|
|
|
const replacement = String(value ?? "");
|
|
|
|
|
result = result.replace(new RegExp(placeholder, "g"), replacement);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 외부 호출 설정 검증
|
|
|
|
|
*/
|
|
|
|
|
validateSettings(settings: SupportedExternalCallSettings): {
|
|
|
|
|
valid: boolean;
|
|
|
|
|
errors: string[];
|
|
|
|
|
} {
|
|
|
|
|
const errors: string[] = [];
|
|
|
|
|
|
|
|
|
|
if (settings.callType === "rest-api") {
|
|
|
|
|
switch (settings.apiType) {
|
|
|
|
|
case "slack":
|
|
|
|
|
const slackSettings = settings as SlackSettings;
|
|
|
|
|
if (!slackSettings.webhookUrl)
|
|
|
|
|
errors.push("슬랙 웹훅 URL이 필요합니다.");
|
|
|
|
|
if (!slackSettings.message) errors.push("슬랙 메시지가 필요합니다.");
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case "kakao-talk":
|
|
|
|
|
const kakaoSettings = settings as KakaoTalkSettings;
|
|
|
|
|
if (!kakaoSettings.accessToken)
|
|
|
|
|
errors.push("카카오톡 액세스 토큰이 필요합니다.");
|
|
|
|
|
if (!kakaoSettings.message)
|
|
|
|
|
errors.push("카카오톡 메시지가 필요합니다.");
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case "discord":
|
|
|
|
|
const discordSettings = settings as DiscordSettings;
|
|
|
|
|
if (!discordSettings.webhookUrl)
|
|
|
|
|
errors.push("디스코드 웹훅 URL이 필요합니다.");
|
|
|
|
|
if (!discordSettings.message)
|
|
|
|
|
errors.push("디스코드 메시지가 필요합니다.");
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case "generic":
|
|
|
|
|
default:
|
|
|
|
|
const genericSettings = settings as GenericApiSettings;
|
|
|
|
|
if (!genericSettings.url) errors.push("API URL이 필요합니다.");
|
|
|
|
|
if (!genericSettings.method) errors.push("HTTP 메서드가 필요합니다.");
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
} else if (settings.callType === "email") {
|
|
|
|
|
const emailSettings = settings as EmailSettings;
|
|
|
|
|
if (!emailSettings.smtpHost) errors.push("SMTP 호스트가 필요합니다.");
|
|
|
|
|
if (!emailSettings.toEmail) errors.push("수신 이메일이 필요합니다.");
|
|
|
|
|
if (!emailSettings.subject) errors.push("이메일 제목이 필요합니다.");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
valid: errors.length === 0,
|
|
|
|
|
errors,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|