Skip to content
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
25 changes: 25 additions & 0 deletions src/main/java/goorm/ddok/member/repository/UserRepository.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package goorm.ddok.member.repository;

import goorm.ddok.member.domain.UserLocation;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import goorm.ddok.member.domain.User;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
Expand Down Expand Up @@ -202,4 +204,27 @@ interface UserOverlayRow {
AND u.id = :id
""", nativeQuery = true)
Optional<UserOverlayRow> findOverlayById(@Param("id") Long id);

@Query("""
SELECT DISTINCT u FROM User u
LEFT JOIN FETCH u.location loc
LEFT JOIN u.positions pos
WHERE (
:keyword IS NULL OR :keyword = '' OR
LOWER(u.nickname) LIKE LOWER(CONCAT('%', :keyword, '%')) OR
(LOWER(pos.positionName) LIKE LOWER(CONCAT('%', :keyword, '%'))) OR
(LOWER(COALESCE(loc.region1DepthName, '')) LIKE LOWER(CONCAT('%', :keyword, '%'))) OR
(LOWER(COALESCE(loc.region2DepthName, '')) LIKE LOWER(CONCAT('%', :keyword, '%'))) OR
(LOWER(COALESCE(loc.region3DepthName, '')) LIKE LOWER(CONCAT('%', :keyword, '%'))) OR
(LOWER(COALESCE(loc.roadName, '')) LIKE LOWER(CONCAT('%', :keyword, '%'))) OR
(LOWER(COALESCE(loc.mainBuildingNo, '')) LIKE LOWER(CONCAT('%', :keyword, '%'))) OR
(LOWER(COALESCE(loc.subBuildingNo, '')) LIKE LOWER(CONCAT('%', :keyword, '%'))) OR
(LOWER(CONCAT(COALESCE(loc.region1DepthName, ''), ' ', COALESCE(loc.region2DepthName, ''))) LIKE LOWER(CONCAT('%', :keyword, '%')))
)
AND (
(:keyword IS NOT NULL AND :keyword != '')
)
ORDER BY LOWER(u.nickname) ASC, u.id ASC
""")
Page<User> searchPlayersWithKeyword(@Param("keyword") String keyword, Pageable pageable);
}
104 changes: 7 additions & 97 deletions src/main/java/goorm/ddok/player/service/ProfileSearchService.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import goorm.ddok.global.dto.AbandonBadgeDto;
import goorm.ddok.global.dto.BadgeDto;
import goorm.ddok.member.domain.User;
import goorm.ddok.member.domain.UserLocation;
import goorm.ddok.member.domain.UserPosition;
import goorm.ddok.member.domain.UserPositionType;
import goorm.ddok.member.repository.UserRepository;
Expand All @@ -18,7 +17,6 @@
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

Expand All @@ -43,121 +41,35 @@ public Page<ProfileSearchResponse> searchPlayers(String keyword, int page, int s
page = Math.max(page, 0);
size = (size <= 0) ? 10 : size;

// Sort 제거하고 Pageable만 생성
Pageable pageable = PageRequest.of(page, size);

Specification<User> spec = hasText(keyword)
? keywordSpecWithSort(keyword)
: publicUsersWithSort();
String searchKeyword = hasText(keyword) ? keyword.trim() : null;

Page<User> rows = userRepository.findAll(spec, pageable);
Page<User> rows = userRepository.searchPlayersWithKeyword(searchKeyword, pageable);
return rows.map(u -> toResponse(u, currentUserId));
}

private Specification<User> keywordSpecWithSort(String raw) {
List<String> tokens = splitTokens(raw);

return (root, query, cb) -> {
// 기존 검색 로직
Join<User, UserLocation> locJoin = root.join("location", JoinType.LEFT);

List<Predicate> andPerToken = new ArrayList<>();
for (String token : tokens) {
String like = "%" + token.toLowerCase() + "%";
List<Predicate> ors = new ArrayList<>();

ors.add(cb.like(cb.lower(root.get("nickname")), like));

Subquery<Long> posSub = Objects.requireNonNull(query).subquery(Long.class);
Root<UserPosition> p = posSub.from(UserPosition.class);
posSub.select(cb.literal(1L));
posSub.where(
cb.equal(p.get("user").get("id"), root.get("id")),
cb.like(cb.lower(p.get("positionName")), like)
);
ors.add(cb.and(cb.isTrue(root.get("isPublic")), cb.exists(posSub)));

ors.add(cb.and(cb.isTrue(root.get("isPublic")),
cb.like(cb.lower(cb.coalesce(locJoin.get("region1DepthName"), "")), like)));
ors.add(cb.and(cb.isTrue(root.get("isPublic")),
cb.like(cb.lower(cb.coalesce(locJoin.get("region2DepthName"), "")), like)));
ors.add(cb.and(cb.isTrue(root.get("isPublic")),
cb.like(cb.lower(cb.coalesce(locJoin.get("region3DepthName"), "")), like)));
ors.add(cb.and(cb.isTrue(root.get("isPublic")),
cb.like(cb.lower(cb.coalesce(locJoin.get("roadName"), "")), like)));
ors.add(cb.and(cb.isTrue(root.get("isPublic")),
cb.like(cb.lower(cb.coalesce(locJoin.get("mainBuildingNo"), "")), like)));
ors.add(cb.and(cb.isTrue(root.get("isPublic")),
cb.like(cb.lower(cb.coalesce(locJoin.get("subBuildingNo"), "")), like)));

ors.add(cb.and(cb.isTrue(root.get("isPublic")),
cb.like(
cb.lower(
cb.concat(
cb.concat(cb.coalesce(locJoin.get("region1DepthName"), ""), " "),
cb.coalesce(locJoin.get("region2DepthName"), "")
)
),
like
)));

andPerToken.add(cb.or(ors.toArray(new Predicate[0])));
}

// 정렬 추가
if (query != null) {
query.orderBy(
cb.asc(root.get("nickname")),
cb.asc(root.get("id"))
);
}

return cb.and(andPerToken.toArray(new Predicate[0]));
};
}

private Specification<User> publicUsersWithSort() {
return (root, query, cb) -> {
// 정렬 추가
if (query != null) {
query.orderBy(
cb.asc(root.get("nickname")),
cb.asc(root.get("id"))
);
}
return cb.isTrue(root.get("isPublic"));
};
}

private List<String> splitTokens(String raw) {
return Arrays.stream(raw.split("[,\\s]+"))
.map(String::trim)
.filter(t -> !t.isEmpty())
.distinct()
.toList();
}

// 나머지 메서드들은 그대로 유지
private ProfileSearchResponse toResponse(User u, Long currentUserId) {

// 기존 코드 그대로
boolean isMine = Objects.equals(u.getId(), currentUserId);
boolean isPublic = u.isPublic();

String address = null;
String mainPosition = null;

// isPublic이 true이거나 본인인 경우에만 주소와 포지션 정보 제공
if (isPublic || isMine) {
address = shortAddress(
u.getLocation() == null ? null : u.getLocation().getRegion1DepthName(),
u.getLocation() == null ? null : u.getLocation().getRegion2DepthName()
);
mainPosition = pickMainPosition(u);
}

BigDecimal temp = userReputationRepository.findByUserId(u.getId())
.map(UserReputation::getTemperature)
.orElse(null);

// 대표 배지 조회
BadgeDto representative = badgeService.getRepresentativeGoodBadge(u);
ProfileSearchResponse.MainBadge mainBadge = null;
if (representative != null) {
Expand All @@ -167,7 +79,6 @@ private ProfileSearchResponse toResponse(User u, Long currentUserId) {
.build();
}

// 나쁜 배지 조회
AbandonBadgeDto abandon = badgeService.getAbandonBadge(u);
ProfileSearchResponse.AbandonBadge abandonBadge =
ProfileSearchResponse.AbandonBadge.builder()
Expand All @@ -183,16 +94,15 @@ private ProfileSearchResponse toResponse(User u, Long currentUserId) {
|| dmRequestService.isDmPendingOrAcceptedOrChatExists(currentUserId, u.getId());
}


return ProfileSearchResponse.builder()
.userId(u.getId())
.category("players")
.nickname(u.getNickname())
.profileImageUrl(u.getProfileImageUrl())
.mainBadge(mainBadge)
.abandonBadge(abandonBadge)
.mainPosition(mainPosition) // isPublic이 false이면 null
.address(address) // isPublic이 false이면 null
.mainPosition(mainPosition)
.address(address)
.temperature(temp)
.IsMine(currentUserId != null && currentUserId.equals(u.getId()))
.chatRoomId(chatRoomId)
Expand Down