diff --git a/.gitignore b/.gitignore index abe6f4b..c691464 100644 --- a/.gitignore +++ b/.gitignore @@ -112,4 +112,5 @@ diff *rinjae* *gf63* *obsidian* -design-backup \ No newline at end of file +design-backup +/.envrc diff --git a/src/main/java/com/eactive/apim/portal/djb/webhook/controller/WebhookController.java b/src/main/java/com/eactive/apim/portal/djb/webhook/controller/WebhookController.java index 3c7b50c..776adb3 100644 --- a/src/main/java/com/eactive/apim/portal/djb/webhook/controller/WebhookController.java +++ b/src/main/java/com/eactive/apim/portal/djb/webhook/controller/WebhookController.java @@ -210,8 +210,11 @@ public class WebhookController { modification.setId(webhook.getId()); modification.setRequestType("MODIFY"); modification.setTargetUrl(webhook.getTargetUrl()); + // 화면 고정 제외 코드(CHECK_NOTICE 등)는 체크박스가 렌더링되지 않으므로 세션 값에서도 제외한다. 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())); model.addAttribute("webhookModification", modification); } @@ -374,7 +377,9 @@ public class WebhookController { bindingResult.rejectValue("targetUrl", "invalid.url", "올바른 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", "EventType을 1개 이상 선택해주세요."); } diff --git a/src/main/java/com/eactive/apim/portal/djb/webhook/service/WebhookEventTypeProvider.java b/src/main/java/com/eactive/apim/portal/djb/webhook/service/WebhookEventTypeProvider.java index faf8a66..2f5f87c 100644 --- a/src/main/java/com/eactive/apim/portal/djb/webhook/service/WebhookEventTypeProvider.java +++ b/src/main/java/com/eactive/apim/portal/djb/webhook/service/WebhookEventTypeProvider.java @@ -2,9 +2,13 @@ package com.eactive.apim.portal.djb.webhook.service; import com.eactive.apim.portal.djb.webhook.dto.WebhookEventTypeDTO; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Set; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import org.springframework.stereotype.Component; @@ -20,6 +24,14 @@ import org.springframework.transaction.annotation.Transactional; @Component public class WebhookEventTypeProvider { + /** + * 신청/수정 화면의 선택 목록에서 고정으로 제외하는 EventType 코드. + * DB(TSEAIRM28)에 USEYN='Y' 로 남아 있어도 포털 사용자가 직접 구독할 수 없는 코드다. + * 기존 데이터 표시(이름 조회)에는 영향이 없다. + */ + private static final Set HIDDEN_CODES = + Collections.unmodifiableSet(new HashSet<>(Arrays.asList("CHECK_NOTICE"))); + private static final String EVENT_TYPE_SQL = "SELECT CODE, CODENAME FROM TSEAIRM28 " + "WHERE CODEGROUP = 'EVENT_TYPE' AND USEYN = 'Y' " @@ -29,15 +41,25 @@ public class WebhookEventTypeProvider { @PersistenceContext private EntityManager entityManager; + /** 신청/수정 화면에 노출할 선택 가능한 EventType 목록({@link #HIDDEN_CODES} 제외). */ @Transactional(readOnly = true) public List getAll() { List list = new ArrayList<>(); 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; } + /** 화면 노출/구독 대상에서 고정 제외되는 코드인지 여부. */ + public boolean isHidden(String code) { + return code != null && HIDDEN_CODES.contains(code); + } + @Transactional(readOnly = true) public Map asMap() { Map map = new LinkedHashMap<>(); @@ -47,6 +69,7 @@ public class WebhookEventTypeProvider { return map; } + /** 코드 자체의 유효성(제외 코드 포함). 구독 가능 여부는 {@link #isHidden(String)} 로 별도 판단한다. */ @Transactional(readOnly = true) public boolean isValid(String code) { if (code == null) { diff --git a/src/main/java/com/eactive/apim/portal/djb/webhook/service/WebhookService.java b/src/main/java/com/eactive/apim/portal/djb/webhook/service/WebhookService.java index 50eabf5..32b895a 100644 --- a/src/main/java/com/eactive/apim/portal/djb/webhook/service/WebhookService.java +++ b/src/main/java/com/eactive/apim/portal/djb/webhook/service/WebhookService.java @@ -161,7 +161,7 @@ public class WebhookService { for (String apiId : dedup(dto.getSelectedApis())) { apiRepository.save(new WebhookRequestApi(reqId, apiId)); } - for (String eventType : dedup(dto.getEventTypes())) { + for (String eventType : selectableEventTypes(dto)) { if (!eventTypeProvider.isValid(eventType)) { throw new IllegalArgumentException("유효하지 않은 EventType 코드입니다: " + eventType); } @@ -169,11 +169,22 @@ public class WebhookService { } } + /** + * 중복 제거 + 화면 고정 제외 코드(예: CHECK_NOTICE) 제거. + * 제외 코드는 신청/수정 화면에 체크박스가 렌더링되지 않으므로 정상 경로로는 들어오지 않지만, + * 세션에 남은 값이나 직접 조작한 요청으로 구독되지 않도록 저장 직전에 한 번 더 걸러낸다. + */ + private List selectableEventTypes(WebhookRegistrationDTO dto) { + return dedup(dto.getEventTypes()).stream() + .filter(code -> !eventTypeProvider.isHidden(code)) + .collect(Collectors.toList()); + } + private void validate(WebhookRegistrationDTO dto) { if (!WebhookUrlValidator.isValid(dto.getTargetUrl())) { throw new IllegalArgumentException("올바른 URL 형식이 아닙니다. http:// 또는 https:// 로 시작하는 전체 주소를 입력해주세요."); } - if (dto.getEventTypes() == null || dto.getEventTypes().isEmpty()) { + if (selectableEventTypes(dto).isEmpty()) { throw new IllegalArgumentException("EventType을 1개 이상 선택해주세요."); } if (dto.getSelectedApis() == null || dto.getSelectedApis().isEmpty()) {