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
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@
import org.websoso.WSSServer.library.service.KeywordService;
import org.websoso.WSSServer.library.service.LibraryService;
import org.websoso.WSSServer.novel.domain.Novel;
import org.websoso.WSSServer.novel.domain.NovelStatisticsContribution;
import org.websoso.WSSServer.novel.service.NovelServiceImpl;
import org.websoso.WSSServer.novel.service.NovelStatisticsService;

/**
* 서재 평가는 서재와 매력 포인트, 키워드가 핵심 도메인이다.
Expand All @@ -39,6 +41,7 @@ public class LibraryEvaluationApplication {

private final NovelServiceImpl novelService;
private final LibraryService libraryService;
private final NovelStatisticsService novelStatisticsService;
private final AttractivePointService attractivePointService;
private final KeywordService keywordService;

Expand All @@ -55,6 +58,11 @@ public void createEvaluation(User user, UserNovelCreateRequest request) {
try {
UserNovel userNovel = libraryService.createLibrary(request.status(), request.userNovelRating(),
request.startDate(), request.endDate(), user, novel);
novelStatisticsService.updateByDelta(
novel.getNovelId(),
NovelStatisticsContribution.EMPTY,
NovelStatisticsContribution.from(userNovel)
);

attractivePointService.createUserNovelAttractivePoints(userNovel, request.attractivePoints());
keywordService.createNovelKeywords(userNovel, request.keywordIds());
Expand Down Expand Up @@ -93,9 +101,16 @@ public UserNovelGetResponse getEvaluation(User user, Long novelId) {
*/
@Transactional
public void updateEvaluation(User user, Long novelId, UserNovelUpdateRequest request) {
UserNovel userNovel = libraryService.getLibraryOrException(user, novelId);
UserNovel userNovel = libraryService.getLibraryForUpdateOrException(user, novelId);
NovelStatisticsContribution before = NovelStatisticsContribution.from(userNovel);

userNovel.updateUserNovel(request.userNovelRating(), request.status(), request.startDate(), request.endDate());
libraryService.updateEvaluation(userNovel, request.userNovelRating(), request.status(), request.startDate(),
request.endDate());
novelStatisticsService.updateByDelta(
novelId,
before,
NovelStatisticsContribution.from(userNovel)
);

updateAttractivePoints(userNovel, request.attractivePoints());

Expand All @@ -110,19 +125,30 @@ public void updateEvaluation(User user, Long novelId, UserNovelUpdateRequest req
*/
@Transactional
public void deleteEvaluation(User user, Long novelId) {
UserNovel userNovel = libraryService.getLibraryOrException(user, novelId);
UserNovel userNovel = libraryService.getLibraryForUpdateOrException(user, novelId);
NovelStatisticsContribution before = NovelStatisticsContribution.from(userNovel);

if (userNovel.getStatus() == null) {
throw new CustomUserNovelException(NOT_EVALUATED, "this novel has not been evaluated by the user");
}

if (userNovel.getIsInterest()) {
userNovel.deleteEvaluation();
libraryService.deleteEvaluation(userNovel);
novelStatisticsService.updateByDelta(
novelId,
before,
NovelStatisticsContribution.from(userNovel)
);

attractivePointService.deleteUserNovelAttractivePoints(userNovel.getUserNovelAttractivePoints());
keywordService.deleteUserNovelKeywords(userNovel.getUserNovelKeywords());
} else {
libraryService.delete(userNovel);
novelStatisticsService.updateByDelta(
novelId,
before,
NovelStatisticsContribution.EMPTY
);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
package org.websoso.WSSServer.application;

import static org.websoso.WSSServer.exception.error.CustomUserNovelError.NOT_INTERESTED;

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.websoso.WSSServer.user.domain.User;
import org.websoso.WSSServer.exception.exception.CustomUserNovelException;
import org.websoso.WSSServer.library.domain.UserNovel;
import org.websoso.WSSServer.library.service.LibraryService;
import org.websoso.WSSServer.novel.domain.Novel;
import org.websoso.WSSServer.novel.domain.NovelStatisticsContribution;
import org.websoso.WSSServer.novel.service.NovelServiceImpl;
import org.websoso.WSSServer.novel.service.NovelStatisticsService;

@Service
@RequiredArgsConstructor
public class LibraryInterestApplication {

private final NovelServiceImpl novelService;
private final LibraryService libraryService;
private final NovelStatisticsService novelStatisticsService;

/**
* 관심있어요를 남긴다.
Expand All @@ -28,8 +28,15 @@ public class LibraryInterestApplication {
@Transactional
public void registerAsInterest(User user, Long novelId) {
Novel novel = novelService.getNovelOrException(novelId);
UserNovel library = libraryService.getOrCreateLibraryForInterest(user, novel);
NovelStatisticsContribution before = NovelStatisticsContribution.from(library);

libraryService.registerInterest(user, novel);
libraryService.registerInterest(library);
novelStatisticsService.updateByDelta(
novelId,
before,
NovelStatisticsContribution.from(library)
);
}

/**
Expand All @@ -40,13 +47,19 @@ public void registerAsInterest(User user, Long novelId) {
*/
@Transactional
public void unregisterAsInterest(User user, Long novelId) {
UserNovel library = libraryService.getLibraryOrNull(user, novelId);
UserNovel library = libraryService.getLibraryForUpdateOrNull(user, novelId);

if (library == null) {
if (library == null || Boolean.FALSE.equals(library.getIsInterest())) {
return;
}

NovelStatisticsContribution before = NovelStatisticsContribution.from(library);
libraryService.unregisterInterest(library);
novelStatisticsService.updateByDelta(
novelId,
before,
NovelStatisticsContribution.from(library)
);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@
import org.websoso.WSSServer.feed.feed.domain.Feed;
import org.websoso.WSSServer.feed.feed.repository.FeedRepository;
import org.websoso.WSSServer.library.domain.Keyword;
import org.websoso.WSSServer.library.domain.UserNovel;
import org.websoso.WSSServer.library.service.LibraryService;
import org.websoso.WSSServer.novel.domain.Novel;
import org.websoso.WSSServer.novel.domain.NovelGenre;
Expand Down Expand Up @@ -86,9 +85,7 @@ public SearchedNovelsResponse searchNovels(User user, String query, int page, in

Page<Novel> novels = novelService.searchNovels(pageRequest, searchQuery);

List<NovelSummaryResponse> novelGetResponsePreviews = novels.stream()
.map(this::convertToDTO2)
.toList();
List<NovelSummaryResponse> novelGetResponsePreviews = convertToNovelSummaries(novels.getContent());

// 로그인한 사용자이며, 검색어가 있는 경우에만 검색 기록에 저장한다.
if (user != null && user.getUserId() != null && !searchQuery.isBlank()) {
Expand All @@ -115,9 +112,7 @@ public FilteredNovelsResponse getFilteredNovels(List<String> genreNames, List<In
novels = novelService.findFilteredNovels(pageRequest, genres, keywords, isCompleted, novelRating, novelRatingEnd, platformNames);
}

List<NovelSummaryResponse> novelGetResponsePreviews = novels.stream()
.map(this::convertToDTO2)
.toList();
List<NovelSummaryResponse> novelGetResponsePreviews = convertToNovelSummaries(novels.getContent());

return FilteredNovelsResponse.of(novelGetResponsePreviews, novels.getTotalElements(), novels.hasNext());
}
Expand Down Expand Up @@ -229,31 +224,17 @@ private String sanitizeQuery(String query) {
.replaceAll("[^a-zA-Z0-9가-힣]", "");
}

private NovelSummaryResponse convertToDTO2(Novel novel) {
// TODO: Repository에서 NovelSummaryResponse에 맞게 데이터를 불러오는게 좋을듯
List<UserNovel> userNovels = novel.getUserNovels();

long interestCount = userNovels.stream()
.filter(UserNovel::getIsInterest)
.count();
long novelRatingCount = userNovels.stream()
.filter(un -> un.getUserNovelRating() != 0.0f)
.count();
double novelRatingSum = userNovels.stream()
.filter(un -> un.getUserNovelRating() != 0.0f)
.mapToDouble(UserNovel::getUserNovelRating)
.sum();

float novelRatingAverage = novelRatingCount == 0
? 0.0f
: Math.round((float) (novelRatingSum / novelRatingCount) * 10.0f) / 10.0f;

return NovelSummaryResponse.of(
novel,
interestCount,
novelRatingAverage,
novelRatingCount
);
private List<NovelSummaryResponse> convertToNovelSummaries(List<Novel> novels) {
List<Long> novelIds = novels.stream()
.map(Novel::getNovelId)
.toList();
Map<Long, Long> interestCounts = libraryService.getInterestCountsByNovelIds(novelIds);
return novels.stream()
.map(novel -> NovelSummaryResponse.of(
novel,
interestCounts.getOrDefault(novel.getNovelId(), 0L)
))
.toList();
}

private String getNovelGenreNames(List<NovelGenre> novelGenres) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.websoso.WSSServer.dto.novel;

import org.websoso.WSSServer.novel.domain.Novel;
import org.websoso.WSSServer.novel.domain.NovelStatistics;

public record NovelSummaryResponse(
long novelId,
Expand All @@ -11,8 +12,18 @@ public record NovelSummaryResponse(
float novelRating,
long novelRatingCount
) {
public static NovelSummaryResponse of(Novel novel, long interestCount, float novelRating,
long novelRatingCount) {
/**
* 작품과 연결된 통계로 검색 결과 요약 응답을 생성한다.
*/
public static NovelSummaryResponse of(Novel novel, long interestCount) {
NovelStatistics statistics = novel.getNovelStatistics();
float novelRating = statistics == null
? 0.0f
: Math.round(statistics.getAverageRating().floatValue() * 10.0f) / 10.0f;
long novelRatingCount = statistics == null
? 0L
: statistics.getRatingCount();

return new NovelSummaryResponse(
novel.getNovelId(),
novel.getNovelImage(),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package org.websoso.WSSServer.controller;
package org.websoso.WSSServer.library.controller;

import static org.springframework.http.HttpStatus.CREATED;
import static org.springframework.http.HttpStatus.NO_CONTENT;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,23 @@

import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
import org.springframework.data.domain.Pageable;
import org.websoso.WSSServer.domain.Genre;
import org.websoso.WSSServer.domain.common.UserNovelSortType;
import org.websoso.WSSServer.library.repository.cursor.UserNovelCursor;
import org.websoso.WSSServer.library.repository.projection.NovelInterestCount;
import org.websoso.WSSServer.novel.domain.Novel;
import org.websoso.WSSServer.library.domain.UserNovel;
import org.websoso.WSSServer.dto.user.UserNovelCountGetResponse;
import org.websoso.WSSServer.user.domain.User;

public interface UserNovelCustomRepository {

Optional<UserNovel> findByNovelIdAndUserForUpdate(Long novelId, User user);

List<NovelInterestCount> findInterestCountsByNovelIds(List<Long> novelIds);

UserNovelCountGetResponse findUserNovelStatistics(Long userId);

List<Long> findTodayPopularNovelsId(Pageable pageable);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.jpa.impl.JPAQuery;
import com.querydsl.jpa.impl.JPAQueryFactory;
import jakarta.persistence.LockModeType;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
Expand All @@ -27,7 +28,9 @@
import org.websoso.WSSServer.domain.common.UserNovelSortType;
import org.websoso.WSSServer.dto.user.UserNovelCountGetResponse;
import org.websoso.WSSServer.library.repository.cursor.UserNovelCursor;
import org.websoso.WSSServer.library.repository.projection.NovelInterestCount;
import org.websoso.WSSServer.novel.domain.Novel;
import org.websoso.WSSServer.user.domain.User;

@Repository
@RequiredArgsConstructor
Expand All @@ -36,6 +39,35 @@ public class UserNovelCustomRepositoryImpl implements UserNovelCustomRepository
private static final long NO_CURSOR = 0L;
private final JPAQueryFactory jpaQueryFactory;

@Override
public Optional<UserNovel> findByNovelIdAndUserForUpdate(Long novelId, User user) {
return Optional.ofNullable(jpaQueryFactory
.selectFrom(userNovel)
.where(
userNovel.novel.novelId.eq(novelId),
userNovel.user.eq(user)
)
.setLockMode(LockModeType.PESSIMISTIC_WRITE)
.fetchOne());
}

@Override
public List<NovelInterestCount> findInterestCountsByNovelIds(List<Long> novelIds) {
return jpaQueryFactory
.select(Projections.constructor(
NovelInterestCount.class,
userNovel.novel.novelId,
userNovel.count()
))
.from(userNovel)
.where(
userNovel.novel.novelId.in(novelIds),
userNovel.isInterest.isTrue()
)
.groupBy(userNovel.novel.novelId)
.fetch();
}

@Override
public UserNovelCountGetResponse findUserNovelStatistics(Long userId) {
return jpaQueryFactory
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package org.websoso.WSSServer.library.repository;

import io.lettuce.core.dynamic.annotation.Param;
import java.util.List;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import org.websoso.WSSServer.novel.domain.Novel;
import org.websoso.WSSServer.user.domain.User;
Expand All @@ -32,23 +32,20 @@ public interface UserNovelRepository extends JpaRepository<UserNovel, Long>, Use

Optional<UserNovel> findByNovel_NovelIdAndUser(Long novelId, User user);

@Modifying(clearAutomatically = true)
@Modifying
@Query(value = """
INSERT INTO user_novel (
INSERT IGNORE INTO user_novel (
user_id, novel_id, is_interest,
user_novel_rating, status,
created_date, modified_date
) VALUES (
:userId, :novelId, true,
:userId, :novelId, false,
:defaultRating,
:#{#defaultStatus?.name()},
NOW(), NOW()
)
ON DUPLICATE KEY UPDATE
is_interest = true,
modified_date = NOW()
""", nativeQuery = true)
void upsertInterest(
int insertLibraryIfAbsent(
@Param("userId") Long userId,
@Param("novelId") Long novelId,
@Param("defaultRating") Float defaultRating,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package org.websoso.WSSServer.library.repository.projection;

public record NovelInterestCount(
Long novelId,
Long interestCount
) {
}
Loading
Loading