From 3581d1ff961a296a853e37bf77fabf8b22a732a3 Mon Sep 17 00:00:00 2001 From: dev-ant Date: Mon, 23 Feb 2026 21:47:24 +0900 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20enchant=20fullname=20api=20?= =?UTF-8?q?=EC=A0=91=EB=91=90=EC=A0=91=EB=AF=B8=20=EC=97=AC=EB=B6=80=20?= =?UTF-8?q?=EC=9A=94=EC=B2=AD=20=ED=8C=8C=EB=9D=BC=EB=AF=B8=ED=84=B0=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AuctionHistoryQueryDslRepository.java | 36 +++++++++++++++++++ .../request/AuctionHistorySearchRequest.java | 3 +- .../dto/request/EnchantSearchRequest.java | 9 +++++ .../AuctionRealtimeQueryDslRepository.java | 36 +++++++++++++++++++ .../request/AuctionRealtimeSearchRequest.java | 4 ++- .../service/EnchantInfoService.java | 13 ++++++- .../exception/EnchantInfoExceptionCode.java | 23 ++++++++++++ .../repository/EnchantInfoRepositoryPort.java | 2 ++ .../persistence/EnchantInfoJpaRepository.java | 3 ++ .../EnchantInfoRepositoryPortImpl.java | 5 +++ .../controller/EnchantInfoController.java | 14 ++++++-- 11 files changed, 142 insertions(+), 6 deletions(-) create mode 100644 src/main/java/until/the/eternity/auctionhistory/interfaces/rest/dto/request/EnchantSearchRequest.java create mode 100644 src/main/java/until/the/eternity/enchantinfo/domain/exception/EnchantInfoExceptionCode.java diff --git a/src/main/java/until/the/eternity/auctionhistory/infrastructure/persistence/AuctionHistoryQueryDslRepository.java b/src/main/java/until/the/eternity/auctionhistory/infrastructure/persistence/AuctionHistoryQueryDslRepository.java index 2bcfcad..88f4d4a 100644 --- a/src/main/java/until/the/eternity/auctionhistory/infrastructure/persistence/AuctionHistoryQueryDslRepository.java +++ b/src/main/java/until/the/eternity/auctionhistory/infrastructure/persistence/AuctionHistoryQueryDslRepository.java @@ -157,6 +157,42 @@ private BooleanBuilder buildHistoryPredicate( } } + // 인챈트 검색 조건 + if (c.enchantSearchRequest() != null) { + String enchantPrefix = c.enchantSearchRequest().enchantPrefix(); + String enchantSuffix = c.enchantSearchRequest().enchantSuffix(); + + if (enchantPrefix != null && !enchantPrefix.isBlank()) { + QAuctionHistoryItemOption optPrefix = + new QAuctionHistoryItemOption("enchantPrefix"); + var subPrefix = + JPAExpressions.select(optPrefix.auctionHistory.auctionBuyId) + .from(optPrefix) + .where( + optPrefix + .optionType + .eq("인챈트") + .and(optPrefix.optionSubType.eq("접두")) + .and(optPrefix.optionValue.eq(enchantPrefix))); + builder.and(ah.auctionBuyId.in(subPrefix)); + } + + if (enchantSuffix != null && !enchantSuffix.isBlank()) { + QAuctionHistoryItemOption optSuffix = + new QAuctionHistoryItemOption("enchantSuffix"); + var subSuffix = + JPAExpressions.select(optSuffix.auctionHistory.auctionBuyId) + .from(optSuffix) + .where( + optSuffix + .optionType + .eq("인챈트") + .and(optSuffix.optionSubType.eq("접미")) + .and(optSuffix.optionValue.eq(enchantSuffix))); + builder.and(ah.auctionBuyId.in(subSuffix)); + } + } + return builder; } diff --git a/src/main/java/until/the/eternity/auctionhistory/interfaces/rest/dto/request/AuctionHistorySearchRequest.java b/src/main/java/until/the/eternity/auctionhistory/interfaces/rest/dto/request/AuctionHistorySearchRequest.java index 72b2bb7..02dd616 100644 --- a/src/main/java/until/the/eternity/auctionhistory/interfaces/rest/dto/request/AuctionHistorySearchRequest.java +++ b/src/main/java/until/the/eternity/auctionhistory/interfaces/rest/dto/request/AuctionHistorySearchRequest.java @@ -16,4 +16,5 @@ public record AuctionHistorySearchRequest( @Schema(description = "소분류 카테고리", example = "검") String itemSubCategory, @Schema(description = "거래 일자 조건") DateAuctionBuyRequest dateAuctionBuyRequest, @Schema(description = "가격 검색 조건") PriceSearchRequest priceSearchRequest, - @Schema(description = "아이템 옵션 검색 조건") ItemOptionSearchRequest itemOptionSearchRequest) {} + @Schema(description = "아이템 옵션 검색 조건") ItemOptionSearchRequest itemOptionSearchRequest, + @Schema(description = "인챈트 검색 조건") EnchantSearchRequest enchantSearchRequest) {} diff --git a/src/main/java/until/the/eternity/auctionhistory/interfaces/rest/dto/request/EnchantSearchRequest.java b/src/main/java/until/the/eternity/auctionhistory/interfaces/rest/dto/request/EnchantSearchRequest.java new file mode 100644 index 0000000..5111883 --- /dev/null +++ b/src/main/java/until/the/eternity/auctionhistory/interfaces/rest/dto/request/EnchantSearchRequest.java @@ -0,0 +1,9 @@ +package until.the.eternity.auctionhistory.interfaces.rest.dto.request; + +import io.swagger.v3.oas.annotations.media.Schema; + +/** 인챈트 검색 조건 DTO */ +@Schema(description = "인챈트 검색 조건") +public record EnchantSearchRequest( + @Schema(description = "접두 인챈트 fullname (예: 당당한 (6랭크))") String enchantPrefix, + @Schema(description = "접미 인챈트 fullname (예: 강한 (5랭크))") String enchantSuffix) {} diff --git a/src/main/java/until/the/eternity/auctionrealtime/infrastructure/persistence/AuctionRealtimeQueryDslRepository.java b/src/main/java/until/the/eternity/auctionrealtime/infrastructure/persistence/AuctionRealtimeQueryDslRepository.java index 14e39bb..a60f726 100644 --- a/src/main/java/until/the/eternity/auctionrealtime/infrastructure/persistence/AuctionRealtimeQueryDslRepository.java +++ b/src/main/java/until/the/eternity/auctionrealtime/infrastructure/persistence/AuctionRealtimeQueryDslRepository.java @@ -125,6 +125,42 @@ private BooleanBuilder buildItemPredicate( } } + // 인챈트 검색 조건 + if (c.enchantSearchRequest() != null) { + String enchantPrefix = c.enchantSearchRequest().enchantPrefix(); + String enchantSuffix = c.enchantSearchRequest().enchantSuffix(); + + if (enchantPrefix != null && !enchantPrefix.isBlank()) { + QAuctionRealtimeItemOption optPrefix = + new QAuctionRealtimeItemOption("enchantPrefix"); + var subPrefix = + JPAExpressions.select(optPrefix.auctionRealtimeItem.id) + .from(optPrefix) + .where( + optPrefix + .optionType + .eq("인챈트") + .and(optPrefix.optionSubType.eq("접두")) + .and(optPrefix.optionValue.eq(enchantPrefix))); + builder.and(ar.id.in(subPrefix)); + } + + if (enchantSuffix != null && !enchantSuffix.isBlank()) { + QAuctionRealtimeItemOption optSuffix = + new QAuctionRealtimeItemOption("enchantSuffix"); + var subSuffix = + JPAExpressions.select(optSuffix.auctionRealtimeItem.id) + .from(optSuffix) + .where( + optSuffix + .optionType + .eq("인챈트") + .and(optSuffix.optionSubType.eq("접미")) + .and(optSuffix.optionValue.eq(enchantSuffix))); + builder.and(ar.id.in(subSuffix)); + } + } + return builder; } diff --git a/src/main/java/until/the/eternity/auctionrealtime/interfaces/rest/dto/request/AuctionRealtimeSearchRequest.java b/src/main/java/until/the/eternity/auctionrealtime/interfaces/rest/dto/request/AuctionRealtimeSearchRequest.java index dc8396d..258ca1e 100644 --- a/src/main/java/until/the/eternity/auctionrealtime/interfaces/rest/dto/request/AuctionRealtimeSearchRequest.java +++ b/src/main/java/until/the/eternity/auctionrealtime/interfaces/rest/dto/request/AuctionRealtimeSearchRequest.java @@ -1,6 +1,7 @@ package until.the.eternity.auctionrealtime.interfaces.rest.dto.request; import io.swagger.v3.oas.annotations.media.Schema; +import until.the.eternity.auctionhistory.interfaces.rest.dto.request.EnchantSearchRequest; import until.the.eternity.auctionhistory.interfaces.rest.dto.request.ItemOptionSearchRequest; import until.the.eternity.auctionhistory.interfaces.rest.dto.request.PriceSearchRequest; @@ -17,4 +18,5 @@ public record AuctionRealtimeSearchRequest( @Schema(description = "대분류 카테고리", example = "근거리 장비") String itemTopCategory, @Schema(description = "소분류 카테고리", example = "검") String itemSubCategory, @Schema(description = "가격 검색 조건") PriceSearchRequest priceSearchRequest, - @Schema(description = "아이템 옵션 검색 조건") ItemOptionSearchRequest itemOptionSearchRequest) {} + @Schema(description = "아이템 옵션 검색 조건") ItemOptionSearchRequest itemOptionSearchRequest, + @Schema(description = "인챈트 검색 조건") EnchantSearchRequest enchantSearchRequest) {} diff --git a/src/main/java/until/the/eternity/enchantinfo/application/service/EnchantInfoService.java b/src/main/java/until/the/eternity/enchantinfo/application/service/EnchantInfoService.java index c39ca8f..396b881 100644 --- a/src/main/java/until/the/eternity/enchantinfo/application/service/EnchantInfoService.java +++ b/src/main/java/until/the/eternity/enchantinfo/application/service/EnchantInfoService.java @@ -1,11 +1,14 @@ package until.the.eternity.enchantinfo.application.service; import java.util.List; +import java.util.Set; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import until.the.eternity.common.exception.CustomException; +import until.the.eternity.enchantinfo.domain.exception.EnchantInfoExceptionCode; import until.the.eternity.enchantinfo.domain.repository.EnchantInfoRepositoryPort; import until.the.eternity.enchantinfo.interfaces.rest.dto.response.EnchantInfoResponse; import until.the.eternity.enchantinfo.interfaces.rest.dto.response.EnchantInfoSyncResponse; @@ -15,13 +18,21 @@ @RequiredArgsConstructor public class EnchantInfoService { + private static final Set ALLOWED_AFFIX_POSITIONS = Set.of("접두", "접미"); + private final EnchantInfoRepositoryPort enchantInfoRepository; public Page findAll(Pageable pageable) { return enchantInfoRepository.findAll(pageable).map(EnchantInfoResponse::from); } - public List findAllFullnames() { + public List findAllFullnames(String affixPosition) { + if (affixPosition != null) { + if (!ALLOWED_AFFIX_POSITIONS.contains(affixPosition)) { + throw new CustomException(EnchantInfoExceptionCode.INVALID_AFFIX_POSITION); + } + return enchantInfoRepository.findAllFullnamesByAffixPosition(affixPosition); + } return enchantInfoRepository.findAllFullnames(); } diff --git a/src/main/java/until/the/eternity/enchantinfo/domain/exception/EnchantInfoExceptionCode.java b/src/main/java/until/the/eternity/enchantinfo/domain/exception/EnchantInfoExceptionCode.java new file mode 100644 index 0000000..ab88e52 --- /dev/null +++ b/src/main/java/until/the/eternity/enchantinfo/domain/exception/EnchantInfoExceptionCode.java @@ -0,0 +1,23 @@ +package until.the.eternity.enchantinfo.domain.exception; + +import static org.springframework.http.HttpStatus.BAD_REQUEST; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import until.the.eternity.common.exception.ExceptionCode; + +@Getter +@RequiredArgsConstructor +public enum EnchantInfoExceptionCode implements ExceptionCode { + INVALID_AFFIX_POSITION(BAD_REQUEST, "affix_position은 '접두' 또는 '접미'만 허용됩니다."), + ; + + private final HttpStatus status; + private final String message; + + @Override + public String getCode() { + return this.name(); + } +} diff --git a/src/main/java/until/the/eternity/enchantinfo/domain/repository/EnchantInfoRepositoryPort.java b/src/main/java/until/the/eternity/enchantinfo/domain/repository/EnchantInfoRepositoryPort.java index 937ca12..753bf6a 100644 --- a/src/main/java/until/the/eternity/enchantinfo/domain/repository/EnchantInfoRepositoryPort.java +++ b/src/main/java/until/the/eternity/enchantinfo/domain/repository/EnchantInfoRepositoryPort.java @@ -11,5 +11,7 @@ public interface EnchantInfoRepositoryPort { List findAllFullnames(); + List findAllFullnamesByAffixPosition(String affixPosition); + int upsertFromAuctionHistory(); } diff --git a/src/main/java/until/the/eternity/enchantinfo/infrastructure/persistence/EnchantInfoJpaRepository.java b/src/main/java/until/the/eternity/enchantinfo/infrastructure/persistence/EnchantInfoJpaRepository.java index 18fe98d..3cc8841 100644 --- a/src/main/java/until/the/eternity/enchantinfo/infrastructure/persistence/EnchantInfoJpaRepository.java +++ b/src/main/java/until/the/eternity/enchantinfo/infrastructure/persistence/EnchantInfoJpaRepository.java @@ -10,6 +10,9 @@ public interface EnchantInfoJpaRepository extends JpaRepository findAllFullnames(); + @Query("SELECT e.fullname FROM EnchantInfoEntity e WHERE e.affixPosition = :affixPosition ORDER BY e.id ASC") + List findAllFullnamesByAffixPosition(@org.springframework.data.repository.query.Param("affixPosition") String affixPosition); + @Modifying @Query( value = diff --git a/src/main/java/until/the/eternity/enchantinfo/infrastructure/persistence/EnchantInfoRepositoryPortImpl.java b/src/main/java/until/the/eternity/enchantinfo/infrastructure/persistence/EnchantInfoRepositoryPortImpl.java index 6417f72..e0c2731 100644 --- a/src/main/java/until/the/eternity/enchantinfo/infrastructure/persistence/EnchantInfoRepositoryPortImpl.java +++ b/src/main/java/until/the/eternity/enchantinfo/infrastructure/persistence/EnchantInfoRepositoryPortImpl.java @@ -23,6 +23,11 @@ public List findAllFullnames() { return jpaRepository.findAllFullnames(); } + @Override + public List findAllFullnamesByAffixPosition(String affixPosition) { + return jpaRepository.findAllFullnamesByAffixPosition(affixPosition); + } + @Override public int upsertFromAuctionHistory() { return jpaRepository.upsertFromAuctionHistory(); diff --git a/src/main/java/until/the/eternity/enchantinfo/interfaces/rest/controller/EnchantInfoController.java b/src/main/java/until/the/eternity/enchantinfo/interfaces/rest/controller/EnchantInfoController.java index 0a63690..14222db 100644 --- a/src/main/java/until/the/eternity/enchantinfo/interfaces/rest/controller/EnchantInfoController.java +++ b/src/main/java/until/the/eternity/enchantinfo/interfaces/rest/controller/EnchantInfoController.java @@ -1,6 +1,7 @@ package until.the.eternity.enchantinfo.interfaces.rest.controller; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; @@ -13,6 +14,7 @@ import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import until.the.eternity.enchantinfo.application.service.EnchantInfoService; import until.the.eternity.enchantinfo.interfaces.rest.dto.request.EnchantInfoPageRequestDto; @@ -41,10 +43,16 @@ public ResponseEntity> getEnchantInfos( @Operation( summary = "모든 인챈트 fullname 조회", - description = "페이지네이션 없이 저장된 모든 인챈트의 fullname(이름 및 랭크)을 한 번에 조회합니다.") + description = + "페이지네이션 없이 저장된 모든 인챈트의 fullname(이름 및 랭크)을 한 번에 조회합니다. " + + "affix_position을 지정하면 해당 위치(접두/접미)의 인챈트만 필터링합니다.") + @ApiResponse(responseCode = "400", description = "잘못된 affix_position 값 (접두 또는 접미만 허용)") @GetMapping("/fullnames") - public List getAllEnchantFullnames() { - return enchantInfoService.findAllFullnames(); + public List getAllEnchantFullnames( + @Parameter(description = "접두/접미 구분 필터 (허용값: 접두, 접미)", example = "접두") + @RequestParam(name = "affix_position", required = false) + String affixPosition) { + return enchantInfoService.findAllFullnames(affixPosition); } @Operation( From 1df783213aa20c1b2fc173992e3037f22025e3d0 Mon Sep 17 00:00:00 2001 From: dev-ant Date: Mon, 23 Feb 2026 22:18:05 +0900 Subject: [PATCH 2/2] =?UTF-8?q?fix:=20auction=20history=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=EC=97=90=20enchant=20?= =?UTF-8?q?=EC=A0=95=EB=B3=B4=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../persistence/EnchantInfoJpaRepository.java | 6 ++++-- .../rest/dto/request/EnchantInfoPageRequestDto.java | 4 +++- .../application/service/AuctionHistoryServiceTest.java | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/main/java/until/the/eternity/enchantinfo/infrastructure/persistence/EnchantInfoJpaRepository.java b/src/main/java/until/the/eternity/enchantinfo/infrastructure/persistence/EnchantInfoJpaRepository.java index 3cc8841..392c1fe 100644 --- a/src/main/java/until/the/eternity/enchantinfo/infrastructure/persistence/EnchantInfoJpaRepository.java +++ b/src/main/java/until/the/eternity/enchantinfo/infrastructure/persistence/EnchantInfoJpaRepository.java @@ -10,8 +10,10 @@ public interface EnchantInfoJpaRepository extends JpaRepository findAllFullnames(); - @Query("SELECT e.fullname FROM EnchantInfoEntity e WHERE e.affixPosition = :affixPosition ORDER BY e.id ASC") - List findAllFullnamesByAffixPosition(@org.springframework.data.repository.query.Param("affixPosition") String affixPosition); + @Query( + "SELECT e.fullname FROM EnchantInfoEntity e WHERE e.affixPosition = :affixPosition ORDER BY e.id ASC") + List findAllFullnamesByAffixPosition( + @org.springframework.data.repository.query.Param("affixPosition") String affixPosition); @Modifying @Query( diff --git a/src/main/java/until/the/eternity/enchantinfo/interfaces/rest/dto/request/EnchantInfoPageRequestDto.java b/src/main/java/until/the/eternity/enchantinfo/interfaces/rest/dto/request/EnchantInfoPageRequestDto.java index 508e694..0ad2a67 100644 --- a/src/main/java/until/the/eternity/enchantinfo/interfaces/rest/dto/request/EnchantInfoPageRequestDto.java +++ b/src/main/java/until/the/eternity/enchantinfo/interfaces/rest/dto/request/EnchantInfoPageRequestDto.java @@ -28,6 +28,8 @@ public Pageable toPageable() { this.direction != null ? this.direction : DEFAULT_DIRECTION; return PageRequest.of( - resolvedPage, resolvedSize, Sort.by(resolvedDirection.toSpringDirection(), SORT_FIELD)); + resolvedPage, + resolvedSize, + Sort.by(resolvedDirection.toSpringDirection(), SORT_FIELD)); } } diff --git a/src/test/java/until/the/eternity/auctionhistory/application/service/AuctionHistoryServiceTest.java b/src/test/java/until/the/eternity/auctionhistory/application/service/AuctionHistoryServiceTest.java index c9ad8af..df502fd 100644 --- a/src/test/java/until/the/eternity/auctionhistory/application/service/AuctionHistoryServiceTest.java +++ b/src/test/java/until/the/eternity/auctionhistory/application/service/AuctionHistoryServiceTest.java @@ -42,7 +42,7 @@ class AuctionHistoryServiceTest { void search_should_return_paged_response() { // given AuctionHistorySearchRequest searchRequest = - new AuctionHistorySearchRequest(null, null, null, null, null, null, null); + new AuctionHistorySearchRequest(null, null, null, null, null, null, null, null); PageRequestDto pageRequestDto = mock(PageRequestDto.class); Pageable pageable = PageRequest.of(0, 10); when(pageRequestDto.toPageable()).thenReturn(pageable);