Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/#324 행사 검색 기능 구현 #686

Merged
merged 10 commits into from
Oct 9, 2023
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,9 @@ void findEvents() throws Exception {
RequestDocumentation.parameterWithName("tags").description("필터링하려는 태그(option)").optional(),
RequestDocumentation.parameterWithName("statuses")
.description("필터링하려는 상태(UPCOMING, IN_PROGRESS, ENDED)(option)")
.optional(),
RequestDocumentation.parameterWithName("keyword")
.description("검색하려는 키워드")
.optional()
);

Expand All @@ -144,18 +147,6 @@ void findEvents() throws Exception {
);

final List<EventResponse> eventResponses = List.of(
new EventResponse(
1L,
"인프콘 2023",
LocalDateTime.parse("2023-06-03T12:00:00"),
LocalDateTime.parse("2023-09-03T12:00:00"),
LocalDateTime.parse("2023-09-01T00:00:00"),
LocalDateTime.parse("2023-09-02T23:59:59"),
List.of("백엔드", "프론트엔드", "안드로이드", "IOS", "AI"),
"https://biz.pusan.ac.kr/dext5editordata/2022/08/20220810_160546511_10103.jpg",
EventMode.ONLINE.getValue(),
PaymentType.PAID.getValue()
),
new EventResponse(
5L,
"웹 컨퍼런스",
Expand All @@ -182,14 +173,15 @@ void findEvents() throws Exception {
Mockito.when(eventService.findEvents(any(EventType.class),
any(LocalDate.class), eq("2023-07-01"),
eq("2023-07-31"),
eq(null), any())).thenReturn(eventResponses);
eq(null), any(), eq("컨퍼"))).thenReturn(eventResponses);

// when & then
mockMvc.perform(get("/events")
.param("category", "CONFERENCE")
.param("start_date", "2023-07-01")
.param("end_date", "2023-07-31")
.param("statuses", "UPCOMING,IN_PROGRESS")
.param("keyword", "컨퍼")
)
.andExpect(status().isOk())
.andDo(MockMvcRestDocumentation.document("find-events", requestParameters, responseFields));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,11 @@ public ResponseEntity<List<EventResponse>> findEvents(
@RequestParam(name = "start_date", required = false) final String startDate,
@RequestParam(name = "end_date", required = false) final String endDate,
@RequestParam(required = false) final List<String> tags,
@RequestParam(required = false) final List<EventStatus> statuses) {
@RequestParam(required = false) final List<EventStatus> statuses,
@RequestParam(required = false) final String keyword) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

나중에 필터링 관련 파라미터끼리 하나의 DTO로 묶어도 좋을 것 같네요.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

말씀을 듣고 고민해봤는데 DTO 객체로 매핑시켰을 때 required=false 설정을 어떻게 적용시킬 수 있을지 방법이 떠오르지 않아서, 추후 EventService 리팩토일 작업을 할 때 고민해봐야할 것 같아요.
좋은 의견 감사합니다~!

return ResponseEntity.ok(
eventService.findEvents(category, LocalDate.now(), startDate, endDate, tags, statuses));
eventService.findEvents(category, LocalDate.now(), startDate, endDate, tags, statuses,
keyword));
}

@PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.emmsale.event.application;

import static com.emmsale.event.domain.repository.EventSpecification.filterByCategory;
import static com.emmsale.event.domain.repository.EventSpecification.filterByNameContainsSearchKeywords;
import static com.emmsale.event.domain.repository.EventSpecification.filterByPeriod;
import static com.emmsale.event.domain.repository.EventSpecification.filterByTags;
import static com.emmsale.event.exception.EventExceptionType.NOT_FOUND_EVENT;
import static com.emmsale.tag.exception.TagExceptionType.NOT_FOUND_TAG;
Expand All @@ -15,7 +17,6 @@
import com.emmsale.event.domain.EventStatus;
import com.emmsale.event.domain.EventType;
import com.emmsale.event.domain.repository.EventRepository;
import com.emmsale.event.domain.repository.EventSpecification;
import com.emmsale.event.domain.repository.EventTagRepository;
import com.emmsale.event.exception.EventException;
import com.emmsale.event.exception.EventExceptionType;
Expand Down Expand Up @@ -92,20 +93,13 @@ private List<String> extractInformationImages(final List<String> imageUrls) {
@Transactional(readOnly = true)
public List<EventResponse> findEvents(final EventType category,
final LocalDate nowDate, final String startDate, final String endDate,
final List<String> tagNames, final List<EventStatus> statuses) {
final List<String> tagNames, final List<EventStatus> statuses, final String keyword) {
Specification<Event> spec = Specification.where(filterByCategory(category));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Specification을 잘 활용해주신 것 같아요.

specification을 활용하는 코드가 많은데 별도의 클래스로 분리해보는 건 어떨까요?
(이건 단순 의견입니다. 반영 안 해주셔도 괜찮을 것 같아요)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

추후 EventService를 리팩토링할 때 고민해보면 좋을 것 같네요!
querydsl로 변경해주어야하기도 하구요


if (isExistTagNames(tagNames)) {
validateTags(tagNames);
spec = spec.and(filterByTags(tagNames));
}
spec = filterByTagIfExist(tagNames, spec);
spec = filterByDateIfExist(startDate, endDate, spec);
spec = filterByKeywordIfExist(keyword, spec);

if (isExistFilterDate(startDate, endDate)) {
final LocalDateTime startDateTime = validateStartDate(startDate);
final LocalDateTime endDateTime = validateEndDate(endDate);
validateEndDateAfterDateStart(startDateTime, endDateTime);
spec = spec.and(EventSpecification.filterByPeriod(startDateTime, endDateTime));
}
final List<Event> events = eventRepository.findAll(spec);

final EnumMap<EventStatus, List<Event>> eventsForEventStatus
Expand All @@ -114,6 +108,15 @@ public List<EventResponse> findEvents(final EventType category,
return filterByStatuses(statuses, eventsForEventStatus, makeImageUrlPerEventId(events));
}

private Specification<Event> filterByTagIfExist(final List<String> tagNames,
Specification<Event> spec) {
if (isExistTagNames(tagNames)) {
validateTags(tagNames);
spec = spec.and(filterByTags(tagNames));
}
return spec;
}

private boolean isExistTagNames(final List<String> tagNames) {
return tagNames != null;
}
Expand All @@ -125,6 +128,17 @@ private void validateTags(final List<String> tagNames) {
}
}

private Specification<Event> filterByDateIfExist(final String startDate, final String endDate,
Specification<Event> spec) {
if (isExistFilterDate(startDate, endDate)) {
final LocalDateTime startDateTime = validateStartDate(startDate);
final LocalDateTime endDateTime = validateEndDate(endDate);
validateEndDateAfterDateStart(startDateTime, endDateTime);
spec = spec.and(filterByPeriod(startDateTime, endDateTime));
}
return spec;
}

private boolean isExistFilterDate(final String startDate, final String endDate) {
return startDate != null || endDate != null;
}
Expand Down Expand Up @@ -171,6 +185,19 @@ private Map<Long, String> makeImageUrlPerEventId(final List<Event> events) {
return imageUrlPerEventId;
}

private Specification<Event> filterByKeywordIfExist(final String keyword,
Specification<Event> spec) {
if (isExistKeyword(keyword)) {
final String[] keywords = keyword.trim().split(" ");
spec = spec.and(filterByNameContainsSearchKeywords(keywords));
}
return spec;
}

private boolean isExistKeyword(final String keyword) {
return keyword != null && !keyword.isBlank();
}

private EnumMap<EventStatus, List<Event>> groupByEventStatus(final LocalDate nowDate,
final List<Event> events) {
return events.stream()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import java.util.List;
import javax.persistence.criteria.Join;
import javax.persistence.criteria.JoinType;
import javax.persistence.criteria.Predicate;
import org.springframework.data.jpa.domain.Specification;

public class EventSpecification {
Expand Down Expand Up @@ -38,4 +39,16 @@ public static Specification<Event> filterByPeriod(final LocalDateTime startDate,
startDate))
);
}

public static Specification<Event> filterByNameContainsSearchKeywords(final String[] keywords) {
return (root, query, criteriaBuilder) -> {
Predicate[] predicates = new Predicate[keywords.length];

for (int i = 0; i < keywords.length; i++) {
predicates[i] = criteriaBuilder.like(root.get("name"), "%" + keywords[i] + "%");
}

return criteriaBuilder.and(predicates);
};
}
}
Loading
Loading