Skip to content

Commit 8abb773

Browse files
authored
Merge pull request #356 from Team-WSS/feat/#352
[FEAT] 서재 기능 업데이트 구현
2 parents 69c17d0 + f95071d commit 8abb773

File tree

7 files changed

+185
-109
lines changed

7 files changed

+185
-109
lines changed

src/main/java/org/websoso/WSSServer/controller/UserController.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import static org.springframework.http.HttpStatus.OK;
66

77
import jakarta.validation.Valid;
8+
import java.util.List;
89
import lombok.RequiredArgsConstructor;
910
import org.springframework.http.ResponseEntity;
1011
import org.springframework.security.access.prepost.PreAuthorize;
@@ -147,14 +148,19 @@ public ResponseEntity<UserIdAndNicknameResponse> getUserIdAndNicknameAndGender(@
147148
@GetMapping("/{userId}/novels")
148149
public ResponseEntity<UserNovelAndNovelsGetResponse> getUserNovelsAndNovels(@AuthenticationPrincipal User visitor,
149150
@PathVariable("userId") Long userId,
150-
@RequestParam("readStatus") String readStatus,
151+
@RequestParam(value = "isInterest", required = false) Boolean isInterest,
152+
@RequestParam(value = "readStatuses", required = false) List<String> readStatuses,
153+
@RequestParam(value = "attractivePoints", required = false) List<String> attractivePoints,
154+
@RequestParam(value = "novelRating", required = false) Float novelRating,
155+
@RequestParam(value = "query", required = false) String query,
151156
@RequestParam("lastUserNovelId") Long lastUserNovelId,
152157
@RequestParam("size") int size,
153158
@RequestParam("sortType") String sortType) {
154159
return ResponseEntity
155160
.status(OK)
156161
.body(userNovelService.getUserNovelsAndNovels(
157-
visitor, userId, readStatus, lastUserNovelId, size, sortType));
162+
visitor, userId, isInterest, readStatuses, attractivePoints, novelRating, query,
163+
lastUserNovelId, size, sortType));
158164
}
159165

160166
@GetMapping("/{userId}/feeds")
Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,55 @@
11
package org.websoso.WSSServer.dto.userNovel;
22

3+
import java.util.List;
4+
import org.websoso.WSSServer.domain.AttractivePoint;
5+
import org.websoso.WSSServer.domain.Keyword;
6+
import org.websoso.WSSServer.domain.Novel;
37
import org.websoso.WSSServer.domain.UserNovel;
8+
import org.websoso.WSSServer.domain.UserNovelAttractivePoint;
9+
import org.websoso.WSSServer.domain.UserNovelKeyword;
410

511
public record UserNovelAndNovelGetResponse(
612
Long userNovelId,
713
Long novelId,
8-
String author,
9-
String novelImage,
1014
String title,
11-
Float novelRating
15+
String novelImage,
16+
Float novelRating,
17+
String readStatus,
18+
Boolean isInterest,
19+
Float userNovelRating,
20+
List<String> attractivePoints,
21+
String startDate,
22+
String endDate,
23+
List<String> keywords,
24+
List<String> myFeeds
1225
) {
26+
public static UserNovelAndNovelGetResponse from(UserNovel userNovel, Float novelRatingAvg, List<String> feeds) {
27+
Novel novel = userNovel.getNovel();
28+
29+
List<String> attractivePoints = userNovel.getUserNovelAttractivePoints().stream()
30+
.map(UserNovelAttractivePoint::getAttractivePoint)
31+
.map(AttractivePoint::getAttractivePointName)
32+
.toList();
33+
34+
List<String> keywords = userNovel.getUserNovelKeywords().stream()
35+
.map(UserNovelKeyword::getKeyword)
36+
.map(Keyword::getKeywordName)
37+
.toList();
1338

14-
public static UserNovelAndNovelGetResponse of(UserNovel userNovel) {
1539
return new UserNovelAndNovelGetResponse(
1640
userNovel.getUserNovelId(),
17-
userNovel.getNovel().getNovelId(),
18-
userNovel.getNovel().getAuthor(),
19-
userNovel.getNovel().getNovelImage(),
20-
userNovel.getNovel().getTitle(),
21-
userNovel.getUserNovelRating()
41+
novel.getNovelId(),
42+
novel.getTitle(),
43+
novel.getNovelImage(),
44+
novelRatingAvg,
45+
userNovel.getStatus() != null ? userNovel.getStatus().name() : null,
46+
userNovel.getIsInterest(),
47+
userNovel.getUserNovelRating(),
48+
attractivePoints,
49+
userNovel.getStartDate() != null ? userNovel.getStartDate().toString() : null,
50+
userNovel.getEndDate() != null ? userNovel.getEndDate().toString() : null,
51+
keywords,
52+
feeds
2253
);
2354
}
2455
}

src/main/java/org/websoso/WSSServer/dto/userNovel/UserNovelAndNovelsGetResponse.java

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,12 @@
44

55
public record UserNovelAndNovelsGetResponse(
66
Long userNovelCount,
7-
Float userNovelRating,
87
Boolean isLoadable,
98
List<UserNovelAndNovelGetResponse> userNovels
109
) {
1110

12-
public static UserNovelAndNovelsGetResponse of(Long userNovelCount, Float userNovelRating, Boolean isLoadable,
11+
public static UserNovelAndNovelsGetResponse of(Long userNovelCount, Boolean isLoadable,
1312
List<UserNovelAndNovelGetResponse> userNovelAndNovelGetResponses) {
14-
return new UserNovelAndNovelsGetResponse(userNovelCount, userNovelRating, isLoadable,
15-
userNovelAndNovelGetResponses);
13+
return new UserNovelAndNovelsGetResponse(userNovelCount, isLoadable, userNovelAndNovelGetResponses);
1614
}
1715
}

src/main/java/org/websoso/WSSServer/repository/FeedRepository.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,9 @@ public interface FeedRepository extends JpaRepository<Feed, Long>, FeedCustomRep
3838
@Modifying(clearAutomatically = true, flushAutomatically = true)
3939
@Query("UPDATE Feed f SET f.user.userId = -1 WHERE f.user.userId = :userId")
4040
void updateUserToUnknown(Long userId);
41+
42+
List<Feed> findByUserUserIdAndIsHiddenFalseAndNovelIdIn(Long userId, List<Long> novelIds);
43+
44+
List<Feed> findByUserUserIdAndIsHiddenFalseAndNovelIdInAndIsPublicTrueAndIsSpoilerFalse(Long userId,
45+
List<Long> novelIds);
4146
}

src/main/java/org/websoso/WSSServer/repository/UserNovelCustomRepository.java

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
import org.springframework.data.domain.Pageable;
55
import org.websoso.WSSServer.domain.Genre;
66
import org.websoso.WSSServer.domain.Novel;
7-
import org.websoso.WSSServer.domain.User;
87
import org.websoso.WSSServer.domain.UserNovel;
98
import org.websoso.WSSServer.dto.user.UserNovelCountGetResponse;
109

@@ -14,10 +13,12 @@ public interface UserNovelCustomRepository {
1413

1514
List<Long> findTodayPopularNovelsId(Pageable pageable);
1615

17-
List<UserNovel> findUserNovelsByNoOffsetPagination(User owner, Long lastUserNovelId, int size,
18-
String readStatus, String sortType);
16+
List<Novel> findTasteNovels(List<Genre> preferGenres);
1917

20-
List<UserNovel> findByUserAndReadStatus(User owner, String readStatus);
18+
List<UserNovel> findFilteredUserNovels(Long userId, Boolean isInterest, List<String> readStatuses,
19+
List<String> attractivePoints, Float novelRating, String query,
20+
Long lastNovelId, int size, boolean isAscending);
2121

22-
List<Novel> findTasteNovels(List<Genre> preferGenres);
22+
Long countByUserIdAndFilters(Long userId, Boolean isInterest, List<String> readStatuses,
23+
List<String> attractivePoints, Float novelRating, String query);
2324
}

src/main/java/org/websoso/WSSServer/repository/UserNovelCustomRepositoryImpl.java

Lines changed: 62 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,18 @@
77
import static org.websoso.WSSServer.domain.common.ReadStatus.WATCHED;
88
import static org.websoso.WSSServer.domain.common.ReadStatus.WATCHING;
99

10-
import com.querydsl.core.types.OrderSpecifier;
1110
import com.querydsl.core.types.Projections;
12-
import com.querydsl.core.types.dsl.BooleanExpression;
11+
import com.querydsl.jpa.impl.JPAQuery;
1312
import com.querydsl.jpa.impl.JPAQueryFactory;
1413
import java.time.LocalDate;
1514
import java.util.List;
15+
import java.util.Optional;
16+
import java.util.stream.Collectors;
1617
import lombok.RequiredArgsConstructor;
1718
import org.springframework.data.domain.Pageable;
1819
import org.springframework.stereotype.Repository;
1920
import org.websoso.WSSServer.domain.Genre;
2021
import org.websoso.WSSServer.domain.Novel;
21-
import org.websoso.WSSServer.domain.User;
2222
import org.websoso.WSSServer.domain.UserNovel;
2323
import org.websoso.WSSServer.domain.common.ReadStatus;
2424
import org.websoso.WSSServer.dto.user.UserNovelCountGetResponse;
@@ -63,66 +63,6 @@ public UserNovelCountGetResponse findUserNovelStatistics(Long userId) {
6363
.fetchOne();
6464
}
6565

66-
@Override
67-
public List<UserNovel> findUserNovelsByNoOffsetPagination(User owner, Long lastUserNovelId, int size,
68-
String readStatus, String sortType) {
69-
return jpaQueryFactory
70-
.selectFrom(userNovel)
71-
.where(
72-
userNovel.user.eq(owner),
73-
generateReadStatusCondition(readStatus),
74-
compareFeedId(lastUserNovelId, sortType)
75-
)
76-
.orderBy(getSortOrder(sortType))
77-
.limit(size)
78-
.fetch();
79-
}
80-
81-
private BooleanExpression compareFeedId(Long lastUserNovelId, String sortType) {
82-
if (lastUserNovelId == 0) {
83-
return null;
84-
}
85-
86-
// TODO 잘못된 sortType이 오는 경우 default null로 return이 아닌 예외 처리
87-
if ("NEWEST".equalsIgnoreCase(sortType)) {
88-
return userNovel.userNovelId.lt(lastUserNovelId);
89-
} else if ("OLDEST".equalsIgnoreCase(sortType)) {
90-
return userNovel.userNovelId.gt(lastUserNovelId);
91-
}
92-
93-
return null;
94-
}
95-
96-
private BooleanExpression generateReadStatusCondition(String readStatus) {
97-
// TODO 잘못된 readStatus가 오는 경우 예외 처리
98-
if (readStatus.equals("INTEREST")) {
99-
return userNovel.isInterest.isTrue();
100-
} else {
101-
ReadStatus status = ReadStatus.valueOf(readStatus);
102-
return userNovel.status.eq(status);
103-
}
104-
}
105-
106-
private OrderSpecifier<?> getSortOrder(String sortType) {
107-
// TODO 잘못된 sortType이 오는 경우 default desc가 아닌 예외 처리
108-
if ("NEWEST".equalsIgnoreCase(sortType)) {
109-
return userNovel.userNovelId.desc();
110-
} else if ("OLDEST".equalsIgnoreCase(sortType)) {
111-
return userNovel.userNovelId.asc();
112-
}
113-
return userNovel.userNovelId.desc();
114-
}
115-
116-
@Override
117-
public List<UserNovel> findByUserAndReadStatus(User owner, String readStatus) {
118-
return jpaQueryFactory
119-
.selectFrom(userNovel)
120-
.where(userNovel.user.eq(owner),
121-
generateReadStatusCondition(readStatus)
122-
)
123-
.fetch();
124-
}
125-
12666
public List<Long> findTodayPopularNovelsId(Pageable pageable) {
12767
LocalDate sevenDaysAgo = LocalDate.now().minusDays(7);
12868

@@ -156,4 +96,63 @@ public List<Novel> findTasteNovels(List<Genre> preferGenres) {
15696
.limit(10)
15797
.toList();
15898
}
99+
100+
@Override
101+
public List<UserNovel> findFilteredUserNovels(Long userId, Boolean isInterest, List<String> readStatuses,
102+
List<String> attractivePoints, Float novelRating, String query,
103+
Long lastUserNovelId, int size, boolean isAscending) {
104+
JPAQuery<UserNovel> queryBuilder = jpaQueryFactory
105+
.selectFrom(userNovel)
106+
.join(userNovel.novel, novel).fetchJoin()
107+
.where(userNovel.user.userId.eq(userId));
108+
109+
applyFilters(queryBuilder, isInterest, readStatuses, attractivePoints, novelRating, query);
110+
111+
queryBuilder.where(isAscending
112+
? userNovel.userNovelId.gt(lastUserNovelId)
113+
: userNovel.userNovelId.lt(lastUserNovelId));
114+
queryBuilder.orderBy(isAscending
115+
? userNovel.userNovelId.asc()
116+
: userNovel.userNovelId.desc());
117+
return queryBuilder.limit(size).fetch();
118+
}
119+
120+
@Override
121+
public Long countByUserIdAndFilters(Long userId, Boolean isInterest, List<String> readStatuses,
122+
List<String> attractivePoints, Float novelRating, String query) {
123+
JPAQuery<Long> queryBuilder = jpaQueryFactory
124+
.select(userNovel.count())
125+
.from(userNovel)
126+
.join(userNovel.novel, novel)
127+
.where(userNovel.user.userId.eq(userId));
128+
129+
applyFilters(queryBuilder, isInterest, readStatuses, attractivePoints, novelRating, query);
130+
131+
return queryBuilder.fetchOne();
132+
}
133+
134+
private <T> void applyFilters(JPAQuery<T> queryBuilder, Boolean isInterest, List<String> readStatuses,
135+
List<String> attractivePoints, Float novelRating, String query) {
136+
Optional.ofNullable(isInterest)
137+
.ifPresent(interest -> queryBuilder.where(userNovel.isInterest.eq(interest)));
138+
139+
Optional.ofNullable(readStatuses)
140+
.filter(list -> !list.isEmpty())
141+
.map(list -> list.stream().map(String::toUpperCase).map(ReadStatus::valueOf)
142+
.collect(Collectors.toList()))
143+
.ifPresent(statusEnums -> queryBuilder.where(userNovel.status.in(statusEnums)));
144+
145+
Optional.ofNullable(attractivePoints)
146+
.filter(list -> !list.isEmpty())
147+
.ifPresent(points -> queryBuilder.where(
148+
userNovel.userNovelAttractivePoints.any().attractivePoint.attractivePointName.in(points)));
149+
150+
Optional.ofNullable(novelRating)
151+
.ifPresent(rating -> queryBuilder.where(userNovel.userNovelRating.goe(rating)));
152+
153+
Optional.ofNullable(query)
154+
.filter(q -> !q.isBlank())
155+
.ifPresent(q -> queryBuilder.where(
156+
novel.title.containsIgnoreCase(q).or(novel.author.containsIgnoreCase(q))));
157+
}
159158
}

0 commit comments

Comments
 (0)