diff --git a/src/main/java/until/the/eternity/metalwareinfo/application/service/MetalwareInfoService.java b/src/main/java/until/the/eternity/metalwareinfo/application/service/MetalwareInfoService.java new file mode 100644 index 00000000..499de1a0 --- /dev/null +++ b/src/main/java/until/the/eternity/metalwareinfo/application/service/MetalwareInfoService.java @@ -0,0 +1,21 @@ +package until.the.eternity.metalwareinfo.application.service; + +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import until.the.eternity.metalwareinfo.domain.repository.MetalwareInfoRepositoryPort; +import until.the.eternity.metalwareinfo.interfaces.rest.dto.response.MetalwareInfoResponse; + +@Service +@Transactional(readOnly = true) +@RequiredArgsConstructor +public class MetalwareInfoService { + + private final MetalwareInfoRepositoryPort metalwareInfoRepository; + + public List findAll() { + List metalwares = metalwareInfoRepository.findAllMetalwares(); + return MetalwareInfoResponse.from(metalwares); + } +} diff --git a/src/main/java/until/the/eternity/metalwareinfo/domain/repository/MetalwareInfoRepositoryPort.java b/src/main/java/until/the/eternity/metalwareinfo/domain/repository/MetalwareInfoRepositoryPort.java new file mode 100644 index 00000000..2a193791 --- /dev/null +++ b/src/main/java/until/the/eternity/metalwareinfo/domain/repository/MetalwareInfoRepositoryPort.java @@ -0,0 +1,7 @@ +package until.the.eternity.metalwareinfo.domain.repository; + +import java.util.List; + +public interface MetalwareInfoRepositoryPort { + List findAllMetalwares(); +} diff --git a/src/main/java/until/the/eternity/metalwareinfo/infrastructure/persistence/MetalwareInfoEntity.java b/src/main/java/until/the/eternity/metalwareinfo/infrastructure/persistence/MetalwareInfoEntity.java new file mode 100644 index 00000000..28ac6ff3 --- /dev/null +++ b/src/main/java/until/the/eternity/metalwareinfo/infrastructure/persistence/MetalwareInfoEntity.java @@ -0,0 +1,19 @@ +package until.the.eternity.metalwareinfo.infrastructure.persistence; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Table(name = "metalware_info") +@Getter +@NoArgsConstructor +public class MetalwareInfoEntity { + + @Id + @Column(name = "metalware", nullable = false, length = 50) + private String metalware; +} diff --git a/src/main/java/until/the/eternity/metalwareinfo/infrastructure/persistence/MetalwareInfoJpaRepository.java b/src/main/java/until/the/eternity/metalwareinfo/infrastructure/persistence/MetalwareInfoJpaRepository.java new file mode 100644 index 00000000..61481a24 --- /dev/null +++ b/src/main/java/until/the/eternity/metalwareinfo/infrastructure/persistence/MetalwareInfoJpaRepository.java @@ -0,0 +1,11 @@ +package until.the.eternity.metalwareinfo.infrastructure.persistence; + +import java.util.List; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; + +public interface MetalwareInfoJpaRepository extends JpaRepository { + + @Query("SELECT m.metalware FROM MetalwareInfoEntity m") + List findAllMetalwares(); +} diff --git a/src/main/java/until/the/eternity/metalwareinfo/infrastructure/persistence/MetalwareInfoRepositoryPortImpl.java b/src/main/java/until/the/eternity/metalwareinfo/infrastructure/persistence/MetalwareInfoRepositoryPortImpl.java new file mode 100644 index 00000000..9121dd1b --- /dev/null +++ b/src/main/java/until/the/eternity/metalwareinfo/infrastructure/persistence/MetalwareInfoRepositoryPortImpl.java @@ -0,0 +1,17 @@ +package until.the.eternity.metalwareinfo.infrastructure.persistence; + +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; +import until.the.eternity.metalwareinfo.domain.repository.MetalwareInfoRepositoryPort; + +@Repository +@RequiredArgsConstructor +public class MetalwareInfoRepositoryPortImpl implements MetalwareInfoRepositoryPort { + private final MetalwareInfoJpaRepository jpaRepository; + + @Override + public List findAllMetalwares() { + return jpaRepository.findAllMetalwares(); + } +} diff --git a/src/main/java/until/the/eternity/metalwareinfo/interfaces/rest/controller/MetalwareInfoController.java b/src/main/java/until/the/eternity/metalwareinfo/interfaces/rest/controller/MetalwareInfoController.java new file mode 100644 index 00000000..fa264595 --- /dev/null +++ b/src/main/java/until/the/eternity/metalwareinfo/interfaces/rest/controller/MetalwareInfoController.java @@ -0,0 +1,27 @@ +package until.the.eternity.metalwareinfo.interfaces.rest.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import until.the.eternity.metalwareinfo.application.service.MetalwareInfoService; +import until.the.eternity.metalwareinfo.interfaces.rest.dto.response.MetalwareInfoResponse; + +@RestController +@RequestMapping("/api/metalware-infos") +@RequiredArgsConstructor +@Tag(name = "Metalware Info", description = "세공 정보 조회 API") +public class MetalwareInfoController { + + private final MetalwareInfoService metalwareInfoService; + + // TODO: 세공 레벨별 능력치, 최대 레벨, 한계 돌파 최대 레벨은 수기로 추가 후 조회 시 사용 + @Operation(summary = "모든 세공 정보 조회", description = "시스템에 저장된 모든 세공 정보를 조회합니다.") + @GetMapping + public List getAllMetalwareInfos() { + return metalwareInfoService.findAll(); + } +} diff --git a/src/main/java/until/the/eternity/metalwareinfo/interfaces/rest/dto/response/MetalwareInfoResponse.java b/src/main/java/until/the/eternity/metalwareinfo/interfaces/rest/dto/response/MetalwareInfoResponse.java new file mode 100644 index 00000000..a3122710 --- /dev/null +++ b/src/main/java/until/the/eternity/metalwareinfo/interfaces/rest/dto/response/MetalwareInfoResponse.java @@ -0,0 +1,19 @@ +package until.the.eternity.metalwareinfo.interfaces.rest.dto.response; + +import io.swagger.v3.oas.annotations.media.Schema; +import java.util.List; +import java.util.stream.Collectors; +import lombok.Builder; + +@Builder +@Schema(description = "세공 정보 응답 DTO") +public record MetalwareInfoResponse( + @Schema(description = "세공", example = "불의 연성술") String metalware) { + public static MetalwareInfoResponse from(String metalware) { + return MetalwareInfoResponse.builder().metalware(metalware).build(); + } + + public static List from(List metalwares) { + return metalwares.stream().map(MetalwareInfoResponse::from).collect(Collectors.toList()); + } +} diff --git a/src/main/resources/db/migration/R__insert_item_info.sql b/src/main/resources/db/migration/R__insert_item_info.sql new file mode 100644 index 00000000..7f96dc75 --- /dev/null +++ b/src/main/resources/db/migration/R__insert_item_info.sql @@ -0,0 +1,3 @@ +delete from item_info; +INSERT INTO item_info (name, top_category, sub_category) +select distinct item_name, item_top_category, item_sub_category from auction_history; \ No newline at end of file diff --git a/src/main/resources/db/migration/R__insert_item_option_value_info.sql b/src/main/resources/db/migration/R__insert_item_option_value_info.sql new file mode 100644 index 00000000..188c53f7 --- /dev/null +++ b/src/main/resources/db/migration/R__insert_item_option_value_info.sql @@ -0,0 +1,3 @@ +delete from item_option_value_info; +insert into item_option_value_info (option_type, option_sub_type, option_value, option_value2, option_desc) +select distinct option_type, option_sub_type, option_value, option_value2, option_desc from auction_item_option; \ No newline at end of file diff --git a/src/main/resources/db/migration/R__insert_metalware_info_table.sql b/src/main/resources/db/migration/R__insert_metalware_info_table.sql new file mode 100644 index 00000000..d7920370 --- /dev/null +++ b/src/main/resources/db/migration/R__insert_metalware_info_table.sql @@ -0,0 +1,5 @@ +delete from metalware_info; +insert into metalware_info (metalware) +select distinct regexp_replace(regexp_substr(option_value, ' ?[^\(]+'), ' ?[0-9][0-9]? ?레벨', '') as metalware +from auction_item_option +where option_type = '세공 옵션'; \ No newline at end of file diff --git a/src/main/resources/db/migration/V9__create_metalware_info_table.sql b/src/main/resources/db/migration/V9__create_metalware_info_table.sql new file mode 100644 index 00000000..5c2e31f1 --- /dev/null +++ b/src/main/resources/db/migration/V9__create_metalware_info_table.sql @@ -0,0 +1,14 @@ +create table metalware_info +( + metalware VARCHAR(50) not null primary key comment '세공', + level_attribute VARCHAR(50) comment '레벨별 능력치 효과', + max_level TINYINT comment '레벨 최대 수치', + limit_break_level TINYINT comment '한계 돌파 레벨 최대 수치' +) + comment '세공 정보 테이블'; + +-- 데이터 적재 쿼리 +--insert into metalware_info (metalware) +--select distinct regexp_replace(regexp_substr(option_value, ' ?[^\(]+'), ' ?[0-9][0-9]? ?레벨', '') as metalware +--from auction_item_option +--where option_type = '세공 옵션'; \ No newline at end of file diff --git a/src/test/java/until/the/eternity/metalwareinfo/application/service/MetalwareInfoServiceTest.java b/src/test/java/until/the/eternity/metalwareinfo/application/service/MetalwareInfoServiceTest.java new file mode 100644 index 00000000..45f7b8da --- /dev/null +++ b/src/test/java/until/the/eternity/metalwareinfo/application/service/MetalwareInfoServiceTest.java @@ -0,0 +1,54 @@ +package until.the.eternity.metalwareinfo.application.service; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.*; + +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import until.the.eternity.metalwareinfo.domain.repository.MetalwareInfoRepositoryPort; +import until.the.eternity.metalwareinfo.interfaces.rest.dto.response.MetalwareInfoResponse; + +@ExtendWith(MockitoExtension.class) +class MetalwareInfoServiceTest { + + @Mock private MetalwareInfoRepositoryPort repositoryPort; + + @InjectMocks private MetalwareInfoService service; + + @Test + @DisplayName("모든 세공 정보를 조회하면 세공 목록을 반환한다") + void findAll_should_return_all_metalwares() { + // given + List metalwares = List.of("불의 연성술", "물의 연성술", "바람의 연성술"); + when(repositoryPort.findAllMetalwares()).thenReturn(metalwares); + + // when + List result = service.findAll(); + + // then + assertThat(result).hasSize(3); + assertThat(result.get(0).metalware()).isEqualTo("불의 연성술"); + assertThat(result.get(1).metalware()).isEqualTo("물의 연성술"); + assertThat(result.get(2).metalware()).isEqualTo("바람의 연성술"); + verify(repositoryPort).findAllMetalwares(); + } + + @Test + @DisplayName("세공 정보가 없으면 빈 목록을 반환한다") + void findAll_should_return_empty_list_when_no_data() { + // given + when(repositoryPort.findAllMetalwares()).thenReturn(List.of()); + + // when + List result = service.findAll(); + + // then + assertThat(result).isEmpty(); + verify(repositoryPort).findAllMetalwares(); + } +}