EventType 화면 고정 제외 코드 필터링 로직 추가
eapim-portal CI / build (push) Waiting to run
eapim-portal Test / test (push) Waiting to run

- 제외 코드(CHECK_NOTICE 등) 구독 방지 위한 유효성 검사 강화
- 선택 가능한 EventType만 검증/표시하도록 로직 수정
This commit is contained in:
Rinjae(gf63)
2026-09-21 15:32:05 +09:00
parent 0f48e4be89
commit cd97c680a3
4 changed files with 46 additions and 6 deletions
+2 -1
View File
@@ -112,4 +112,5 @@ diff
*rinjae* *rinjae*
*gf63* *gf63*
*obsidian* *obsidian*
design-backup design-backup
/.envrc
@@ -210,8 +210,11 @@ public class WebhookController {
modification.setId(webhook.getId()); modification.setId(webhook.getId());
modification.setRequestType("MODIFY"); modification.setRequestType("MODIFY");
modification.setTargetUrl(webhook.getTargetUrl()); modification.setTargetUrl(webhook.getTargetUrl());
// 화면 고정 제외 코드(CHECK_NOTICE 등)는 체크박스가 렌더링되지 않으므로 세션 값에서도 제외한다.
modification.setEventTypes(webhook.getEventTypes().stream() modification.setEventTypes(webhook.getEventTypes().stream()
.map(e -> e.getCode()).collect(java.util.stream.Collectors.toList())); .map(e -> e.getCode())
.filter(code -> !eventTypeProvider.isHidden(code))
.collect(java.util.stream.Collectors.toList()));
modification.setSelectedApis(new java.util.ArrayList<>(webhook.getApiIds())); modification.setSelectedApis(new java.util.ArrayList<>(webhook.getApiIds()));
model.addAttribute("webhookModification", modification); model.addAttribute("webhookModification", modification);
} }
@@ -374,7 +377,9 @@ public class WebhookController {
bindingResult.rejectValue("targetUrl", "invalid.url", bindingResult.rejectValue("targetUrl", "invalid.url",
"올바른 URL 형식이 아닙니다. http:// 또는 https:// 로 시작하는 전체 주소를 입력해주세요."); "올바른 URL 형식이 아닙니다. http:// 또는 https:// 로 시작하는 전체 주소를 입력해주세요.");
} }
if (dto.getEventTypes() == null || dto.getEventTypes().isEmpty()) { boolean hasSelectableEventType = dto.getEventTypes() != null && dto.getEventTypes().stream()
.anyMatch(code -> !eventTypeProvider.isHidden(code));
if (!hasSelectableEventType) {
bindingResult.rejectValue("eventTypes", "empty.eventTypes", bindingResult.rejectValue("eventTypes", "empty.eventTypes",
"EventType을 1개 이상 선택해주세요."); "EventType을 1개 이상 선택해주세요.");
} }
@@ -2,9 +2,13 @@ package com.eactive.apim.portal.djb.webhook.service;
import com.eactive.apim.portal.djb.webhook.dto.WebhookEventTypeDTO; import com.eactive.apim.portal.djb.webhook.dto.WebhookEventTypeDTO;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
import javax.persistence.EntityManager; import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext; import javax.persistence.PersistenceContext;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@@ -20,6 +24,14 @@ import org.springframework.transaction.annotation.Transactional;
@Component @Component
public class WebhookEventTypeProvider { public class WebhookEventTypeProvider {
/**
* 신청/수정 화면의 선택 목록에서 고정으로 제외하는 EventType 코드.
* DB(TSEAIRM28)에 USEYN='Y' 로 남아 있어도 포털 사용자가 직접 구독할 수 없는 코드다.
* 기존 데이터 표시(이름 조회)에는 영향이 없다.
*/
private static final Set<String> HIDDEN_CODES =
Collections.unmodifiableSet(new HashSet<>(Arrays.asList("CHECK_NOTICE")));
private static final String EVENT_TYPE_SQL = private static final String EVENT_TYPE_SQL =
"SELECT CODE, CODENAME FROM TSEAIRM28 " "SELECT CODE, CODENAME FROM TSEAIRM28 "
+ "WHERE CODEGROUP = 'EVENT_TYPE' AND USEYN = 'Y' " + "WHERE CODEGROUP = 'EVENT_TYPE' AND USEYN = 'Y' "
@@ -29,15 +41,25 @@ public class WebhookEventTypeProvider {
@PersistenceContext @PersistenceContext
private EntityManager entityManager; private EntityManager entityManager;
/** 신청/수정 화면에 노출할 선택 가능한 EventType 목록({@link #HIDDEN_CODES} 제외). */
@Transactional(readOnly = true) @Transactional(readOnly = true)
public List<WebhookEventTypeDTO> getAll() { public List<WebhookEventTypeDTO> getAll() {
List<WebhookEventTypeDTO> list = new ArrayList<>(); List<WebhookEventTypeDTO> list = new ArrayList<>();
for (Object[] row : rows()) { for (Object[] row : rows()) {
list.add(new WebhookEventTypeDTO(asString(row[0]), asString(row[1]))); String code = asString(row[0]);
if (isHidden(code)) {
continue;
}
list.add(new WebhookEventTypeDTO(code, asString(row[1])));
} }
return list; return list;
} }
/** 화면 노출/구독 대상에서 고정 제외되는 코드인지 여부. */
public boolean isHidden(String code) {
return code != null && HIDDEN_CODES.contains(code);
}
@Transactional(readOnly = true) @Transactional(readOnly = true)
public Map<String, String> asMap() { public Map<String, String> asMap() {
Map<String, String> map = new LinkedHashMap<>(); Map<String, String> map = new LinkedHashMap<>();
@@ -47,6 +69,7 @@ public class WebhookEventTypeProvider {
return map; return map;
} }
/** 코드 자체의 유효성(제외 코드 포함). 구독 가능 여부는 {@link #isHidden(String)} 로 별도 판단한다. */
@Transactional(readOnly = true) @Transactional(readOnly = true)
public boolean isValid(String code) { public boolean isValid(String code) {
if (code == null) { if (code == null) {
@@ -161,7 +161,7 @@ public class WebhookService {
for (String apiId : dedup(dto.getSelectedApis())) { for (String apiId : dedup(dto.getSelectedApis())) {
apiRepository.save(new WebhookRequestApi(reqId, apiId)); apiRepository.save(new WebhookRequestApi(reqId, apiId));
} }
for (String eventType : dedup(dto.getEventTypes())) { for (String eventType : selectableEventTypes(dto)) {
if (!eventTypeProvider.isValid(eventType)) { if (!eventTypeProvider.isValid(eventType)) {
throw new IllegalArgumentException("유효하지 않은 EventType 코드입니다: " + eventType); throw new IllegalArgumentException("유효하지 않은 EventType 코드입니다: " + eventType);
} }
@@ -169,11 +169,22 @@ public class WebhookService {
} }
} }
/**
* 중복 제거 + 화면 고정 제외 코드(예: CHECK_NOTICE) 제거.
* 제외 코드는 신청/수정 화면에 체크박스가 렌더링되지 않으므로 정상 경로로는 들어오지 않지만,
* 세션에 남은 값이나 직접 조작한 요청으로 구독되지 않도록 저장 직전에 한 번 더 걸러낸다.
*/
private List<String> selectableEventTypes(WebhookRegistrationDTO dto) {
return dedup(dto.getEventTypes()).stream()
.filter(code -> !eventTypeProvider.isHidden(code))
.collect(Collectors.toList());
}
private void validate(WebhookRegistrationDTO dto) { private void validate(WebhookRegistrationDTO dto) {
if (!WebhookUrlValidator.isValid(dto.getTargetUrl())) { if (!WebhookUrlValidator.isValid(dto.getTargetUrl())) {
throw new IllegalArgumentException("올바른 URL 형식이 아닙니다. http:// 또는 https:// 로 시작하는 전체 주소를 입력해주세요."); throw new IllegalArgumentException("올바른 URL 형식이 아닙니다. http:// 또는 https:// 로 시작하는 전체 주소를 입력해주세요.");
} }
if (dto.getEventTypes() == null || dto.getEventTypes().isEmpty()) { if (selectableEventTypes(dto).isEmpty()) {
throw new IllegalArgumentException("EventType을 1개 이상 선택해주세요."); throw new IllegalArgumentException("EventType을 1개 이상 선택해주세요.");
} }
if (dto.getSelectedApis() == null || dto.getSelectedApis().isEmpty()) { if (dto.getSelectedApis() == null || dto.getSelectedApis().isEmpty()) {