From 361ffce8a638f9f342e33569133683d4c8814154 Mon Sep 17 00:00:00 2001 From: blaxsior Date: Tue, 6 Aug 2024 14:26:05 +0900 Subject: [PATCH 1/8] =?UTF-8?q?[chore]=20json=20=EC=A7=81=EB=A0=AC?= =?UTF-8?q?=ED=99=94=20=EC=8B=9C=20null=EC=9D=80=20=EB=AC=B4=EC=8B=9C?= =?UTF-8?q?=ED=95=98=EA=B2=8C=20=EC=84=A4=EC=A0=95(#32)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../softeer/orange/config/AppConfig.java | 4 +++ .../event/common/entity/EventFrame.java | 2 +- .../softeer/orange/event/dto/EventDto.java | 34 ++++++++++++++++++- 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/src/main/java/hyundai/softeer/orange/config/AppConfig.java b/src/main/java/hyundai/softeer/orange/config/AppConfig.java index 92286c18..066b920b 100644 --- a/src/main/java/hyundai/softeer/orange/config/AppConfig.java +++ b/src/main/java/hyundai/softeer/orange/config/AppConfig.java @@ -1,5 +1,6 @@ package hyundai.softeer.orange.config; +import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; @@ -15,6 +16,9 @@ public ObjectMapper objectMapper() { objectMapper.registerModule(new JavaTimeModule()); // timestamp를 문자열로 전달 objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + // serialization 시 값 없는 필드 = null을 노출하지 않도록 제외. 문제가 되는 경우 구체적인 dto로 이동할 예정. + // 참고: https://www.baeldung.com/jackson-ignore-null-fields + objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); return objectMapper; } } diff --git a/src/main/java/hyundai/softeer/orange/event/common/entity/EventFrame.java b/src/main/java/hyundai/softeer/orange/event/common/entity/EventFrame.java index 33091ba9..909f1688 100644 --- a/src/main/java/hyundai/softeer/orange/event/common/entity/EventFrame.java +++ b/src/main/java/hyundai/softeer/orange/event/common/entity/EventFrame.java @@ -19,7 +19,7 @@ public class EventFrame { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @Column + @Column(unique = true, nullable = false) private String name; @OneToMany(mappedBy="eventFrame") diff --git a/src/main/java/hyundai/softeer/orange/event/dto/EventDto.java b/src/main/java/hyundai/softeer/orange/event/dto/EventDto.java index c761b219..79d64d2c 100644 --- a/src/main/java/hyundai/softeer/orange/event/dto/EventDto.java +++ b/src/main/java/hyundai/softeer/orange/event/dto/EventDto.java @@ -24,32 +24,64 @@ public class EventDto { @NotNull(groups = {EventEditGroup.class}) Long id; + /** + * HD000000_000 형식으로 구성된 id 값 + */ @NotNull(groups = {EventEditGroup.class}) String eventId; + /** + * 이벤트의 이름 + */ @Size(min = 1, max = 40) private String name; + + /** + * 이벤트에 대한 설명 + */ @Size(min = 1, max = 100) private String description; + + /** + * 이벤트 시작 시간 + */ @NotNull private LocalDateTime startTime; + + /** + * 이벤트 종료 시간 + */ @NotNull private LocalDateTime endTime; + + /** + * 이벤트 페이지의 url + */ @NotBlank private String url; + + /** + * 이벤트의 타입 + */ @NotNull private EventType eventType; /** - * 이벤트에 대한 태그 ex) 2024 현대 여름 이벤트 + * 이벤트 프레임 정보. 추후 변경될 수 있음. */ @NotNull private String tag; + /** + * fcfs 이벤트 내용을 정의하는 부분 + */ @Valid private List fcfs; + /** + * draw 이벤트 내용을 정의하는 부분 + */ @Valid private DrawEventDto draw; From cc11b40d333d1ab07286bd41db31f173e40c393c Mon Sep 17 00:00:00 2001 From: blaxsior Date: Tue, 6 Aug 2024 14:28:17 +0900 Subject: [PATCH 2/8] =?UTF-8?q?[feat]=20jpa=20criteria=20query=20=EA=B8=B0?= =?UTF-8?q?=EB=B0=98=EC=9C=BC=EB=A1=9C=20=EC=A1=B0=ED=9A=8C=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EA=B5=AC=ED=98=84(#32)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../orange/event/common/EventConst.java | 7 ++ .../query/EventSearchQueryParser.java | 26 +++++++ .../common/controller/EventController.java | 35 ++++++++-- .../CustomEventMetadataRepository.java | 10 +++ .../EventFrameRepositoryDefaultImpl.java | 46 ++++++++++++ .../repository/EventMetadataRepository.java | 2 +- .../event/common/service/EventService.java | 70 +++++++++++++++++++ .../orange/event/dto/BriefEventDto.java | 45 ++++++++++++ 8 files changed, 235 insertions(+), 6 deletions(-) create mode 100644 src/main/java/hyundai/softeer/orange/event/common/component/query/EventSearchQueryParser.java create mode 100644 src/main/java/hyundai/softeer/orange/event/common/repository/CustomEventMetadataRepository.java create mode 100644 src/main/java/hyundai/softeer/orange/event/common/repository/EventFrameRepositoryDefaultImpl.java create mode 100644 src/main/java/hyundai/softeer/orange/event/dto/BriefEventDto.java diff --git a/src/main/java/hyundai/softeer/orange/event/common/EventConst.java b/src/main/java/hyundai/softeer/orange/event/common/EventConst.java index 88c7149a..5ec9cf3c 100644 --- a/src/main/java/hyundai/softeer/orange/event/common/EventConst.java +++ b/src/main/java/hyundai/softeer/orange/event/common/EventConst.java @@ -1,5 +1,12 @@ package hyundai.softeer.orange.event.common; +import java.util.Set; + public class EventConst { public static final String REDIS_KEY_PREFIX = "@event_key:"; + + // 검색 기능 관련 상수들 + public static final int EVENT_DEFAULT_PAGE = 0; + public static final int EVENT_DEFAULT_SIZE = 5; + public static final Set sortableFields = Set.of("eventId", "name", "startTime", "endTime", "eventType"); } diff --git a/src/main/java/hyundai/softeer/orange/event/common/component/query/EventSearchQueryParser.java b/src/main/java/hyundai/softeer/orange/event/common/component/query/EventSearchQueryParser.java new file mode 100644 index 00000000..357e7dea --- /dev/null +++ b/src/main/java/hyundai/softeer/orange/event/common/component/query/EventSearchQueryParser.java @@ -0,0 +1,26 @@ +package hyundai.softeer.orange.event.common.component.query; + +import java.util.HashMap; +import java.util.Map; + +public class EventSearchQueryParser { + public static Map parse(String searchQuery) { + Map map = new HashMap<>(); + // searchQuery가 null이면 빈 map 반환 + if (searchQuery == null) return map; + + String[] queries = searchQuery.split(","); + + for (String query : queries) { + String[] pair = query.split(":"); + if(pair.length <= 0 || pair.length > 2) continue; // 값이 없거나 넘치면 무시. + String key = pair[0].trim(); + String value; + if(pair.length > 1) value = pair[1].trim(); + else value = ""; + + map.put(key, value); + } + return map; + } +} diff --git a/src/main/java/hyundai/softeer/orange/event/common/controller/EventController.java b/src/main/java/hyundai/softeer/orange/event/common/controller/EventController.java index 87cffaa4..2b36dcda 100644 --- a/src/main/java/hyundai/softeer/orange/event/common/controller/EventController.java +++ b/src/main/java/hyundai/softeer/orange/event/common/controller/EventController.java @@ -3,6 +3,7 @@ import hyundai.softeer.orange.core.auth.Auth; import hyundai.softeer.orange.core.auth.AuthRole; import hyundai.softeer.orange.event.common.service.EventService; +import hyundai.softeer.orange.event.dto.BriefEventDto; import hyundai.softeer.orange.event.dto.EventDto; import hyundai.softeer.orange.event.dto.EventFrameCreateRequest; import hyundai.softeer.orange.event.dto.group.EventEditGroup; @@ -16,16 +17,43 @@ import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; +import java.util.List; + /** * 이벤트 관련 CRUD를 다루는 API */ @Slf4j @RequiredArgsConstructor -@RequestMapping("/api/v1/event") +@RequestMapping("/api/v1/events") @RestController public class EventController { private final EventService eventService; + + /** + * + * @param search + * @param sort 정렬 기준. (eventId|:(asc|desc) + * @param page 페이지 번호 + * @param size 한번에 검색하는 이벤트 개수 + * @return 요청한 이벤트 리스트 + */ + @Auth({AuthRole.admin}) + @GetMapping + @Operation(summary = "이벤트 리스트 획득", description = "관리자가 이벤트 목록을 검색한다. 검색어, sort 기준 등을 정의할 수 있다.", responses = { + @ApiResponse(responseCode = "200", description = "성공적으로 이벤트 목록을 반환한다"), + @ApiResponse(responseCode = "5xx", description = "서버 내부적 에러"), + @ApiResponse(responseCode = "4xx", description = "클라이언트 에러 (보통 page / size 값을 잘못 지정) ") + }) + public ResponseEntity> getEvents( + @RequestParam(required = false) String search, + @RequestParam(required = false) String sort, + @RequestParam(required = false) Integer page, + @RequestParam(required = false) Integer size) { + List events = eventService.searchEvents(search, sort, page, size); + return ResponseEntity.ok(events); + } + @Auth({AuthRole.admin}) @PostMapping @Operation(summary = "이벤트 생성", description = "관리자가 이벤트를 새롭게 등록한다", responses = { @@ -57,14 +85,11 @@ public ResponseEntity getEventEditData( @ApiResponse(responseCode = "4xx", description = "유저 측 실수로 이벤트 생성 실패") }) public ResponseEntity editEvent( - @Validated({EventEditGroup.class}) @RequestBody EventDto eventDto - ) { + @Validated({EventEditGroup.class}) @RequestBody EventDto eventDto) { eventService.editEvent(eventDto); return ResponseEntity.ok().build(); } - - @Auth({AuthRole.admin}) @PostMapping("/frame") @Operation(summary = "이벤트 프레임 생성", description = "관리자가 이벤트 프레임을 새롭게 등록한다", responses = { diff --git a/src/main/java/hyundai/softeer/orange/event/common/repository/CustomEventMetadataRepository.java b/src/main/java/hyundai/softeer/orange/event/common/repository/CustomEventMetadataRepository.java new file mode 100644 index 00000000..c40c2d5b --- /dev/null +++ b/src/main/java/hyundai/softeer/orange/event/common/repository/CustomEventMetadataRepository.java @@ -0,0 +1,10 @@ +package hyundai.softeer.orange.event.common.repository; + +import hyundai.softeer.orange.event.common.entity.EventMetadata; +import org.springframework.data.domain.Pageable; + +import java.util.List; + +public interface CustomEventMetadataRepository { + List findAllBySearch(String search, Pageable pageable); +} diff --git a/src/main/java/hyundai/softeer/orange/event/common/repository/EventFrameRepositoryDefaultImpl.java b/src/main/java/hyundai/softeer/orange/event/common/repository/EventFrameRepositoryDefaultImpl.java new file mode 100644 index 00000000..9766b59d --- /dev/null +++ b/src/main/java/hyundai/softeer/orange/event/common/repository/EventFrameRepositoryDefaultImpl.java @@ -0,0 +1,46 @@ +package hyundai.softeer.orange.event.common.repository; + +import hyundai.softeer.orange.event.common.entity.EventMetadata; +import jakarta.persistence.EntityManager; +import jakarta.persistence.TypedQuery; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Predicate; +import jakarta.persistence.criteria.Root; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.query.QueryUtils; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +@RequiredArgsConstructor +public class EventFrameRepositoryDefaultImpl implements CustomEventMetadataRepository { + private final EntityManager em; + + @Override + public List findAllBySearch(String search, Pageable pageable) { + CriteriaBuilder cb = em.getCriteriaBuilder(); + // 쿼리 객체 생성 + CriteriaQuery cq = cb.createQuery(EventMetadata.class); + // 루트 객체 생성 = Q 객체와 유사한 역할인듯? + Root metadata = cq.from(EventMetadata.class); + + // 정렬 기준이 존재할 + if(search != null && !search.isEmpty()) { + Predicate condName = cb.like(metadata.get("name"), "%" + search + "%"); + Predicate condEventId = cb.like(metadata.get("eventId"), "%" + search + "%"); + cq.where(condName, condEventId); + } + + // https://stackoverflow.com/questions/49463512/transform-from-pageable-getsort-to-listorder-to-sort-a-query-made-by-criteri + cq.orderBy(QueryUtils.toOrders(pageable.getSort(), metadata, cb)).select(metadata); + + TypedQuery query = em.createQuery(cq); + query.setFirstResult((int) pageable.getOffset()); + query.setMaxResults(pageable.getPageSize()); + + return query.getResultList(); + } +} diff --git a/src/main/java/hyundai/softeer/orange/event/common/repository/EventMetadataRepository.java b/src/main/java/hyundai/softeer/orange/event/common/repository/EventMetadataRepository.java index ff2f50cc..ddd29400 100644 --- a/src/main/java/hyundai/softeer/orange/event/common/repository/EventMetadataRepository.java +++ b/src/main/java/hyundai/softeer/orange/event/common/repository/EventMetadataRepository.java @@ -7,6 +7,6 @@ import java.util.Optional; @Repository -public interface EventMetadataRepository extends JpaRepository { +public interface EventMetadataRepository extends JpaRepository, CustomEventMetadataRepository { Optional findFirstByEventId(String eventId); } diff --git a/src/main/java/hyundai/softeer/orange/event/common/service/EventService.java b/src/main/java/hyundai/softeer/orange/event/common/service/EventService.java index d386a28d..ec7a79fb 100644 --- a/src/main/java/hyundai/softeer/orange/event/common/service/EventService.java +++ b/src/main/java/hyundai/softeer/orange/event/common/service/EventService.java @@ -1,8 +1,10 @@ package hyundai.softeer.orange.event.common.service; import hyundai.softeer.orange.common.ErrorCode; +import hyundai.softeer.orange.event.common.EventConst; import hyundai.softeer.orange.event.common.component.eventFieldMapper.EventFieldMapperMatcher; import hyundai.softeer.orange.event.common.component.eventFieldMapper.mapper.EventFieldMapper; +import hyundai.softeer.orange.event.common.component.query.EventSearchQueryParser; import hyundai.softeer.orange.event.common.entity.EventFrame; import hyundai.softeer.orange.event.common.entity.EventMetadata; import hyundai.softeer.orange.event.common.enums.EventStatus; @@ -11,12 +13,17 @@ import hyundai.softeer.orange.event.common.repository.EventFrameRepository; import hyundai.softeer.orange.event.common.repository.EventMetadataRepository; import hyundai.softeer.orange.event.component.EventKeyGenerator; +import hyundai.softeer.orange.event.dto.BriefEventDto; import hyundai.softeer.orange.event.dto.EventDto; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.util.ArrayList; +import java.util.List; import java.util.Optional; /** @@ -31,6 +38,10 @@ public class EventService { private final EventFieldMapperMatcher mapperMatcher; private final EventKeyGenerator keyGenerator; + /** + * 이벤트를 생성한다. + * @param eventDto 이벤트 dto + */ @Transactional public void createEvent(EventDto eventDto) { // 1. eventframe을 찾는다. 없으면 작업이 의미 X @@ -62,6 +73,10 @@ public void createEvent(EventDto eventDto) { emRepository.save(eventMetadata); } + /** + * 이벤트를 수정한다. + * @param eventDto 수정 데이터가 담긴 이벤트 dto + */ @Transactional public void editEvent(EventDto eventDto) { String eventId = eventDto.getEventId(); @@ -81,6 +96,11 @@ public void editEvent(EventDto eventDto) { emRepository.save(eventMetadata); } + /** + * 이벤트에 대한 초기 데이터 정보를 제공한다. + * @param eventId 요청한 이벤트의 id + * @return 이벤트 내용을 담은 dto + */ @Transactional(readOnly = true) public EventDto getEventInfo(String eventId) { Optional metadataOpt = emRepository.findFirstByEventId(eventId); @@ -105,6 +125,56 @@ public EventDto getEventInfo(String eventId) { return eventDto; } + /** + * 매칭되는 이벤트를 탐색한다 + * @param search 이벤트 검색 내용 + * @param sortQuery 정렬 내용이 담긴 쿼리 + * @param page 현재 페이지 + * @param size 페이지의 크기 + * @return 매칭된 이벤트 목록 + */ + @Transactional(readOnly = true) + public List searchEvents(String search, String sortQuery, Integer page, Integer size) { + + List orders = new ArrayList<>(); + for(var entries: EventSearchQueryParser.parse(sortQuery).entrySet()){ + String field = entries.getKey(); + String value = entries.getValue().toLowerCase(); + + if(!EventConst.sortableFields.contains(field)) continue; + switch (value) { + case "asc": case "": + orders.add(Sort.Order.asc(field)); + break; + case "desc": + orders.add(Sort.Order.desc(field)); + break; + } + } + Sort sort = Sort.by(orders); + + PageRequest pageInfo = PageRequest.of( + page != null ? page : EventConst.EVENT_DEFAULT_PAGE, + size != null ? size : EventConst.EVENT_DEFAULT_SIZE, + sort + ); + + List events = emRepository.findAllBySearch(search, pageInfo); + + return events.stream().map( + it -> BriefEventDto.of( + it.getEventId(), + it.getName(), + it.getStartTime(), + it.getEndTime(), + it.getEventType() + )).toList(); + } + + /** + * 이벤트 프레임을 생성한다. + * @param name 이벤트 프레임의 이름 + */ @Transactional public void createEventFrame(String name) { EventFrame eventFrame = EventFrame.of(name); diff --git a/src/main/java/hyundai/softeer/orange/event/dto/BriefEventDto.java b/src/main/java/hyundai/softeer/orange/event/dto/BriefEventDto.java new file mode 100644 index 00000000..47ec276f --- /dev/null +++ b/src/main/java/hyundai/softeer/orange/event/dto/BriefEventDto.java @@ -0,0 +1,45 @@ +package hyundai.softeer.orange.event.dto; + +import hyundai.softeer.orange.event.common.enums.EventType; +import hyundai.softeer.orange.event.dto.group.EventEditGroup; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; + +import java.time.LocalDateTime; + +public class BriefEventDto { + /** + * HD000000_000 형식으로 구성된 id 값 + */ + String eventId; + /** + * 이벤트의 이름 + */ + private String name; + + /** + * 이벤트 시작 시간 + */ + private LocalDateTime startTime; + + /** + * 이벤트 종료 시간 + */ + private LocalDateTime endTime; + + /** + * 이벤트의 타입 + */ + private EventType eventType; + + public static BriefEventDto of(String eventId, String name, LocalDateTime startTime, LocalDateTime endTime, EventType eventType) { + BriefEventDto briefEventDto = new BriefEventDto(); + briefEventDto.eventId = eventId; + briefEventDto.name = name; + briefEventDto.startTime = startTime; + briefEventDto.endTime = endTime; + briefEventDto.eventType = eventType; + return briefEventDto; + } +} From 607d5c21af04b66b59f16a725aec9a51cb78186c Mon Sep 17 00:00:00 2001 From: blaxsior Date: Tue, 6 Aug 2024 15:35:53 +0900 Subject: [PATCH 3/8] =?UTF-8?q?[refactor]=20=EA=B2=80=EC=83=89=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=EC=9D=84=20jpa=20criteria=20query=20=EA=B8=B0?= =?UTF-8?q?=EB=B0=98=EC=97=90=EC=84=9C=20jpa=20specification=EC=9D=84=20?= =?UTF-8?q?=EC=9D=B4=EC=9A=A9=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98?= =?UTF-8?q?=EC=A0=95,=20=EB=B3=B4=EC=9D=BC=EB=9F=AC=20=ED=94=8C=EB=A0=88?= =?UTF-8?q?=EC=9D=B4=ED=8A=B8=20=EC=A0=9C=EA=B1=B0(#32)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/controller/EventController.java | 22 ++-- .../CustomEventMetadataRepository.java | 10 -- .../EventFrameRepositoryDefaultImpl.java | 46 -------- .../repository/EventMetadataRepository.java | 3 +- .../common/repository/EventSpecification.java | 17 +++ .../event/common/service/EventService.java | 6 +- .../orange/event/dto/BriefEventDto.java | 6 +- .../repository/EventSpecificationTest.java | 104 ++++++++++++++++++ 8 files changed, 145 insertions(+), 69 deletions(-) delete mode 100644 src/main/java/hyundai/softeer/orange/event/common/repository/CustomEventMetadataRepository.java delete mode 100644 src/main/java/hyundai/softeer/orange/event/common/repository/EventFrameRepositoryDefaultImpl.java create mode 100644 src/main/java/hyundai/softeer/orange/event/common/repository/EventSpecification.java create mode 100644 src/test/java/hyundai/softeer/orange/event/common/repository/EventSpecificationTest.java diff --git a/src/main/java/hyundai/softeer/orange/event/common/controller/EventController.java b/src/main/java/hyundai/softeer/orange/event/common/controller/EventController.java index 2b36dcda..cba8d1ae 100644 --- a/src/main/java/hyundai/softeer/orange/event/common/controller/EventController.java +++ b/src/main/java/hyundai/softeer/orange/event/common/controller/EventController.java @@ -8,6 +8,8 @@ import hyundai.softeer.orange.event.dto.EventFrameCreateRequest; import hyundai.softeer.orange.event.dto.group.EventEditGroup; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; @@ -15,6 +17,7 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; +import org.springframework.web.ErrorResponse; import org.springframework.web.bind.annotation.*; import java.util.List; @@ -32,8 +35,8 @@ public class EventController { /** * - * @param search - * @param sort 정렬 기준. (eventId|:(asc|desc) + * @param search 검색어 + * @param sort 정렬 기준. (eventId|name|startTime|endTime|eventType)(:(asc|desc))? 패턴이 ,로 나뉘는 형태. ex) eventId,name:asc,startTime:desc * @param page 페이지 번호 * @param size 한번에 검색하는 이벤트 개수 * @return 요청한 이벤트 리스트 @@ -43,7 +46,7 @@ public class EventController { @Operation(summary = "이벤트 리스트 획득", description = "관리자가 이벤트 목록을 검색한다. 검색어, sort 기준 등을 정의할 수 있다.", responses = { @ApiResponse(responseCode = "200", description = "성공적으로 이벤트 목록을 반환한다"), @ApiResponse(responseCode = "5xx", description = "서버 내부적 에러"), - @ApiResponse(responseCode = "4xx", description = "클라이언트 에러 (보통 page / size 값을 잘못 지정) ") + @ApiResponse(responseCode = "4xx", description = "클라이언트 에러 (보통 page / size 값을 잘못 지정. 숫자가 아닌 경우 등) ") }) public ResponseEntity> getEvents( @RequestParam(required = false) String search, @@ -60,14 +63,19 @@ public ResponseEntity> getEvents( @ApiResponse(responseCode = "201", description = "이벤트 생성 성공"), @ApiResponse(responseCode = "4xx", description = "유저 측 실수로 이벤트 생성 실패") }) - public ResponseEntity createEvent(@Validated @RequestBody EventDto eventDto) { + public ResponseEntity createEvent(@Validated @RequestBody EventDto eventDto) { eventService.createEvent(eventDto); return ResponseEntity.status(HttpStatus.CREATED).build(); } + /** + * + * @param eventId 이벤트 ID. HD000000~로 시작하는 그것 + * @return 해당 이벤트에 대한 정보 + */ @Auth({AuthRole.admin}) @GetMapping("/edit") - @Operation(summary = "이벤트 수정 초기 데이터 획득", description = "이벤트 정보 수정을 위해 초기 정보를 받는다", responses = { + @Operation(summary = "이벤트 데이터 획득", description = "이벤트 초기 정보를 받는다", responses = { @ApiResponse(responseCode = "200", description = "이벤트 정보를 정상적으로 받음"), @ApiResponse(responseCode = "404", description = "대응되는 이벤트가 존재하지 않음") }) @@ -84,7 +92,7 @@ public ResponseEntity getEventEditData( @ApiResponse(responseCode = "200", description = "이벤트 생성 성공"), @ApiResponse(responseCode = "4xx", description = "유저 측 실수로 이벤트 생성 실패") }) - public ResponseEntity editEvent( + public ResponseEntity editEvent( @Validated({EventEditGroup.class}) @RequestBody EventDto eventDto) { eventService.editEvent(eventDto); return ResponseEntity.ok().build(); @@ -96,7 +104,7 @@ public ResponseEntity editEvent( @ApiResponse(responseCode = "201", description = "이벤트 프레임 생성 성공"), @ApiResponse(responseCode = "4xx", description = "이벤트 프레임 생성 실패") }) - public ResponseEntity createEventFrame(@Valid @RequestBody EventFrameCreateRequest req) { + public ResponseEntity createEventFrame(@Valid @RequestBody EventFrameCreateRequest req) { eventService.createEventFrame(req.getName()); return ResponseEntity.status(HttpStatus.CREATED).build(); } diff --git a/src/main/java/hyundai/softeer/orange/event/common/repository/CustomEventMetadataRepository.java b/src/main/java/hyundai/softeer/orange/event/common/repository/CustomEventMetadataRepository.java deleted file mode 100644 index c40c2d5b..00000000 --- a/src/main/java/hyundai/softeer/orange/event/common/repository/CustomEventMetadataRepository.java +++ /dev/null @@ -1,10 +0,0 @@ -package hyundai.softeer.orange.event.common.repository; - -import hyundai.softeer.orange.event.common.entity.EventMetadata; -import org.springframework.data.domain.Pageable; - -import java.util.List; - -public interface CustomEventMetadataRepository { - List findAllBySearch(String search, Pageable pageable); -} diff --git a/src/main/java/hyundai/softeer/orange/event/common/repository/EventFrameRepositoryDefaultImpl.java b/src/main/java/hyundai/softeer/orange/event/common/repository/EventFrameRepositoryDefaultImpl.java deleted file mode 100644 index 9766b59d..00000000 --- a/src/main/java/hyundai/softeer/orange/event/common/repository/EventFrameRepositoryDefaultImpl.java +++ /dev/null @@ -1,46 +0,0 @@ -package hyundai.softeer.orange.event.common.repository; - -import hyundai.softeer.orange.event.common.entity.EventMetadata; -import jakarta.persistence.EntityManager; -import jakarta.persistence.TypedQuery; -import jakarta.persistence.criteria.CriteriaBuilder; -import jakarta.persistence.criteria.CriteriaQuery; -import jakarta.persistence.criteria.Predicate; -import jakarta.persistence.criteria.Root; -import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Pageable; -import org.springframework.data.jpa.repository.query.QueryUtils; -import org.springframework.stereotype.Repository; - -import java.util.List; - -@Repository -@RequiredArgsConstructor -public class EventFrameRepositoryDefaultImpl implements CustomEventMetadataRepository { - private final EntityManager em; - - @Override - public List findAllBySearch(String search, Pageable pageable) { - CriteriaBuilder cb = em.getCriteriaBuilder(); - // 쿼리 객체 생성 - CriteriaQuery cq = cb.createQuery(EventMetadata.class); - // 루트 객체 생성 = Q 객체와 유사한 역할인듯? - Root metadata = cq.from(EventMetadata.class); - - // 정렬 기준이 존재할 - if(search != null && !search.isEmpty()) { - Predicate condName = cb.like(metadata.get("name"), "%" + search + "%"); - Predicate condEventId = cb.like(metadata.get("eventId"), "%" + search + "%"); - cq.where(condName, condEventId); - } - - // https://stackoverflow.com/questions/49463512/transform-from-pageable-getsort-to-listorder-to-sort-a-query-made-by-criteri - cq.orderBy(QueryUtils.toOrders(pageable.getSort(), metadata, cb)).select(metadata); - - TypedQuery query = em.createQuery(cq); - query.setFirstResult((int) pageable.getOffset()); - query.setMaxResults(pageable.getPageSize()); - - return query.getResultList(); - } -} diff --git a/src/main/java/hyundai/softeer/orange/event/common/repository/EventMetadataRepository.java b/src/main/java/hyundai/softeer/orange/event/common/repository/EventMetadataRepository.java index ddd29400..a67ede12 100644 --- a/src/main/java/hyundai/softeer/orange/event/common/repository/EventMetadataRepository.java +++ b/src/main/java/hyundai/softeer/orange/event/common/repository/EventMetadataRepository.java @@ -2,11 +2,12 @@ import hyundai.softeer.orange.event.common.entity.EventMetadata; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import org.springframework.stereotype.Repository; import java.util.Optional; @Repository -public interface EventMetadataRepository extends JpaRepository, CustomEventMetadataRepository { +public interface EventMetadataRepository extends JpaRepository, JpaSpecificationExecutor { Optional findFirstByEventId(String eventId); } diff --git a/src/main/java/hyundai/softeer/orange/event/common/repository/EventSpecification.java b/src/main/java/hyundai/softeer/orange/event/common/repository/EventSpecification.java new file mode 100644 index 00000000..1b18eff6 --- /dev/null +++ b/src/main/java/hyundai/softeer/orange/event/common/repository/EventSpecification.java @@ -0,0 +1,17 @@ +package hyundai.softeer.orange.event.common.repository; + +import hyundai.softeer.orange.event.common.entity.EventMetadata; +import jakarta.persistence.criteria.Predicate; +import org.springframework.data.jpa.domain.Specification; + +public class EventSpecification { + public static Specification withSearch(String search) { + return (metadata, query, cb) -> { + if (search == null || search.isEmpty()) return cb.conjunction(); + + Predicate searchName = cb.like(metadata.get("name"), "%" + search + "%"); + Predicate searchEventId = cb.like(metadata.get("eventId"), "%" + search + "%"); + return cb.or(searchName, searchEventId); + }; + } +} diff --git a/src/main/java/hyundai/softeer/orange/event/common/service/EventService.java b/src/main/java/hyundai/softeer/orange/event/common/service/EventService.java index ec7a79fb..d17f58e8 100644 --- a/src/main/java/hyundai/softeer/orange/event/common/service/EventService.java +++ b/src/main/java/hyundai/softeer/orange/event/common/service/EventService.java @@ -12,11 +12,13 @@ import hyundai.softeer.orange.event.common.exception.EventException; import hyundai.softeer.orange.event.common.repository.EventFrameRepository; import hyundai.softeer.orange.event.common.repository.EventMetadataRepository; +import hyundai.softeer.orange.event.common.repository.EventSpecification; import hyundai.softeer.orange.event.component.EventKeyGenerator; import hyundai.softeer.orange.event.dto.BriefEventDto; import hyundai.softeer.orange.event.dto.EventDto; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Sort; import org.springframework.stereotype.Service; @@ -159,7 +161,9 @@ public List searchEvents(String search, String sortQuery, Integer sort ); - List events = emRepository.findAllBySearch(search, pageInfo); + var withSearch = EventSpecification.withSearch(search); + Page eventPage = emRepository.findAll(withSearch, pageInfo); + List events = eventPage.getContent(); return events.stream().map( it -> BriefEventDto.of( diff --git a/src/main/java/hyundai/softeer/orange/event/dto/BriefEventDto.java b/src/main/java/hyundai/softeer/orange/event/dto/BriefEventDto.java index 47ec276f..31fb3a89 100644 --- a/src/main/java/hyundai/softeer/orange/event/dto/BriefEventDto.java +++ b/src/main/java/hyundai/softeer/orange/event/dto/BriefEventDto.java @@ -1,13 +1,11 @@ package hyundai.softeer.orange.event.dto; import hyundai.softeer.orange.event.common.enums.EventType; -import hyundai.softeer.orange.event.dto.group.EventEditGroup; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; -import jakarta.validation.constraints.Size; +import lombok.Getter; import java.time.LocalDateTime; +@Getter public class BriefEventDto { /** * HD000000_000 형식으로 구성된 id 값 diff --git a/src/test/java/hyundai/softeer/orange/event/common/repository/EventSpecificationTest.java b/src/test/java/hyundai/softeer/orange/event/common/repository/EventSpecificationTest.java new file mode 100644 index 00000000..855b94ed --- /dev/null +++ b/src/test/java/hyundai/softeer/orange/event/common/repository/EventSpecificationTest.java @@ -0,0 +1,104 @@ +package hyundai.softeer.orange.event.common.repository; + +import hyundai.softeer.orange.event.common.entity.EventFrame; +import hyundai.softeer.orange.event.common.entity.EventMetadata; +import net.bytebuddy.description.annotation.AnnotationValue; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.test.context.TestPropertySource; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.*; +@DataJpaTest +@TestPropertySource(locations = "classpath:application-test.yml") +class EventSpecificationTest { + @Autowired + EventFrameRepository efRepository; + @Autowired + EventMetadataRepository emRepository; + + @DisplayName("search가 없으면 predicate는 항상 참") + @Test + void searchWithoutSearchClause() { + EventFrame ef = EventFrame.of("test"); + efRepository.save(ef); + EventMetadata em1 = EventMetadata.builder() + .eventId("HD240805_001") + .name("hyundai car event") + .eventFrame(ef) + .build(); + + EventMetadata em2 = EventMetadata.builder() + .eventId("HD240805_002") + .name("hello HD bye") + .eventFrame(ef) + .build(); + + EventMetadata em3 = EventMetadata.builder() + .eventId("HD240805_003") + .name("hyundai car event2") + .eventFrame(ef) + .build(); + emRepository.save(em1); + emRepository.save(em2); + emRepository.save(em3); + + Specification spec = EventSpecification.withSearch(null); + + Page result = emRepository.findAll(spec, PageRequest.of(0,100)); + assertThat(result.getTotalElements()).isEqualTo(3); + } + + @DisplayName("search가 있으면 필터링 수행") + @Test + void searchWithSearchClause() { + EventFrame ef = EventFrame.of("test"); + efRepository.save(ef); + // event 포함 ( name ) + EventMetadata em1 = EventMetadata.builder() + .eventId("HD240805_001") + .name("hyundai car event") + .eventFrame(ef) + .build(); + + // event 포함 ( eventId ) + EventMetadata em2 = EventMetadata.builder() + .eventId("event011") + .name("hello HD bye") + .eventFrame(ef) + .build(); + + // event 포함 ( name ) + EventMetadata em3 = EventMetadata.builder() + .eventId("HD240805_003") + .name("hyundai car event hello world") + .eventFrame(ef) + .build(); + + // event 포함 안됨 + EventMetadata em4 = EventMetadata.builder() + .eventId("HD240805_004") + .name("not included") + .eventFrame(ef) + .build(); + emRepository.save(em1); + emRepository.save(em2); + emRepository.save(em3); + + Specification spec = EventSpecification.withSearch("event"); + + Page result = emRepository.findAll(spec, PageRequest.of(0,100)); + assertThat(result.getTotalElements()).isEqualTo(3); + } +} \ No newline at end of file From f05429159b33b161f43459f4e896dbd70d0ce0bf Mon Sep 17 00:00:00 2001 From: blaxsior Date: Tue, 6 Aug 2024 15:56:02 +0900 Subject: [PATCH 4/8] =?UTF-8?q?[docs]=20event=20dto=20=EB=B0=8F=20?= =?UTF-8?q?=EC=BB=A8=ED=8A=B8=EB=A1=A4=EB=9F=AC=20=EC=A3=BC=EC=84=9D=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80(#32)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/controller/EventController.java | 8 ++++++-- .../event/common/service/EventService.java | 1 - .../orange/event/dto/BriefEventDto.java | 3 +++ .../softeer/orange/event/dto/EventDto.java | 6 +++--- .../orange/event/dto/draw/DrawEventDto.java | 6 ++++++ .../event/dto/draw/DrawEventMetadataDto.java | 15 +++++++++++++++ .../dto/draw/DrawEventScorePolicyDto.java | 12 ++++++++++++ .../orange/event/dto/fcfs/FcfsEventDto.java | 18 ++++++++++++++++++ 8 files changed, 63 insertions(+), 6 deletions(-) diff --git a/src/main/java/hyundai/softeer/orange/event/common/controller/EventController.java b/src/main/java/hyundai/softeer/orange/event/common/controller/EventController.java index cba8d1ae..4b9f0d34 100644 --- a/src/main/java/hyundai/softeer/orange/event/common/controller/EventController.java +++ b/src/main/java/hyundai/softeer/orange/event/common/controller/EventController.java @@ -31,8 +31,6 @@ @RestController public class EventController { private final EventService eventService; - - /** * * @param search 검색어 @@ -86,6 +84,9 @@ public ResponseEntity getEventEditData( return ResponseEntity.ok(eventInfo); } + /** + * @param eventDto 수정된 이벤트 정보 + */ @Auth({AuthRole.admin}) @PostMapping("/edit") @Operation(summary = "이벤트 수정", description = "관리자가 이벤트를 수정한다", responses = { @@ -98,6 +99,9 @@ public ResponseEntity editEvent( return ResponseEntity.ok().build(); } + /** + * @param req 이벤트 프레임 생성을 위한 json + */ @Auth({AuthRole.admin}) @PostMapping("/frame") @Operation(summary = "이벤트 프레임 생성", description = "관리자가 이벤트 프레임을 새롭게 등록한다", responses = { diff --git a/src/main/java/hyundai/softeer/orange/event/common/service/EventService.java b/src/main/java/hyundai/softeer/orange/event/common/service/EventService.java index d17f58e8..879ecf3d 100644 --- a/src/main/java/hyundai/softeer/orange/event/common/service/EventService.java +++ b/src/main/java/hyundai/softeer/orange/event/common/service/EventService.java @@ -113,7 +113,6 @@ public EventDto getEventInfo(String eventId) { if(mapper == null) throw new EventException(ErrorCode.INVALID_EVENT_TYPE); EventDto eventDto = EventDto.builder() - .id(metadata.getId()) .eventId(metadata.getEventId()) .name(metadata.getName()) .description(metadata.getDescription()) diff --git a/src/main/java/hyundai/softeer/orange/event/dto/BriefEventDto.java b/src/main/java/hyundai/softeer/orange/event/dto/BriefEventDto.java index 31fb3a89..1a4fc8d7 100644 --- a/src/main/java/hyundai/softeer/orange/event/dto/BriefEventDto.java +++ b/src/main/java/hyundai/softeer/orange/event/dto/BriefEventDto.java @@ -5,6 +5,9 @@ import java.time.LocalDateTime; +/** + * 이벤트 리스트를 위한 정보만 담고 있는 객체 + */ @Getter public class BriefEventDto { /** diff --git a/src/main/java/hyundai/softeer/orange/event/dto/EventDto.java b/src/main/java/hyundai/softeer/orange/event/dto/EventDto.java index 79d64d2c..3ac447bb 100644 --- a/src/main/java/hyundai/softeer/orange/event/dto/EventDto.java +++ b/src/main/java/hyundai/softeer/orange/event/dto/EventDto.java @@ -16,14 +16,14 @@ import java.time.LocalDateTime; import java.util.List; +/** + * 이벤트 정보를 포현하는 객체 + */ @Builder @NoArgsConstructor @AllArgsConstructor @Getter public class EventDto { - @NotNull(groups = {EventEditGroup.class}) - Long id; - /** * HD000000_000 형식으로 구성된 id 값 */ diff --git a/src/main/java/hyundai/softeer/orange/event/dto/draw/DrawEventDto.java b/src/main/java/hyundai/softeer/orange/event/dto/draw/DrawEventDto.java index 06229f45..0729262e 100644 --- a/src/main/java/hyundai/softeer/orange/event/dto/draw/DrawEventDto.java +++ b/src/main/java/hyundai/softeer/orange/event/dto/draw/DrawEventDto.java @@ -7,12 +7,18 @@ import java.util.List; +/** + * 추첨 이벤트를 나타내는 객체 + */ @Builder @NoArgsConstructor @AllArgsConstructor @Getter @Setter public class DrawEventDto { + /** + * 추첨 이벤트의 id 값. 서버 내부적으로 사용하는 데이터. + */ private Long id; @NotNull diff --git a/src/main/java/hyundai/softeer/orange/event/dto/draw/DrawEventMetadataDto.java b/src/main/java/hyundai/softeer/orange/event/dto/draw/DrawEventMetadataDto.java index f1fc73f0..aa64e9bd 100644 --- a/src/main/java/hyundai/softeer/orange/event/dto/draw/DrawEventMetadataDto.java +++ b/src/main/java/hyundai/softeer/orange/event/dto/draw/DrawEventMetadataDto.java @@ -4,19 +4,34 @@ import jakarta.validation.constraints.NotNull; import lombok.*; +/** + * 추첨 이벤트 등수에 따른 당첨 인원 및 상품 정보를 표현하는 객체 + */ @Builder @NoArgsConstructor @AllArgsConstructor @Getter public class DrawEventMetadataDto { + /** + * 추첨 이벤트 메타데이터에 대한 id 값. 서버 내부적으로 사용 + */ private Long id; + /** + * 추첨 이벤트 등수 + */ @NotNull private Long grade; + /** + * 현재 등수에 대한 최대 당첨 인원 수 + */ @NotNull private Long count; + /** + * 상품 정보를 기입하는 영역 + */ @NotNull private String prizeInfo; } diff --git a/src/main/java/hyundai/softeer/orange/event/dto/draw/DrawEventScorePolicyDto.java b/src/main/java/hyundai/softeer/orange/event/dto/draw/DrawEventScorePolicyDto.java index c800da96..5e175767 100644 --- a/src/main/java/hyundai/softeer/orange/event/dto/draw/DrawEventScorePolicyDto.java +++ b/src/main/java/hyundai/softeer/orange/event/dto/draw/DrawEventScorePolicyDto.java @@ -6,16 +6,28 @@ import jakarta.validation.constraints.NotNull; import lombok.*; +/** + * 추첨 이벤트에서 수행하는 행위에 대한 점수 정책을 정의하는 객체 + */ @Builder @NoArgsConstructor @AllArgsConstructor @Getter public class DrawEventScorePolicyDto { + /** + * 점수 정책의 id. 서버 내부적으로 사용하는 값 + */ private Long id; + /** + * 유저의 점수를 증가시키는 행동 + */ @NotNull private DrawEventAction action; + /** + * action 1회 수행 시 증가하는 점수 + */ @NotNull private Integer score; } diff --git a/src/main/java/hyundai/softeer/orange/event/dto/fcfs/FcfsEventDto.java b/src/main/java/hyundai/softeer/orange/event/dto/fcfs/FcfsEventDto.java index 33696cc6..aef05299 100644 --- a/src/main/java/hyundai/softeer/orange/event/dto/fcfs/FcfsEventDto.java +++ b/src/main/java/hyundai/softeer/orange/event/dto/fcfs/FcfsEventDto.java @@ -10,22 +10,40 @@ import java.time.LocalDateTime; +/** + * 선착순 이벤트를 표현하는 객체 + */ @Builder @RequiredArgsConstructor @AllArgsConstructor @Getter public class FcfsEventDto { + /** + * Fcfs 이벤트의 id. 서버 db 측에서 사용하기 위한 값 + */ private Long id; + /** + * 시작 시간 + */ @NotNull private LocalDateTime startTime; + /** + * 종료 시간 + */ @NotNull private LocalDateTime endTime; + /** + * 당첨 인원 + */ @NotNull private Long participantCount; + /** + * 상품 관련된 정보를 저장하는 영역 + */ @NotBlank private String prizeInfo; } \ No newline at end of file From 2a49e58e811b9b814f7b226baebfd6aa7a25bd4a Mon Sep 17 00:00:00 2001 From: blaxsior Date: Tue, 6 Aug 2024 15:56:51 +0900 Subject: [PATCH 5/8] =?UTF-8?q?[test]=20event=20spec=20search=EC=97=90=20?= =?UTF-8?q?=EB=8C=80=ED=95=9C=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80(#32)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../event/common/repository/EventSpecificationTest.java | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/test/java/hyundai/softeer/orange/event/common/repository/EventSpecificationTest.java b/src/test/java/hyundai/softeer/orange/event/common/repository/EventSpecificationTest.java index 855b94ed..f888a70d 100644 --- a/src/test/java/hyundai/softeer/orange/event/common/repository/EventSpecificationTest.java +++ b/src/test/java/hyundai/softeer/orange/event/common/repository/EventSpecificationTest.java @@ -2,24 +2,17 @@ import hyundai.softeer.orange.event.common.entity.EventFrame; import hyundai.softeer.orange.event.common.entity.EventMetadata; -import net.bytebuddy.description.annotation.AnnotationValue; -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Sort; import org.springframework.data.jpa.domain.Specification; import org.springframework.test.context.TestPropertySource; -import java.util.List; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.*; @DataJpaTest @TestPropertySource(locations = "classpath:application-test.yml") class EventSpecificationTest { From 9c567801a3d38d5f3ef8865c27d7c28e99b8517a Mon Sep 17 00:00:00 2001 From: blaxsior Date: Tue, 6 Aug 2024 16:17:21 +0900 Subject: [PATCH 6/8] =?UTF-8?q?[chore]=20=EC=9D=B4=EB=B2=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=A0=95=EB=B3=B4=EB=A5=BC=20events/edit=20=EB=8C=80=EC=8B=A0?= =?UTF-8?q?=20events/{eventId}=EB=A1=9C=20=EC=96=BB=EC=9D=84=20=EC=88=98?= =?UTF-8?q?=20=EC=9E=88=EA=B2=8C=20=EB=B3=80=EA=B2=BD(#32)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 현재 프로젝트는 이벤트 수정 - 이벤트 상세에서 사용하는 데이터가 거의 동일하므로, 굳이 상세 이벤트 / 수정 이벤트 객체로 구분할 필요가 없다. --- .../orange/event/common/controller/EventController.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/main/java/hyundai/softeer/orange/event/common/controller/EventController.java b/src/main/java/hyundai/softeer/orange/event/common/controller/EventController.java index 4b9f0d34..10bf0946 100644 --- a/src/main/java/hyundai/softeer/orange/event/common/controller/EventController.java +++ b/src/main/java/hyundai/softeer/orange/event/common/controller/EventController.java @@ -17,7 +17,6 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; -import org.springframework.web.ErrorResponse; import org.springframework.web.bind.annotation.*; import java.util.List; @@ -72,13 +71,13 @@ public ResponseEntity createEvent(@Validated @RequestBody EventDto eventDt * @return 해당 이벤트에 대한 정보 */ @Auth({AuthRole.admin}) - @GetMapping("/edit") + @GetMapping("{eventId}") @Operation(summary = "이벤트 데이터 획득", description = "이벤트 초기 정보를 받는다", responses = { @ApiResponse(responseCode = "200", description = "이벤트 정보를 정상적으로 받음"), @ApiResponse(responseCode = "404", description = "대응되는 이벤트가 존재하지 않음") }) - public ResponseEntity getEventEditData( - @RequestParam("eventId") String eventId + public ResponseEntity getEventData( + @PathVariable("eventId") String eventId ) { EventDto eventInfo = eventService.getEventInfo(eventId); return ResponseEntity.ok(eventInfo); From 3be53a7e9ec8b28ed551f82d4debe392a6d221f1 Mon Sep 17 00:00:00 2001 From: blaxsior Date: Tue, 6 Aug 2024 16:54:42 +0900 Subject: [PATCH 7/8] =?UTF-8?q?[fix]=20=EC=BF=BC=EB=A6=AC=20=ED=8C=8C?= =?UTF-8?q?=EC=84=9C=20=ED=8C=8C=EC=8B=B1=20=EB=A1=9C=EC=A7=81=EC=9D=B4=20?= =?UTF-8?q?=EB=B9=88=20=EB=AC=B8=EC=9E=90=EC=97=B4=EC=9D=84=20=EB=AC=B4?= =?UTF-8?q?=EC=8B=9C=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95=20(#3?= =?UTF-8?q?2)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../query/EventSearchQueryParser.java | 1 + .../query/EventSearchQueryParserTest.java | 27 +++++++++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 src/test/java/hyundai/softeer/orange/event/common/component/query/EventSearchQueryParserTest.java diff --git a/src/main/java/hyundai/softeer/orange/event/common/component/query/EventSearchQueryParser.java b/src/main/java/hyundai/softeer/orange/event/common/component/query/EventSearchQueryParser.java index 357e7dea..5a6642e5 100644 --- a/src/main/java/hyundai/softeer/orange/event/common/component/query/EventSearchQueryParser.java +++ b/src/main/java/hyundai/softeer/orange/event/common/component/query/EventSearchQueryParser.java @@ -12,6 +12,7 @@ public static Map parse(String searchQuery) { String[] queries = searchQuery.split(","); for (String query : queries) { + if (query.isBlank()) continue; String[] pair = query.split(":"); if(pair.length <= 0 || pair.length > 2) continue; // 값이 없거나 넘치면 무시. String key = pair[0].trim(); diff --git a/src/test/java/hyundai/softeer/orange/event/common/component/query/EventSearchQueryParserTest.java b/src/test/java/hyundai/softeer/orange/event/common/component/query/EventSearchQueryParserTest.java new file mode 100644 index 00000000..5a690ec9 --- /dev/null +++ b/src/test/java/hyundai/softeer/orange/event/common/component/query/EventSearchQueryParserTest.java @@ -0,0 +1,27 @@ +package hyundai.softeer.orange.event.common.component.query; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.*; + +class EventSearchQueryParserTest { + @DisplayName("query parser이 정상적으로 동작하는지 검사") + @Test + public void testParser() { + // 1. key:value,key:value,key 형식으로 표현됨 + // 2. :으로 구분되는 부분은 1개 또는 2개여야 함. + + String searchQuery = "test1:value1,, \t\n,hello,test2:value2,wrong:a:b"; + + Map parsed = EventSearchQueryParser.parse(searchQuery); + assertThat(parsed).hasSize(3); + assertThat(parsed.get("test1")).isEqualTo("value1"); + assertThat(parsed.get("test2")).isEqualTo("value2"); + assertThat(parsed.get("hello")).isEqualTo(""); + } +} \ No newline at end of file From 3219a5598b5c957eef52b45c127eadc9f583f866 Mon Sep 17 00:00:00 2001 From: blaxsior Date: Tue, 6 Aug 2024 18:18:44 +0900 Subject: [PATCH 8/8] =?UTF-8?q?[test]=20=EC=9D=B4=EB=B2=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20=EA=B8=B0=EB=8A=A5=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20(#32)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/EventServiceSearchEventsTest.java | 156 ++++++++++++++++++ .../common/service/EventServiceTest.java | 5 + 2 files changed, 161 insertions(+) create mode 100644 src/test/java/hyundai/softeer/orange/event/common/service/EventServiceSearchEventsTest.java diff --git a/src/test/java/hyundai/softeer/orange/event/common/service/EventServiceSearchEventsTest.java b/src/test/java/hyundai/softeer/orange/event/common/service/EventServiceSearchEventsTest.java new file mode 100644 index 00000000..8eb0cf47 --- /dev/null +++ b/src/test/java/hyundai/softeer/orange/event/common/service/EventServiceSearchEventsTest.java @@ -0,0 +1,156 @@ +package hyundai.softeer.orange.event.common.service; + +import hyundai.softeer.orange.event.common.component.eventFieldMapper.EventFieldMapperMatcher; +import hyundai.softeer.orange.event.common.entity.EventFrame; +import hyundai.softeer.orange.event.common.entity.EventMetadata; +import hyundai.softeer.orange.event.common.repository.EventFrameRepository; +import hyundai.softeer.orange.event.common.repository.EventMetadataRepository; +import hyundai.softeer.orange.event.component.EventKeyGenerator; +import hyundai.softeer.orange.event.dto.BriefEventDto; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.test.context.TestPropertySource; + +import java.time.LocalDateTime; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +@DataJpaTest +@TestPropertySource(locations = "classpath:application-test.yml") +public class EventServiceSearchEventsTest { + @Autowired + private EventMetadataRepository emRepo; + @Autowired + private EventFrameRepository efRepo; + + private EventFieldMapperMatcher mapperMatcher; + private EventKeyGenerator keyGenerator; + private EventService eventService; + + @BeforeEach + public void setUp() { + mapperMatcher = mock(EventFieldMapperMatcher.class); + keyGenerator = mock(EventKeyGenerator.class); + eventService = new EventService(efRepo, emRepo, mapperMatcher, keyGenerator); + EventFrame ef = EventFrame.of("test"); + efRepo.save(ef); + EventMetadata em1 = EventMetadata.builder() + .eventId("HD240805_001") + .name("hyundai car event") + .startTime(LocalDateTime.of(2024,8,1,15,0)) + .endTime(LocalDateTime.of(2024,8,2,15,0)) + .eventFrame(ef) + .build(); + + EventMetadata em2 = EventMetadata.builder() + .eventId("HD240805_002") + .name("hello bye") + .startTime(LocalDateTime.of(2024,8,1,15,0)) + .endTime(LocalDateTime.of(2024,8,2,17,0)) + .eventFrame(ef) + .build(); + + EventMetadata em3 = EventMetadata.builder() + .eventId("HD240805_003") + .name("hyundai car event") + .startTime(LocalDateTime.of(2024,8,1,18,0)) + .endTime(LocalDateTime.of(2024,8,1,20,0)) + .eventFrame(ef) + .build(); + + EventMetadata em4 = EventMetadata.builder() + .eventId("HD240805_004") + .name("25 always opened") + .startTime(LocalDateTime.of(2024,8,1,19,0)) + .endTime(LocalDateTime.of(2024,8,1,20,0)) + .eventFrame(ef) + .build(); + + EventMetadata em5 = EventMetadata.builder() + .eventId("HD240805_005") + .name("zebra car event") + .startTime(LocalDateTime.of(2024,8,1,21,0)) + .endTime(LocalDateTime.of(2024,8,1,22,0)) + .eventFrame(ef) + .build(); + + EventMetadata em6 = EventMetadata.builder() + .eventId("HD240805_006") + .name("25 always opened") + .startTime(LocalDateTime.of(2024,8,1,22,0)) + .endTime(LocalDateTime.of(2024,8,1,23,0)) + .eventFrame(ef) + .build(); + + emRepo.save(em1); + emRepo.save(em2); + emRepo.save(em3); + emRepo.save(em4); + emRepo.save(em5); + emRepo.save(em6); + } + + @DisplayName("search 없으면 모두 출력") + @Test + void searchEvents_findAllIfSearchIsNull() { + var list = eventService.searchEvents(null, null, null, null); + assertThat(list).hasSize(5); + } + + @DisplayName("search 있으면 매칭되는 값 출력") + @Test + void searchEvents_findMatchedIfSearchExists() { + var list = eventService.searchEvents("hyundai", null, null, null); + assertThat(list).hasSize(2); + } + + @DisplayName("search 있더라도 매칭되는 것 없으면 아무것도 반환 안함") + @Test + void searchEvents_findNothingIfSearchExistsButNotMatch() { + var list = eventService.searchEvents("not-exist", null, null, null); + assertThat(list).hasSize(0); + } + + @DisplayName("정렬 옵션 있으면 정렬된 형태로 반환") + @Test + void searchEvents_returnOrdered() { + String query = "startTime,endTime:desc,error"; + // startTime은 존재, 기본 값 asc + // endTime은 존재, desc + // error은 존재 X, 맞지 않는 값은 그냥 무시 + + var list = eventService.searchEvents(null, "startTime,endTime:desc,error", null, null); + BriefEventDto target = list.get(0); + + assertThat(target.getEventId()).isEqualTo("HD240805_002"); + } + + @DisplayName("페이지 옵션 있다면 해당 데이터 반환") + @Test + void searchEvents_returnPaged() { + String query = "startTime,endTime:desc,error"; + // startTime은 존재, 기본 값 asc + // endTime은 존재, desc + // error은 존재 X, 맞지 않는 값은 그냥 무시 + + var list = eventService.searchEvents(null, "eventId", 1, 2); + + assertThat(list.get(0).getEventId()).isEqualTo("HD240805_003"); + assertThat(list.get(1).getEventId()).isEqualTo("HD240805_004"); + } + + @DisplayName("여러 옵션 함께 사용도 가능") + @Test + void searchEvents_withMultipleOptions() { + + var list = eventService.searchEvents("25", "endTime:desc", 1, 1); + BriefEventDto target = list.get(0); + + assertThat(list).hasSize(1); + assertThat(target.getEventId()).isEqualTo("HD240805_004"); + } +} diff --git a/src/test/java/hyundai/softeer/orange/event/common/service/EventServiceTest.java b/src/test/java/hyundai/softeer/orange/event/common/service/EventServiceTest.java index 055e8cd4..42a4fad5 100644 --- a/src/test/java/hyundai/softeer/orange/event/common/service/EventServiceTest.java +++ b/src/test/java/hyundai/softeer/orange/event/common/service/EventServiceTest.java @@ -14,6 +14,8 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.test.context.TestPropertySource; import java.util.Optional; @@ -23,6 +25,9 @@ import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.*; + +@DataJpaTest +@TestPropertySource(locations = "classpath:application-test.yml") class EventServiceTest { private EventService eventService; private EventFrameRepository efRepo;