diff --git a/src/main/java/com/dnd/accompany/domain/accompany/api/AccompanyBoardController.java b/src/main/java/com/dnd/accompany/domain/accompany/api/AccompanyBoardController.java index 4a41ce6..ba62661 100644 --- a/src/main/java/com/dnd/accompany/domain/accompany/api/AccompanyBoardController.java +++ b/src/main/java/com/dnd/accompany/domain/accompany/api/AccompanyBoardController.java @@ -50,6 +50,14 @@ public ResponseEntity create( return ResponseEntity.ok(accompanyServiceFacade.createBoard(user.getId(), request)); } + @Operation(summary = "동행글 검색") + @PostMapping("/search") + public ResponseEntity> search( + @RequestBody @Valid PageRequest request, + @RequestParam(value = "keyword") String keyword) { + return ResponseEntity.ok(accompanyBoardService.getMatchedBoards(request, keyword)); + } + @Operation(summary = "동행글 목록 조회") @PostMapping("/all") public ResponseEntity> readAll( diff --git a/src/main/java/com/dnd/accompany/domain/accompany/entity/enums/Region.java b/src/main/java/com/dnd/accompany/domain/accompany/entity/enums/Region.java index 0125de8..2defc28 100644 --- a/src/main/java/com/dnd/accompany/domain/accompany/entity/enums/Region.java +++ b/src/main/java/com/dnd/accompany/domain/accompany/entity/enums/Region.java @@ -15,4 +15,17 @@ public enum Region { Region(String description) { this.description = description; } + + public String description() { + return description; + } + + public static Region from(String keyword) { + for (Region region : Region.values()) { + if (region.description().contains(keyword)) { + return region; + } + } + return null; + } } diff --git a/src/main/java/com/dnd/accompany/domain/accompany/infrastructure/querydsl/AccompanyBoardRepositoryImpl.java b/src/main/java/com/dnd/accompany/domain/accompany/infrastructure/querydsl/AccompanyBoardRepositoryImpl.java index c7ce972..cb0564e 100644 --- a/src/main/java/com/dnd/accompany/domain/accompany/infrastructure/querydsl/AccompanyBoardRepositoryImpl.java +++ b/src/main/java/com/dnd/accompany/domain/accompany/infrastructure/querydsl/AccompanyBoardRepositoryImpl.java @@ -6,6 +6,7 @@ import static com.dnd.accompany.domain.accompany.entity.QAccompanyImage.*; import static com.dnd.accompany.domain.accompany.entity.QAccompanyRequest.*; import static com.dnd.accompany.domain.accompany.entity.QAccompanyUser.*; +import static com.dnd.accompany.domain.accompany.entity.enums.Region.*; import static com.dnd.accompany.domain.accompany.entity.enums.Role.*; import static com.dnd.accompany.domain.user.entity.QUser.*; @@ -25,6 +26,7 @@ import com.querydsl.core.types.Projections; import com.querydsl.core.types.dsl.BooleanExpression; import com.querydsl.core.types.dsl.Expressions; +import com.querydsl.jpa.JPAExpressions; import com.querydsl.jpa.impl.JPAQueryFactory; import lombok.RequiredArgsConstructor; @@ -35,6 +37,39 @@ public class AccompanyBoardRepositoryImpl implements AccompanyBoardRepositoryCus private final JPAQueryFactory queryFactory; + @Override + public Slice findBoardThumbnailsByKeyword(String cursor, int size, String keyword) { + List content = queryFactory + .select(Projections.constructor(FindBoardThumbnailsResult.class, + accompanyBoard.id, + accompanyBoard.title, + accompanyBoard.region, + accompanyBoard.startDate, + accompanyBoard.endDate, + user.nickname, + Expressions.stringTemplate("GROUP_CONCAT(DISTINCT {0})", accompanyImage.imageUrl), + Expressions.stringTemplate( + "CONCAT(DATE_FORMAT({0}, '%Y%m%d%H%i%S'), LPAD(CAST({1} AS STRING), 6, '0'))", + accompanyBoard.updatedAt, + accompanyBoard.id + )) + ) + .from(accompanyUser) + .join(accompanyUser.accompanyBoard, accompanyBoard) + .join(accompanyUser.user, user) + .leftJoin(accompanyImage).on(accompanyImage.accompanyBoard.id.eq(accompanyBoard.id)) + .where(isHost()) + .where(isContains(keyword)) + .where(cursorCondition(cursor, accompanyBoard.updatedAt, accompanyBoard.id)) + .groupBy(accompanyBoard.id, accompanyBoard.title, accompanyBoard.region, + accompanyBoard.startDate, accompanyBoard.endDate, user.nickname) + .orderBy(accompanyBoard.updatedAt.desc(), accompanyBoard.id.desc()) + .limit(size + 1) + .fetch(); + + return createSlice(size, content); + } + @Override public Slice findBoardThumbnails(String cursor, int size, Region region) { List content = queryFactory @@ -161,4 +196,26 @@ private BooleanBuilder isRegion(Region region) { return clause; } + + public BooleanExpression isRegionKeyword(String keyword) { + if(from(keyword) == null) + return accompanyBoard.region.isNull(); + + return accompanyBoard.region.eq(from(keyword)); + } + + private BooleanBuilder isContains(String keyword) { + BooleanBuilder booleanBuilder = new BooleanBuilder(); + booleanBuilder.or(isRegionKeyword(keyword)); + booleanBuilder.or(accompanyBoard.title.contains(keyword)); + booleanBuilder.or( + JPAExpressions.selectOne() + .from(accompanyTag) + .where(accompanyTag.accompanyBoard.id.eq(accompanyBoard.id) + .and(accompanyTag.name.contains(keyword))) + .exists() + ); + + return booleanBuilder; + } } diff --git a/src/main/java/com/dnd/accompany/domain/accompany/infrastructure/querydsl/interfaces/AccompanyBoardRepositoryCustom.java b/src/main/java/com/dnd/accompany/domain/accompany/infrastructure/querydsl/interfaces/AccompanyBoardRepositoryCustom.java index 440c13b..cd547be 100644 --- a/src/main/java/com/dnd/accompany/domain/accompany/infrastructure/querydsl/interfaces/AccompanyBoardRepositoryCustom.java +++ b/src/main/java/com/dnd/accompany/domain/accompany/infrastructure/querydsl/interfaces/AccompanyBoardRepositoryCustom.java @@ -6,11 +6,14 @@ import com.dnd.accompany.domain.accompany.entity.enums.Region; public interface AccompanyBoardRepositoryCustom { + Slice findBoardThumbnails(String cursor, int size, Region region); Slice findRecordsByUserId(String cursor, int size, Long userId); Slice findBoardThumbnailsByHostId(String cursor, int size, Long userId); + + Slice findBoardThumbnailsByKeyword(String cursor, int size, String keyword); boolean isHostOfBoard(Long userId, Long boardId); } diff --git a/src/main/java/com/dnd/accompany/domain/accompany/service/AccompanyBoardService.java b/src/main/java/com/dnd/accompany/domain/accompany/service/AccompanyBoardService.java index d6676a1..ffb1701 100644 --- a/src/main/java/com/dnd/accompany/domain/accompany/service/AccompanyBoardService.java +++ b/src/main/java/com/dnd/accompany/domain/accompany/service/AccompanyBoardService.java @@ -1,6 +1,7 @@ package com.dnd.accompany.domain.accompany.service; import static com.dnd.accompany.domain.accompany.api.dto.FindBoardThumbnailsResult.*; +import static com.dnd.accompany.domain.accompany.entity.AccompanyBoard.*; import java.util.List; @@ -56,6 +57,14 @@ public PageResponse getMyBoards(PageRequest request, Lo return new PageResponse<>(sliceResult.hasNext(), thumbnails, getLastCursor(sliceResult.getContent())); } + public PageResponse getMatchedBoards(PageRequest request, String keyword) { + Slice sliceResult = accompanyBoardRepository.findBoardThumbnailsByKeyword(request.cursor(), request.size(), keyword); + + List thumbnails = getBoardThumbnails(sliceResult.getContent()); + + return new PageResponse<>(sliceResult.hasNext(), thumbnails, getLastCursor(sliceResult.getContent())); + } + @Transactional(readOnly = true) public PageResponse getAllBoards(PageRequest request, Region region) { Slice sliceResult = accompanyBoardRepository.findBoardThumbnails(request.cursor(), request.size(), region); diff --git a/src/main/java/com/dnd/accompany/domain/review/api/ReviewController.java b/src/main/java/com/dnd/accompany/domain/review/api/ReviewController.java index 367abaa..0ef4d66 100644 --- a/src/main/java/com/dnd/accompany/domain/review/api/ReviewController.java +++ b/src/main/java/com/dnd/accompany/domain/review/api/ReviewController.java @@ -1,6 +1,7 @@ package com.dnd.accompany.domain.review.api; import com.dnd.accompany.domain.auth.dto.jwt.JwtAuthentication; +import com.dnd.accompany.domain.review.api.dto.AllEvaluationResponses; import com.dnd.accompany.domain.review.api.dto.CreateReviewRequest; import com.dnd.accompany.domain.review.api.dto.ReviewDetailsResponse; import com.dnd.accompany.domain.review.api.dto.ReviewDetailsResult; @@ -63,9 +64,18 @@ public ResponseEntity getReviewList( @Operation(summary = "받은 동행 평가 조회") @GetMapping("/evaluations") public ResponseEntity getEvaluation( - @AuthenticationPrincipal JwtAuthentication user +// @AuthenticationPrincipal JwtAuthentication user + ) { + SimpleEvaluationResponse response = reviewService.getSimpleEvaluation(3L); + return ResponseEntity.ok(response); + } + + @Operation(summary = "받은 모든 동행 평가 조회") + @GetMapping("/evaluations/all") + public ResponseEntity getEvaluations( +// @AuthenticationPrincipal JwtAuthentication user ) { - SimpleEvaluationResponse response = reviewService.getEvaluation(user.getId()); + AllEvaluationResponses response = reviewService.getEvaluation(3L); return ResponseEntity.ok(response); } } diff --git a/src/main/java/com/dnd/accompany/domain/review/api/dto/AllEvaluationResponses.java b/src/main/java/com/dnd/accompany/domain/review/api/dto/AllEvaluationResponses.java new file mode 100644 index 0000000..c2d010a --- /dev/null +++ b/src/main/java/com/dnd/accompany/domain/review/api/dto/AllEvaluationResponses.java @@ -0,0 +1,12 @@ +package com.dnd.accompany.domain.review.api.dto; + +import lombok.Builder; + +import java.util.List; + +@Builder +public record AllEvaluationResponses ( + List evaluationResponse, + int evaluationCount +){ +} diff --git a/src/main/java/com/dnd/accompany/domain/review/api/dto/EvaluationResponse.java b/src/main/java/com/dnd/accompany/domain/review/api/dto/EvaluationResponse.java new file mode 100644 index 0000000..1cb947d --- /dev/null +++ b/src/main/java/com/dnd/accompany/domain/review/api/dto/EvaluationResponse.java @@ -0,0 +1,31 @@ +package com.dnd.accompany.domain.review.api.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; + +@AllArgsConstructor +@Builder +public class EvaluationResponse { + @JsonProperty("type") + private String type; + + @JsonProperty("count") + private long count; + + public static EvaluationResponse create(TypeCountResult topResult) { + if (topResult != null) { + return EvaluationResponse.builder() + .type(topResult.getType() != null ? topResult.getType() : null) + .count(topResult.getCount()) + .build(); + } else { + return EvaluationResponse.builder() + .type(null) + .count(0) + .build(); + } + } +} + + diff --git a/src/main/java/com/dnd/accompany/domain/review/api/dto/SimpleEvaluationResponse.java b/src/main/java/com/dnd/accompany/domain/review/api/dto/SimpleEvaluationResponse.java index e1bc9c9..f343852 100644 --- a/src/main/java/com/dnd/accompany/domain/review/api/dto/SimpleEvaluationResponse.java +++ b/src/main/java/com/dnd/accompany/domain/review/api/dto/SimpleEvaluationResponse.java @@ -1,15 +1,12 @@ package com.dnd.accompany.domain.review.api.dto; -import com.dnd.accompany.domain.review.entity.enums.PersonalityType; -import com.dnd.accompany.domain.review.entity.enums.TravelPreferenceType; -import com.dnd.accompany.domain.review.entity.enums.TravelStyleType; import lombok.Builder; +import java.util.List; + @Builder public record SimpleEvaluationResponse( - TravelStyleType travelStyle, - TravelPreferenceType travelPreference, - PersonalityType personalityType, + List evaluationResponse, int evaluationCount ) { } diff --git a/src/main/java/com/dnd/accompany/domain/review/api/dto/SimpleEvaluationResult.java b/src/main/java/com/dnd/accompany/domain/review/api/dto/SimpleEvaluationResult.java index 249871c..113ba14 100644 --- a/src/main/java/com/dnd/accompany/domain/review/api/dto/SimpleEvaluationResult.java +++ b/src/main/java/com/dnd/accompany/domain/review/api/dto/SimpleEvaluationResult.java @@ -1,17 +1,16 @@ package com.dnd.accompany.domain.review.api.dto; -import com.dnd.accompany.domain.review.entity.enums.PersonalityType; -import com.dnd.accompany.domain.review.entity.enums.TravelPreferenceType; -import com.dnd.accompany.domain.review.entity.enums.TravelStyleType; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; +import java.util.List; + @Getter @NoArgsConstructor @AllArgsConstructor public class SimpleEvaluationResult { - private TravelStyleType travelStyle; - private TravelPreferenceType travelPreference; - private PersonalityType personalityType; -} + private List travelStyles; + private List travelPreferences; + private List personalityTypes; +} \ No newline at end of file diff --git a/src/main/java/com/dnd/accompany/domain/review/api/dto/TypeCountResult.java b/src/main/java/com/dnd/accompany/domain/review/api/dto/TypeCountResult.java new file mode 100644 index 0000000..265f1a9 --- /dev/null +++ b/src/main/java/com/dnd/accompany/domain/review/api/dto/TypeCountResult.java @@ -0,0 +1,13 @@ +package com.dnd.accompany.domain.review.api.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +@AllArgsConstructor +public class TypeCountResult { + private String type; + private long count; +} \ No newline at end of file diff --git a/src/main/java/com/dnd/accompany/domain/review/infrastructure/ReviewRepositoryImpl.java b/src/main/java/com/dnd/accompany/domain/review/infrastructure/ReviewRepositoryImpl.java index cc4af75..b37c115 100644 --- a/src/main/java/com/dnd/accompany/domain/review/infrastructure/ReviewRepositoryImpl.java +++ b/src/main/java/com/dnd/accompany/domain/review/infrastructure/ReviewRepositoryImpl.java @@ -2,9 +2,9 @@ import com.dnd.accompany.domain.review.api.dto.SimpleEvaluationResult; import com.dnd.accompany.domain.review.api.dto.SimpleReviewResult; +import com.dnd.accompany.domain.review.api.dto.TypeCountResult; import com.querydsl.core.types.Projections; import com.querydsl.core.types.dsl.BooleanExpression; -import com.querydsl.jpa.JPAExpressions; import com.querydsl.jpa.impl.JPAQueryFactory; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Repository; @@ -17,7 +17,6 @@ import static com.dnd.accompany.domain.review.entity.QTravelPreference.travelPreference; import static com.dnd.accompany.domain.review.entity.QTravelStyle.travelStyle; import static com.dnd.accompany.domain.user.entity.QUser.user; -import static com.querydsl.jpa.JPAExpressions.*; @Repository @RequiredArgsConstructor @@ -47,36 +46,40 @@ public List findAllByReceiverId(Long receiverId) { @Override public SimpleEvaluationResult findEvaluationsByReceiverId(Long userId) { - return jpaQueryFactory - .select( - Projections.constructor(SimpleEvaluationResult.class, - select(travelStyle.type) - .from(review) - .join(review.travelStyle, travelStyle) - .where(eqReceiver(userId)) - .groupBy(travelStyle.type) - .orderBy(travelStyle.type.count().desc()) - .limit(1), + List travelStyleCounts = jpaQueryFactory + .select(Projections.constructor(TypeCountResult.class, + travelStyle.type.stringValue(), + travelStyle.type.count() + )) + .from(travelStyle) + .join(travelStyle.review, review) + .where(eqReceiver(userId)) + .groupBy(travelStyle.type) + .fetch(); - select(travelPreference.type) - .from(review) - .join(review.travelPreference, travelPreference) - .where(eqReceiver(userId)) - .groupBy(travelPreference.type) - .orderBy(travelPreference.type.count().desc()) - .limit(1), + List travelPreferenceCounts = jpaQueryFactory + .select(Projections.constructor(TypeCountResult.class, + travelPreference.type.stringValue(), + travelPreference.type.count() + )) + .from(travelPreference) + .join(travelPreference.review, review) + .where(eqReceiver(userId)) + .groupBy(travelPreference.type) + .fetch(); - select(personality.type) - .from(review) - .join(review.personalityType, personality) - .where(eqReceiver(userId)) - .groupBy(personality.type) - .orderBy(personality.type.count().desc()) - .limit(1) - ) - ) - .from(review) - .fetchFirst(); + List personalityCounts = jpaQueryFactory + .select(Projections.constructor(TypeCountResult.class, + personality.type.stringValue(), + personality.type.count() + )) + .from(personality) + .join(personality.review, review) + .where(eqReceiver(userId)) + .groupBy(personality.type) + .fetch(); + + return new SimpleEvaluationResult(travelStyleCounts, travelPreferenceCounts, personalityCounts); } private BooleanExpression eqReceiver(Long receiverId) { diff --git a/src/main/java/com/dnd/accompany/domain/review/service/ReviewService.java b/src/main/java/com/dnd/accompany/domain/review/service/ReviewService.java index 365f221..4a351d0 100644 --- a/src/main/java/com/dnd/accompany/domain/review/service/ReviewService.java +++ b/src/main/java/com/dnd/accompany/domain/review/service/ReviewService.java @@ -2,12 +2,15 @@ import com.dnd.accompany.domain.accompany.entity.AccompanyBoard; import com.dnd.accompany.domain.accompany.infrastructure.AccompanyBoardRepository; +import com.dnd.accompany.domain.review.api.dto.AllEvaluationResponses; import com.dnd.accompany.domain.review.api.dto.CreateReviewRequest; +import com.dnd.accompany.domain.review.api.dto.EvaluationResponse; import com.dnd.accompany.domain.review.api.dto.ReviewDetailsResult; import com.dnd.accompany.domain.review.api.dto.SimpleEvaluationResponse; import com.dnd.accompany.domain.review.api.dto.SimpleEvaluationResult; import com.dnd.accompany.domain.review.api.dto.SimpleReviewResponses; import com.dnd.accompany.domain.review.api.dto.SimpleReviewResult; +import com.dnd.accompany.domain.review.api.dto.TypeCountResult; import com.dnd.accompany.domain.review.entity.Review; import com.dnd.accompany.domain.review.infrastructure.ReviewRepository; import com.dnd.accompany.domain.user.entity.User; @@ -19,6 +22,8 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.util.ArrayList; +import java.util.Comparator; import java.util.List; @Service @@ -95,15 +100,62 @@ private void validateReceiver(Long userId, Review review) { } } - public SimpleEvaluationResponse getEvaluation(Long userId) { + public AllEvaluationResponses getEvaluation(Long userId) { User receiver = getUser(userId); - SimpleEvaluationResult result = reviewRepository.findEvaluationsByReceiverId(userId); + SimpleEvaluationResult evaluations = getEvaluations(userId); + + List travelStyles = evaluations.getTravelStyles(); + List travelPreferences = evaluations.getTravelPreferences(); + List personalities = evaluations.getPersonalityTypes(); + + List combinedResults = new ArrayList<>(); + combinedResults.addAll(travelStyles); + combinedResults.addAll(travelPreferences); + combinedResults.addAll(personalities); + + combinedResults.sort(Comparator.comparingLong(TypeCountResult::getCount).reversed()); + + List evaluationResponses = combinedResults.stream() + .map(EvaluationResponse::create) + .toList(); + + return AllEvaluationResponses.builder() + .evaluationResponse(evaluationResponses) + .evaluationCount(receiver.getEvaluationCount()) + .build(); + } + + public SimpleEvaluationResponse getSimpleEvaluation(Long userId) { + User receiver = getUser(userId); + SimpleEvaluationResult evaluations = getEvaluations(userId); + + List evaluationResponses = List.of( + EvaluationResponse.create( + evaluations.getTravelStyles().stream() + .max(Comparator.comparingLong(TypeCountResult::getCount)) + .orElse(null) + ), + + EvaluationResponse.create( + evaluations.getTravelPreferences().stream() + .max(Comparator.comparingLong(TypeCountResult::getCount)) + .orElse(null) + ), + + EvaluationResponse.create( + evaluations.getPersonalityTypes().stream() + .max(Comparator.comparingLong(TypeCountResult::getCount)) + .orElse(null) + ) + ); return SimpleEvaluationResponse.builder() - .personalityType(result.getPersonalityType()) - .travelPreference(result.getTravelPreference()) - .travelStyle(result.getTravelStyle()) + .evaluationResponse(evaluationResponses) .evaluationCount(receiver.getEvaluationCount()) .build(); } + + private SimpleEvaluationResult getEvaluations(Long userId) { + return reviewRepository.findEvaluationsByReceiverId(userId); + } }