Skip to content

Commit 849d406

Browse files
authored
Merge pull request #42 from prgrms-web-devcourse-final-project/feat/emotionRecord/search
감정 기록 검색 기능 및 기타 감정기록 관련 수정
2 parents ec4101f + 6d72e98 commit 849d406

File tree

5 files changed

+89
-33
lines changed

5 files changed

+89
-33
lines changed

src/main/java/org/dfbf/soundlink/domain/emotionRecord/controller/EmotionRecordController.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package org.dfbf.soundlink.domain.emotionRecord.controller;
22

33
import io.swagger.v3.oas.annotations.Operation;
4+
import io.swagger.v3.oas.annotations.Parameter;
45
import io.swagger.v3.oas.annotations.tags.Tag;
56
import jakarta.validation.Valid;
67
import lombok.RequiredArgsConstructor;
@@ -11,6 +12,8 @@
1112
import org.springframework.security.core.annotation.AuthenticationPrincipal;
1213
import org.springframework.web.bind.annotation.*;
1314

15+
import java.util.List;
16+
1417
@RestController
1518
@RequestMapping("/api/emotion")
1619
@RequiredArgsConstructor
@@ -34,13 +37,17 @@ public ResponseResult saveEmotionWithMusic(
3437
@GetMapping
3538
@Operation(
3639
summary = "감정 기록 전체 조회 메인 API",
37-
description = "유저들이 작성한 감정 기록 전체를 조회합니다.(자신의 아이디에 해당하는 감정 기록은 조회되지 않습니다.)"
40+
description = "유저들이 작성한 감정 기록 전체를 조회합니다. 자신의 아이디에 해당하는 감정 기록은 조회되지 않으며," +
41+
" 주어진 spotifyId와 emotion을 가진 감정 기록을 검색 할 수 있습니다. 검색 조건이 없으면 전체 조회합니다."
3842
)
3943
public ResponseResult getEmotionRecordsWithoutMine(
4044
@AuthenticationPrincipal Long userId,
45+
@RequestParam(required = false) String spotifyId,
46+
@Parameter(description = "감정 필터(추가 가능)")
47+
@RequestParam(required = false) List<String> emotions,
4148
@RequestParam(defaultValue = "0") int page,
4249
@RequestParam(defaultValue = "10") int size) {
43-
return emotionRecordService.getEmotionRecordsExcludingUserId(userId, page, size);
50+
return emotionRecordService.getEmotionRecordsExcludingUserIdByFilters(userId, emotions, spotifyId, page, size);
4451
}
4552

4653
@GetMapping("/user")

src/main/java/org/dfbf/soundlink/domain/emotionRecord/repository/dsl/EmotionRecordRepositoryCustom.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import org.dfbf.soundlink.domain.emotionRecord.entity.EmotionRecord;
44
import org.dfbf.soundlink.domain.user.dto.response.EmotionRecordDto;
55
import org.dfbf.soundlink.domain.user.entity.User;
6+
import org.dfbf.soundlink.global.comm.enums.Emotions;
67
import org.springframework.data.domain.Page;
78
import org.springframework.data.domain.Pageable;
89

@@ -15,7 +16,7 @@ public interface EmotionRecordRepositoryCustom {
1516

1617
Page<EmotionRecord> findByLoginId(String loginId, Pageable pageable);
1718

18-
Page<EmotionRecord> findByWithoutUserId(Long userId, Pageable pageable);
19+
Page<EmotionRecord> findByFilters(Long userId, List<Emotions> emotions, String spotifyId, Pageable pageable);
1920

2021
Optional<EmotionRecord> findByRecordId(Long recordId);
2122

src/main/java/org/dfbf/soundlink/domain/emotionRecord/repository/dsl/EmotionRecordRepositoryImpl.java

Lines changed: 48 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package org.dfbf.soundlink.domain.emotionRecord.repository.dsl;
22

33
import com.querydsl.core.types.Projections;
4+
import com.querydsl.core.types.dsl.BooleanExpression;
45
import com.querydsl.jpa.impl.JPAQueryFactory;
56
import lombok.RequiredArgsConstructor;
67
import org.dfbf.soundlink.domain.emotionRecord.entity.EmotionRecord;
@@ -9,9 +10,10 @@
910
import org.dfbf.soundlink.domain.user.dto.response.EmotionRecordDto;
1011
import org.dfbf.soundlink.domain.user.entity.QUser;
1112
import org.dfbf.soundlink.domain.user.entity.User;
13+
import org.dfbf.soundlink.global.comm.enums.Emotions;
1214
import org.springframework.data.domain.Page;
13-
import org.springframework.data.domain.PageImpl;
1415
import org.springframework.data.domain.Pageable;
16+
import org.springframework.data.support.PageableExecutionUtils;
1517
import org.springframework.stereotype.Repository;
1618

1719
import java.util.List;
@@ -50,48 +52,56 @@ public Page<EmotionRecord> findByLoginId(String loginId, Pageable pageable) {
5052
.join(QEmotionRecord.emotionRecord.user, QUser.user).fetchJoin()
5153
.join(QEmotionRecord.emotionRecord.spotifyMusic, QSpotifyMusic.spotifyMusic).fetchJoin()
5254
.where(QUser.user.loginId.eq(loginId))
55+
.orderBy(QEmotionRecord.emotionRecord.createdAt.desc())
5356
.offset(pageable.getOffset())
5457
.limit(pageable.getPageSize())
5558
.fetch();
5659

5760
// Spring Data JPA에서 페이징 처리를 위한 메서드 사용 시,
5861
// 내부적으로 데이터(페이징된 결과)를 가져오는 쿼리와 전체 데이터 수를 계산하는 쿼리가 둘 다 실행됨
5962
// QueryDSL을 사용할 경우에 위와 달리 데이터 수 계산 쿼리를 별도로 실행해 줘야함 (Querydsl 5 이상 권장 방식)
60-
long total = Optional.ofNullable(
61-
jpaQueryFactory
62-
.select(QEmotionRecord.emotionRecord.count())
63-
.from(QEmotionRecord.emotionRecord)
64-
.join(QEmotionRecord.emotionRecord.user, QUser.user)
65-
.where(QUser.user.loginId.eq(loginId))
66-
.fetchOne()
67-
).orElse(0L);
68-
69-
return new PageImpl<>(emotionRecords, pageable, total);
63+
return PageableExecutionUtils.getPage(emotionRecords, pageable, () ->
64+
Optional.ofNullable(
65+
jpaQueryFactory
66+
.select(QEmotionRecord.emotionRecord.count())
67+
.from(QEmotionRecord.emotionRecord)
68+
.join(QEmotionRecord.emotionRecord.user, QUser.user)
69+
.where(QUser.user.loginId.eq(loginId))
70+
.fetchOne()
71+
).orElse(0L)
72+
);
7073
}
7174

72-
// 로그인 된 userId를 제외한 EmotionRecord 조회 (LEFT JOIN으로 spotifyMusic 포함) 후 페이징처리
73-
@Override
74-
public Page<EmotionRecord> findByWithoutUserId(Long userId, Pageable pageable) {
75+
// 동적으로 필터링 후 EmotionRecords를 가져오는 쿼리
76+
public Page<EmotionRecord> findByFilters(Long userId, List<Emotions> emotions, String spotifyId, Pageable pageable) {
7577
List<EmotionRecord> emotionRecords = jpaQueryFactory
7678
.selectFrom(QEmotionRecord.emotionRecord)
7779
.join(QEmotionRecord.emotionRecord.user, QUser.user).fetchJoin()
7880
.leftJoin(QEmotionRecord.emotionRecord.spotifyMusic, QSpotifyMusic.spotifyMusic).fetchJoin()
79-
.where(QUser.user.userId.ne(userId))
81+
.where(
82+
excludeUserId(userId),
83+
filterByEmotions(emotions),
84+
filterBySpotifyId(spotifyId)
85+
)
86+
.orderBy(QEmotionRecord.emotionRecord.createdAt.desc())
8087
.offset(pageable.getOffset())
8188
.limit(pageable.getPageSize())
8289
.fetch();
8390

8491
// 위의 findByLoginId 메서드 설명 참고
85-
long total = Optional.ofNullable(
86-
jpaQueryFactory
87-
.select(QEmotionRecord.emotionRecord.count())
88-
.from(QEmotionRecord.emotionRecord)
89-
.join(QEmotionRecord.emotionRecord.user, QUser.user)
90-
.where(QUser.user.userId.ne(userId))
91-
.fetchOne()
92-
).orElse(0L);
93-
94-
return new PageImpl<>(emotionRecords, pageable, total);
92+
return PageableExecutionUtils.getPage(emotionRecords, pageable, () ->
93+
Optional.ofNullable(
94+
jpaQueryFactory
95+
.select(QEmotionRecord.emotionRecord.count())
96+
.from(QEmotionRecord.emotionRecord)
97+
.join(QEmotionRecord.emotionRecord.user, QUser.user)
98+
.where(
99+
excludeUserId(userId), // 로그인 된 userId를 제외한 EmotionRecord 조회
100+
filterByEmotions(emotions), // 감정이 있을 경우
101+
filterBySpotifyId(spotifyId) // spotifyId 있을 경우
102+
)
103+
.fetchOne()
104+
).orElse(0L));
95105
}
96106

97107
// recordId에 해당하는 EmotionRecord 조회
@@ -107,9 +117,21 @@ public Optional<EmotionRecord> findByRecordId(Long recordId) {
107117
// recordId에 해당하는 EmotionRecord 삭제
108118
@Override
109119
public int deleteByRecordId(Long recordId) {
110-
return (int)jpaQueryFactory
120+
return (int) jpaQueryFactory
111121
.delete(QEmotionRecord.emotionRecord)
112122
.where(QEmotionRecord.emotionRecord.recordId.eq(recordId))
113123
.execute();
114124
}
125+
126+
private BooleanExpression excludeUserId(Long userId) {
127+
return userId != null ? QUser.user.userId.ne(userId) : null;
128+
}
129+
130+
private BooleanExpression filterByEmotions(List<Emotions> emotions) {
131+
return (emotions != null && !emotions.isEmpty()) ? QEmotionRecord.emotionRecord.emotion.in(emotions) : null;
132+
}
133+
134+
private BooleanExpression filterBySpotifyId(String spotifyId) {
135+
return (spotifyId != null && !spotifyId.isBlank()) ? QSpotifyMusic.spotifyMusic.spotifyId.eq(spotifyId) : null;
136+
}
115137
}

src/main/java/org/dfbf/soundlink/domain/emotionRecord/service/EmotionRecordService.java

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import org.dfbf.soundlink.domain.emotionRecord.repository.SpotifyMusicRepository;
1414
import org.dfbf.soundlink.domain.user.entity.User;
1515
import org.dfbf.soundlink.domain.user.repository.UserRepository;
16+
import org.dfbf.soundlink.global.comm.enums.Emotions;
1617
import org.dfbf.soundlink.global.exception.ErrorCode;
1718
import org.dfbf.soundlink.global.exception.ResponseResult;
1819
import org.springframework.dao.DataAccessException;
@@ -72,8 +73,14 @@ public ResponseResult saveEmotionRecordWithMusic(Long userId, EmotionRecordReque
7273

7374
@Transactional(readOnly = true)
7475
public ResponseResult getEmotionRecordsByLoginId(String userTag, int page, int size) {
76+
Pageable pageable;
77+
78+
try {
79+
pageable = PageRequest.of(page, size, Sort.by("createdAt").descending());
80+
} catch (IllegalArgumentException e) {
81+
return new ResponseResult(ErrorCode.INVALID_PAGE_REQUEST, "페이지 요청 값이 잘못되었습니다.");
82+
}
7583
try {
76-
Pageable pageable = PageRequest.of(page, size, Sort.by("createdAt").descending());
7784
Page<EmotionRecord> recordsPage = emotionRecordRepository.findByLoginId(userTag, pageable);
7885

7986
List<EmotionRecordResponseWithoutNicknameDTO> dtoList = recordsPage.getContent()
@@ -89,12 +96,28 @@ public ResponseResult getEmotionRecordsByLoginId(String userTag, int page, int s
8996
}
9097
}
9198

92-
public ResponseResult getEmotionRecordsExcludingUserId(Long userId, int page, int size) {
99+
public ResponseResult getEmotionRecordsExcludingUserIdByFilters(Long userId, List<String> emotionList, String spotifyId, int page, int size) {
100+
Pageable pageable;
101+
List<Emotions> emotionEnums = null;
93102

94103
try {
95-
Pageable pageable = PageRequest.of(page, size, Sort.by("createdAt").descending());
96-
Page<EmotionRecord> recordsPage = emotionRecordRepository.findByWithoutUserId(userId, pageable);
104+
pageable = PageRequest.of(page, size, Sort.by("createdAt").descending());
105+
} catch (IllegalArgumentException e) {
106+
return new ResponseResult(ErrorCode.INVALID_PAGE_REQUEST, "페이지 요청 값이 잘못되었습니다.");
107+
}
97108

109+
if (emotionList != null && !emotionList.isEmpty()) {
110+
try {
111+
emotionEnums = emotionList.stream()
112+
.map(e -> Emotions.valueOf(e.toUpperCase()))
113+
.toList();
114+
} catch (IllegalArgumentException e) {
115+
return new ResponseResult(ErrorCode.FAIL_TO_FIND_EMOTION, "잘못된 감정 값이 포함되어 있습니다.");
116+
}
117+
}
118+
119+
try {
120+
Page<EmotionRecord> recordsPage = emotionRecordRepository.findByFilters(userId, emotionEnums, spotifyId, pageable);
98121
List<EmotionRecordResponseMainDTO> dtoList = recordsPage.getContent()
99122
.stream()
100123
.map(EmotionRecordResponseMainDTO::fromEntity)
@@ -114,6 +137,7 @@ public ResponseResult getEmotionRecord(Long userId, Long recordId) {
114137
try {
115138
EmotionRecord records = emotionRecordRepository.findByRecordId(recordId)
116139
.orElseThrow(EmotionRecordNotFoundException::new);
140+
117141
return new ResponseResult(ErrorCode.SUCCESS, EmotionRecordResponseWithOwnerDTO.fromEntity(records, userId));
118142
} catch (EmotionRecordNotFoundException e) {
119143
return new ResponseResult(ErrorCode.FAIL_TO_FIND_EMOTION_RECORD, e.getMessage());

src/main/java/org/dfbf/soundlink/global/exception/ErrorCode.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ public enum ErrorCode {
4343

4444
// EmotionRecord
4545
FAIL_TO_FIND_EMOTION_RECORD(HttpStatus.NOT_FOUND, "해당 감정 기록을 찾을 수 없습니다."),
46+
FAIL_TO_FIND_EMOTION(HttpStatus.NOT_FOUND, "해당 감정을 찾을 수 없습니다."),
47+
INVALID_PAGE_REQUEST(HttpStatus.BAD_REQUEST, "페이지 요청 값이 잘못되었습니다."),
4648

4749
// Auth
4850
TOKEN_NOT_EXPIRED(HttpStatus.OK, "토큰 정상"),

0 commit comments

Comments
 (0)