diff --git a/src/main/java/org/websoso/WSSServer/domain/UserNovel.java b/src/main/java/org/websoso/WSSServer/domain/UserNovel.java index 5dbc3964..04941bf2 100644 --- a/src/main/java/org/websoso/WSSServer/domain/UserNovel.java +++ b/src/main/java/org/websoso/WSSServer/domain/UserNovel.java @@ -22,6 +22,8 @@ import lombok.Getter; import lombok.NoArgsConstructor; import org.hibernate.annotations.DynamicInsert; +import org.hibernate.annotations.SQLDelete; +import org.hibernate.annotations.SQLRestriction; import org.websoso.WSSServer.domain.common.BaseEntity; import org.websoso.WSSServer.domain.common.ReadStatus; @@ -30,8 +32,10 @@ @DynamicInsert @NoArgsConstructor(access = AccessLevel.PROTECTED) @Table(name = "user_novel", uniqueConstraints = { - @UniqueConstraint(columnNames = {"user_id", "novel_id"}) + @UniqueConstraint(columnNames = {"user_id", "novel_id", "is_deleted"}) }) +@SQLDelete(sql = "UPDATE user_novel SET is_deleted = true WHERE user_novel_id = ?") +@SQLRestriction("is_deleted = false") public class UserNovel extends BaseEntity { @Id @@ -55,6 +59,9 @@ public class UserNovel extends BaseEntity { @Column private LocalDate endDate; + @Column(columnDefinition = "Boolean default false", nullable = false) + private boolean isDeleted = false; + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "user_id", nullable = false) private User user; @@ -78,6 +85,7 @@ private UserNovel(ReadStatus status, Float userNovelRating, LocalDate startDate, this.user = user; this.novel = novel; this.isInterest = false; + this.isDeleted = false; } public static UserNovel create(ReadStatus status, Float userNovelRating, LocalDate startDate, LocalDate endDate, @@ -103,4 +111,8 @@ public void deleteEvaluation() { this.endDate = null; } + public void restore() { + this.isDeleted = false; + deleteEvaluation(); + } } diff --git a/src/main/java/org/websoso/WSSServer/repository/NovelCustomRepositoryImpl.java b/src/main/java/org/websoso/WSSServer/repository/NovelCustomRepositoryImpl.java index 3ca26356..ef8bebb4 100644 --- a/src/main/java/org/websoso/WSSServer/repository/NovelCustomRepositoryImpl.java +++ b/src/main/java/org/websoso/WSSServer/repository/NovelCustomRepositoryImpl.java @@ -78,7 +78,7 @@ public Page findFilteredNovels(Pageable pageable, List genres, Boo List keywords) { NumberTemplate popularity = Expressions.numberTemplate(Long.class, - "(SELECT COUNT(un) FROM UserNovel un WHERE un.novel = {0} AND (un.isInterest = true OR un.status <> 'QUIT'))", + "(SELECT COUNT(un) FROM UserNovel un WHERE un.novel = {0} AND un.isDeleted = false AND (un.isInterest = true OR un.status <> 'QUIT'))", novel); JPAQuery query = jpaQueryFactory @@ -106,20 +106,25 @@ public Page findFilteredNovels(Pageable pageable, List genres, Boo private NumberExpression getAverageRating(QNovel novel) { return Expressions.numberTemplate(Double.class, - "(SELECT AVG(un.userNovelRating) FROM UserNovel un WHERE un.novel = {0} AND un.userNovelRating <> 0)", + "(SELECT AVG(un.userNovelRating) FROM UserNovel un WHERE un.novel = {0} AND un.isDeleted = false AND un.userNovelRating <> 0)", novel); } private NumberExpression getKeywordCount(QNovel novel, List keywords) { return Expressions.numberTemplate(Integer.class, - "(SELECT COUNT(unk.keyword) FROM UserNovelKeyword unk WHERE unk.userNovel.novel = {0} AND unk.keyword IN ({1}) GROUP BY unk.userNovel.novel.id)", + "(SELECT COUNT(unk.keyword) FROM UserNovelKeyword unk JOIN unk.userNovel un WHERE un.novel = {0} AND un.isDeleted = false AND unk.keyword IN ({1}) GROUP BY un.novel.id)", novel, keywords); } private NumberExpression getPopularity(QNovel novel) { return new CaseBuilder() - .when(userNovel.isInterest.isTrue() - .or(userNovel.status.in(WATCHING, WATCHED))) + .when( + userNovel.isDeleted.isFalse() + .and( + userNovel.isInterest.isTrue() + .or(userNovel.status.in(WATCHING, WATCHED)) + ) + ) .then(1L) .otherwise(0L) .sum(); diff --git a/src/main/java/org/websoso/WSSServer/repository/NovelRepository.java b/src/main/java/org/websoso/WSSServer/repository/NovelRepository.java index 02d235a1..cbeec953 100644 --- a/src/main/java/org/websoso/WSSServer/repository/NovelRepository.java +++ b/src/main/java/org/websoso/WSSServer/repository/NovelRepository.java @@ -11,6 +11,7 @@ public interface NovelRepository extends JpaRepository, NovelCustomRepository { @Query("SELECT n FROM Novel n JOIN UserNovel un ON n.novelId = un.novel.novelId " + + "WHERE un.isDeleted = false " + "GROUP BY n.novelId " + "ORDER BY MAX(un.createdDate) DESC") Page findSosoPick(Pageable pageable); diff --git a/src/main/java/org/websoso/WSSServer/repository/UserNovelRepository.java b/src/main/java/org/websoso/WSSServer/repository/UserNovelRepository.java index e0fdda96..561f0175 100644 --- a/src/main/java/org/websoso/WSSServer/repository/UserNovelRepository.java +++ b/src/main/java/org/websoso/WSSServer/repository/UserNovelRepository.java @@ -19,7 +19,7 @@ public interface UserNovelRepository extends JpaRepository, Use Integer countByNovelAndIsInterestTrue(Novel novel); - @Query("SELECT SUM(un.userNovelRating) FROM UserNovel un WHERE un.novel = :novel") + @Query("SELECT SUM(un.userNovelRating) FROM UserNovel un WHERE un.novel = :novel AND un.isDeleted = false") Float sumUserNovelRatingByNovel(Novel novel); Integer countByNovelAndUserNovelRatingNot(Novel novel, float ratingToExclude); @@ -29,4 +29,7 @@ public interface UserNovelRepository extends JpaRepository, Use List findUserNovelByUser(User user); Optional findByNovel_NovelIdAndUser(Long novelId, User user); + + @Query(value = "SELECT * FROM user_novel WHERE user_id = :userId AND novel_id = :novelId", nativeQuery = true) + Optional findByUserAndNovelIncludeDeleted(Long userId, Long novelId); } diff --git a/src/main/java/org/websoso/WSSServer/service/NovelService.java b/src/main/java/org/websoso/WSSServer/service/NovelService.java index 804facb7..f1a89f6b 100644 --- a/src/main/java/org/websoso/WSSServer/service/NovelService.java +++ b/src/main/java/org/websoso/WSSServer/service/NovelService.java @@ -14,6 +14,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Random; import java.util.Set; import java.util.stream.Collectors; @@ -131,13 +132,19 @@ private String getRandomNovelGenreImage(List novelGenres) { public void registerAsInterest(User user, Long novelId) { Novel novel = getNovelOrException(novelId); - UserNovel userNovel = userNovelService.getUserNovelOrNull(user, novel); - - if (userNovel != null && userNovel.getIsInterest()) { - throw new CustomUserNovelException(ALREADY_INTERESTED, "already registered as interested"); - } - - if (userNovel == null) { + Optional existingUserNovel = userNovelRepository.findByUserAndNovelIncludeDeleted(user.getUserId(), + novelId); + UserNovel userNovel; + + if (existingUserNovel.isPresent()) { + userNovel = existingUserNovel.get(); + if ((!userNovel.isDeleted()) && userNovel.getIsInterest()) { + throw new CustomUserNovelException(ALREADY_INTERESTED, "already registered as interested"); + } + if (userNovel.isDeleted()) { + userNovel.restore(); + } + } else { try { userNovel = userNovelService.createUserNovelByInterest(user, novel); } catch (DataIntegrityViolationException e) { diff --git a/src/main/java/org/websoso/WSSServer/service/UserNovelService.java b/src/main/java/org/websoso/WSSServer/service/UserNovelService.java index 7f85459e..e864a28b 100644 --- a/src/main/java/org/websoso/WSSServer/service/UserNovelService.java +++ b/src/main/java/org/websoso/WSSServer/service/UserNovelService.java @@ -15,6 +15,7 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; @@ -98,6 +99,22 @@ public void createEvaluation(User user, UserNovelCreateRequest request) { Novel novel = novelRepository.findById(request.novelId()) .orElseThrow(() -> new CustomNovelException(NOVEL_NOT_FOUND, "novel with the given id is not found")); + Optional conflictingUserNovel = userNovelRepository.findByUserAndNovelIncludeDeleted( + user.getUserId(), novel.getNovelId()); + + if (conflictingUserNovel.isPresent()) { + UserNovel userNovel = conflictingUserNovel.get(); + if (!userNovel.isDeleted()) { + throw new CustomUserNovelException(USER_NOVEL_ALREADY_EXISTS, "this novel is already registered"); + } + userNovel.restore(); + userNovel.updateUserNovel(request.userNovelRating(), request.status(), request.startDate(), + request.endDate()); + createUserNovelAttractivePoints(userNovel, request.attractivePoints()); + createNovelKeywords(userNovel, request.keywordIds()); + return; + } + try { UserNovel userNovel = userNovelRepository.save(UserNovel.create( request.status(), @@ -236,8 +253,15 @@ public void deleteEvaluation(User user, Long novelId) { } public UserNovel createUserNovelByInterest(User user, Novel novel) { - if (getUserNovelOrNull(user, novel) != null) { - throw new CustomUserNovelException(USER_NOVEL_ALREADY_EXISTS, "this novel is already registered"); + Optional conflictingUserNovel = userNovelRepository.findByUserAndNovelIncludeDeleted( + user.getUserId(), novel.getNovelId()); + if (conflictingUserNovel.isPresent()) { + UserNovel userNovel = conflictingUserNovel.get(); + if (!userNovel.isDeleted()) { + throw new CustomUserNovelException(USER_NOVEL_ALREADY_EXISTS, "this novel is already registered"); + } + userNovel.restore(); + return userNovel; } return userNovelRepository.save(UserNovel.create(null, 0.0f, null, null, user, novel)); }