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

feat: 관련도 정렬을 제외한 리스트 검색 기능 구현 #99

Merged
merged 2 commits into from
Feb 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions src/main/java/com/listywave/common/util/StringUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.listywave.common.util;

import java.util.regex.Pattern;

public class StringUtils {

public static boolean match(String source, String keyword) {
return source.matches(".*" + Pattern.quote(keyword) + ".*");
}
}
28 changes: 28 additions & 0 deletions src/main/java/com/listywave/list/application/domain/Lists.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.listywave.list.application.domain;

import static com.listywave.common.util.StringUtils.match;

import com.listywave.list.application.dto.ListCreateCommand;
import com.listywave.list.application.vo.ItemComment;
import com.listywave.list.application.vo.ItemImageUrl;
Expand Down Expand Up @@ -163,6 +165,32 @@ public static Lists createList(
return lists;
}

public boolean isRelatedWith(String keyword) {
if (keyword.isBlank()) {
return true;
}
if (match(title.getValue(), keyword)) {
return true;
}
if (labels.stream().anyMatch(label -> match(label.getLabelName(), keyword))) {
return true;
}
if (items.stream().anyMatch(item -> match(item.getTitle(), keyword) || match(item.getComment(), keyword))) {
return true;
}
return false;
}

public boolean isIncluded(CategoryType category) {
if (category.equals(CategoryType.ENTIRE)) {
return true;
}
if (this.category.equals(category)) {
return true;
}
return false;
}

public void sortItems() {
this.getItems().sort(Comparator.comparing(Item::getRanking));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.listywave.list.application.domain;

public enum SortType {

NEW,
OLD,
RELATED,
COLLECTED,
;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package com.listywave.list.application.dto.response;

import com.listywave.list.application.domain.Item;
import com.listywave.list.application.domain.Lists;
import java.time.LocalDateTime;
import java.util.List;
import lombok.Builder;

@Builder
public record ListSearchResponse(
List<ListInfo> resultLists,
Long totalCount,
Long cursorId,
boolean hasNext
) {

public static ListSearchResponse of(java.util.List<Lists> lists, Long totalCount, Long cursorId, boolean hasNext) {
return ListSearchResponse.builder()
.resultLists(ListInfo.toList(lists))
.totalCount(totalCount)
.cursorId(cursorId)
.hasNext(hasNext)
.build();
}
}

@Builder
record ListInfo(
Long id,
String title,
java.util.List<ItemInfo> items,
boolean isPublic,
String backgroundColor,
LocalDateTime updatedDate,
Long ownerId,
String ownerNickname,
String ownerProfileImageUrl
) {

public static List<ListInfo> toList(java.util.List<Lists> lists) {
return lists.stream()
.map(ListInfo::of)
.toList();
}

private static ListInfo of(Lists lists) {
return ListInfo.builder()
.id(lists.getId())
.title(lists.getTitle())
.items(ItemInfo.toList(lists.getItems()))
.isPublic(lists.isPublic())
.backgroundColor(lists.getBackgroundColor())
.updatedDate(lists.getUpdatedDate())
.ownerId(lists.getUser().getId())
.ownerNickname(lists.getUser().getNickname())
.ownerProfileImageUrl(lists.getUser().getProfileImageUrl())
.build();
}
}

@Builder
record ItemInfo(
Long id,
String title
) {

public static java.util.List<ItemInfo> toList(java.util.List<Item> items) {
return items.stream()
.map(ItemInfo::of)
.toList();
}

private static ItemInfo of(Item item) {
return ItemInfo.builder()
.id(item.getId())
.title(item.getTitle())
.build();
}
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,25 @@
package com.listywave.list.application.service;

import static com.listywave.list.application.domain.SortType.COLLECTED;
import static com.listywave.list.application.domain.SortType.OLD;

import com.listywave.auth.application.domain.JwtManager;
import com.listywave.collaborator.application.domain.Collaborator;
import com.listywave.collaborator.repository.CollaboratorRepository;
import com.listywave.common.exception.CustomException;
import com.listywave.common.exception.ErrorCode;
import com.listywave.common.util.UserUtil;
import com.listywave.image.application.service.ImageService;
import com.listywave.list.application.domain.CategoryType;
import com.listywave.list.application.domain.Comment;
import com.listywave.list.application.domain.Item;
import com.listywave.list.application.domain.Lists;
import com.listywave.list.application.domain.SortType;
import com.listywave.list.application.dto.ListCreateCommand;
import com.listywave.list.application.dto.response.ListCreateResponse;
import com.listywave.list.application.dto.response.ListDetailResponse;
import com.listywave.list.application.dto.response.ListRecentResponse;
import com.listywave.list.application.dto.response.ListSearchResponse;
import com.listywave.list.application.dto.response.ListTrandingResponse;
import com.listywave.list.presentation.dto.request.ItemCreateRequest;
import com.listywave.list.repository.CommentRepository;
Expand Down Expand Up @@ -192,4 +198,52 @@ public ListRecentResponse getRecentLists(String accessToken) {
private boolean isSignedIn(String accessToken) {
return !accessToken.isBlank();
}

// TODO: 관련도 순 추가 (List 일급 컬렉션 만들어서 Scoring 하는 방식)
// TODO: 리팩터링
public ListSearchResponse search(String keyword, SortType sortType, CategoryType category, int size, Long cursorId) {
List<Lists> all = listRepository.findAll();

List<Lists> filtered = all.stream()
.filter(list -> list.isIncluded(category))
.filter(list -> list.isRelatedWith(keyword))
.sorted((list, other) -> {
if (sortType.equals(OLD)) {
return list.getUpdatedDate().compareTo(other.getUpdatedDate());
}
if (sortType.equals(COLLECTED)) {
return -(list.getCollectCount() - other.getCollectCount());
}
return -(list.getUpdatedDate().compareTo(other.getUpdatedDate()));
})
.toList();
Comment on lines +207 to +219
Copy link
Collaborator

Choose a reason for hiding this comment

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

해당 정렬은 query dsl 을 이용하여 동적 정렬을 이용하시는게 더 좋아보이네요
reference : https://velog.io/@seungho1216/Querydsl%EB%8F%99%EC%A0%81-sorting%EC%9D%84-%EC%9C%84%ED%95%9C-OrderSpecifier-%ED%81%B4%EB%9E%98%EC%8A%A4-%EA%B5%AC%ED%98%84


List<Lists> result;
if (cursorId == 0L) {
if (filtered.size() >= size) {
return ListSearchResponse.of(filtered.subList(0, size), (long) filtered.size(), filtered.get(size - 1).getId(), true);
}
return ListSearchResponse.of(filtered, (long) filtered.size(), filtered.get(filtered.size() - 1).getId(), false);
} else {
Lists cursorList = listRepository.getById(cursorId);

int cursorIndex = filtered.indexOf(cursorList);
int startIndex = cursorIndex + 1;
int endIndex = cursorIndex + 1 + size;

if (endIndex >= filtered.size()) {
endIndex = filtered.size() - 1;
}

result = filtered.subList(startIndex, endIndex + 1);
}

int totalCount = filtered.size();
if (result.size() < size) {
return ListSearchResponse.of(result, (long) totalCount, result.get(result.size() - 1).getId(), false);
}
boolean hasNext = result.size() > size;
result = result.subList(0, size);
return ListSearchResponse.of(result, (long) totalCount, result.get(result.size() - 1).getId(), hasNext);
Comment on lines +221 to +247
Copy link
Collaborator

Choose a reason for hiding this comment

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

해당 부분은 모든 List데이터를 한번에 가져와서 직접 페이징을 처리하신 걸로 보이는데 추후 무수한 데이터가 있을 경우 성능면에서 문제가 있을 거 같아 보이네요!

추후 query 부분에서 가져올때 처리하는게 더 좋아 보여요!

}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package com.listywave.list.presentation.controller;

import com.listywave.list.application.domain.CategoryType;
import com.listywave.list.application.domain.SortType;
import com.listywave.list.application.dto.ListCreateCommand;
import com.listywave.list.application.dto.response.ListCreateResponse;
import com.listywave.list.application.dto.response.ListDetailResponse;
import com.listywave.list.application.dto.response.ListRecentResponse;
import com.listywave.list.application.dto.response.ListSearchResponse;
import com.listywave.list.application.dto.response.ListTrandingResponse;
import com.listywave.list.application.service.ListService;
import com.listywave.list.presentation.dto.request.ListCreateRequest;
Expand All @@ -18,6 +21,7 @@
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
Expand Down Expand Up @@ -67,4 +71,16 @@ ResponseEntity<ListRecentResponse> getRecentLists(
ListRecentResponse recentLists = listService.getRecentLists(accessToken);
return ResponseEntity.ok(recentLists);
}

@GetMapping("/search")
ResponseEntity<ListSearchResponse> search(
@RequestParam(value = "keyword", defaultValue = "") String keyword,
@RequestParam(value = "sort", defaultValue = "new") SortType sort,
@RequestParam(value = "category", defaultValue = "entire") CategoryType category,
@RequestParam(value = "size", defaultValue = "5") int size,
@RequestParam(value = "cursorId", defaultValue = "0") Long cursorId
) {
ListSearchResponse response = listService.search(keyword, sort, category, size, cursorId);
return ResponseEntity.ok(response);
}
}
Loading