From a81be74811c9a889f2f304e01b3a11b01d7abc6b Mon Sep 17 00:00:00 2001 From: rinarina0429 Date: Sun, 3 Aug 2025 01:26:23 +0900 Subject: [PATCH 1/8] =?UTF-8?q?[FEAT]=20UserNovel=EC=97=90=20isDeleted=20?= =?UTF-8?q?=ED=95=84=EB=93=9C=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=EC=86=8C?= =?UTF-8?q?=ED=94=84=ED=8A=B8=EB=94=9C=EB=A6=AC=ED=8A=B8=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=EC=96=B4=EB=85=B8=ED=85=8C=EC=9D=B4=EC=85=98=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/org/websoso/WSSServer/domain/UserNovel.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/main/java/org/websoso/WSSServer/domain/UserNovel.java b/src/main/java/org/websoso/WSSServer/domain/UserNovel.java index 5dbc3964..464a5c8e 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; @@ -32,6 +34,8 @@ @Table(name = "user_novel", uniqueConstraints = { @UniqueConstraint(columnNames = {"user_id", "novel_id"}) }) +@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, From 6c5ff649199cc30e8c4bbf1c43328d9959b5eccb Mon Sep 17 00:00:00 2001 From: rinarina0429 Date: Sun, 3 Aug 2025 21:04:46 +0900 Subject: [PATCH 2/8] =?UTF-8?q?[FEAT]=20UserNovel=20=EC=82=AD=EC=A0=9C=20?= =?UTF-8?q?=ED=9B=84=20=EC=9E=AC=EB=93=B1=EB=A1=9D=20=EA=B2=BD=EC=9A=B0?= =?UTF-8?q?=EB=A5=BC=20=EC=9C=84=ED=95=9C=20restore()=20=ED=95=A8=EC=88=98?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/org/websoso/WSSServer/domain/UserNovel.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/websoso/WSSServer/domain/UserNovel.java b/src/main/java/org/websoso/WSSServer/domain/UserNovel.java index 464a5c8e..04f573db 100644 --- a/src/main/java/org/websoso/WSSServer/domain/UserNovel.java +++ b/src/main/java/org/websoso/WSSServer/domain/UserNovel.java @@ -32,7 +32,7 @@ @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") @@ -111,4 +111,7 @@ public void deleteEvaluation() { this.endDate = null; } + public void restore() { + this.isDeleted = false; + } } From 8bd3b9a39096cfb3d35ff63a64a16d8dabfea504 Mon Sep 17 00:00:00 2001 From: rinarina0429 Date: Mon, 4 Aug 2025 01:09:44 +0900 Subject: [PATCH 3/8] =?UTF-8?q?[FIX]=20=EC=9E=91=ED=92=88=20=ED=8F=89?= =?UTF-8?q?=EA=B0=80=20=EC=8B=9C=20soft=20delete=EB=90=9C=20=EA=B2=BD?= =?UTF-8?q?=EC=9A=B0=20=EC=B2=98=EB=A6=AC=20=EB=A1=9C=EC=A7=81=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/UserNovelRepository.java | 3 +++ .../WSSServer/service/UserNovelService.java | 16 ++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/src/main/java/org/websoso/WSSServer/repository/UserNovelRepository.java b/src/main/java/org/websoso/WSSServer/repository/UserNovelRepository.java index e0fdda96..cf9f3030 100644 --- a/src/main/java/org/websoso/WSSServer/repository/UserNovelRepository.java +++ b/src/main/java/org/websoso/WSSServer/repository/UserNovelRepository.java @@ -29,4 +29,7 @@ public interface UserNovelRepository extends JpaRepository, Use List findUserNovelByUser(User user); Optional findByNovel_NovelIdAndUser(Long novelId, User user); + + @Query("SELECT un FROM UserNovel un WHERE un.user = :user AND un.novel = :novel") + Optional findByUserAndNovelIncludeDeleted(User user, Novel novel); } diff --git a/src/main/java/org/websoso/WSSServer/service/UserNovelService.java b/src/main/java/org/websoso/WSSServer/service/UserNovelService.java index 7f85459e..4741c730 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,21 @@ 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, novel); + + 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(), From 86bb9a8f610538ddfd08ff905821a5cc2cd4495d Mon Sep 17 00:00:00 2001 From: rinarina0429 Date: Mon, 4 Aug 2025 01:42:26 +0900 Subject: [PATCH 4/8] =?UTF-8?q?[FIX]=20=EC=9E=91=ED=92=88=20=EA=B4=80?= =?UTF-8?q?=EC=8B=AC=20=EB=93=B1=EB=A1=9D=20=EC=8B=9C=20soft=20delete?= =?UTF-8?q?=EB=90=9C=20=EA=B2=BD=EC=9A=B0=20=EC=B2=98=EB=A6=AC=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../websoso/WSSServer/service/UserNovelService.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/websoso/WSSServer/service/UserNovelService.java b/src/main/java/org/websoso/WSSServer/service/UserNovelService.java index 4741c730..7433ffd0 100644 --- a/src/main/java/org/websoso/WSSServer/service/UserNovelService.java +++ b/src/main/java/org/websoso/WSSServer/service/UserNovelService.java @@ -252,8 +252,14 @@ 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, novel); + 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)); } From c324a53bdf9d64f202bd05a1d6d8ed9189fb827f Mon Sep 17 00:00:00 2001 From: rinarina0429 Date: Mon, 4 Aug 2025 02:10:48 +0900 Subject: [PATCH 5/8] =?UTF-8?q?[FIX]=20UserNovel=20=EA=B4=80=EB=A0=A8=20?= =?UTF-8?q?=EC=BF=BC=EB=A6=AC,=20=ED=95=A8=EC=88=98=20=EB=93=B1=EC=97=90?= =?UTF-8?q?=EC=84=9C=20isDeleted=EA=B0=80=20true=EC=9D=B8=20=EA=B2=83?= =?UTF-8?q?=EB=93=A4=EC=9D=80=20=EC=A0=9C=EC=99=B8=ED=95=98=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/NovelCustomRepositoryImpl.java | 15 ++++++++++----- .../WSSServer/repository/NovelRepository.java | 1 + .../WSSServer/repository/UserNovelRepository.java | 2 +- 3 files changed, 12 insertions(+), 6 deletions(-) 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 cf9f3030..f716fed6 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); From 5f72dc35061a5929c157e905a6f8724757fee302 Mon Sep 17 00:00:00 2001 From: rinarina0429 Date: Wed, 6 Aug 2025 02:13:32 +0900 Subject: [PATCH 6/8] =?UTF-8?q?[FIX]=20soft=20delete=EA=B9=8C=EC=A7=80=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=ED=95=B4=EC=95=BC=20=ED=95=98=EB=8A=94=20?= =?UTF-8?q?=EC=BF=BC=EB=A6=AC=EB=AC=B8=20native=20=EC=BF=BC=EB=A6=AC?= =?UTF-8?q?=EB=AC=B8=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../websoso/WSSServer/repository/UserNovelRepository.java | 4 ++-- .../org/websoso/WSSServer/service/UserNovelService.java | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/websoso/WSSServer/repository/UserNovelRepository.java b/src/main/java/org/websoso/WSSServer/repository/UserNovelRepository.java index f716fed6..561f0175 100644 --- a/src/main/java/org/websoso/WSSServer/repository/UserNovelRepository.java +++ b/src/main/java/org/websoso/WSSServer/repository/UserNovelRepository.java @@ -30,6 +30,6 @@ public interface UserNovelRepository extends JpaRepository, Use Optional findByNovel_NovelIdAndUser(Long novelId, User user); - @Query("SELECT un FROM UserNovel un WHERE un.user = :user AND un.novel = :novel") - Optional findByUserAndNovelIncludeDeleted(User user, Novel novel); + @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/UserNovelService.java b/src/main/java/org/websoso/WSSServer/service/UserNovelService.java index 7433ffd0..e864a28b 100644 --- a/src/main/java/org/websoso/WSSServer/service/UserNovelService.java +++ b/src/main/java/org/websoso/WSSServer/service/UserNovelService.java @@ -99,7 +99,8 @@ 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, novel); + Optional conflictingUserNovel = userNovelRepository.findByUserAndNovelIncludeDeleted( + user.getUserId(), novel.getNovelId()); if (conflictingUserNovel.isPresent()) { UserNovel userNovel = conflictingUserNovel.get(); @@ -252,7 +253,8 @@ public void deleteEvaluation(User user, Long novelId) { } public UserNovel createUserNovelByInterest(User user, Novel novel) { - Optional conflictingUserNovel = userNovelRepository.findByUserAndNovelIncludeDeleted(user, novel); + Optional conflictingUserNovel = userNovelRepository.findByUserAndNovelIncludeDeleted( + user.getUserId(), novel.getNovelId()); if (conflictingUserNovel.isPresent()) { UserNovel userNovel = conflictingUserNovel.get(); if (!userNovel.isDeleted()) { From 018ce437e5c03588febf3569c147831be5a30c25 Mon Sep 17 00:00:00 2001 From: rinarina0429 Date: Wed, 6 Aug 2025 02:56:00 +0900 Subject: [PATCH 7/8] =?UTF-8?q?[FIX]=20UserNovel=20=EB=B3=B5=EA=B5=AC=20?= =?UTF-8?q?=EC=8B=9C=20=EA=B8=B0=EC=A1=B4=20=ED=8F=89=EA=B0=80=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0=EB=90=98=EB=8F=84=EB=A1=9D=20restore()=20=ED=95=A8?= =?UTF-8?q?=EC=88=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/org/websoso/WSSServer/domain/UserNovel.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/org/websoso/WSSServer/domain/UserNovel.java b/src/main/java/org/websoso/WSSServer/domain/UserNovel.java index 04f573db..04941bf2 100644 --- a/src/main/java/org/websoso/WSSServer/domain/UserNovel.java +++ b/src/main/java/org/websoso/WSSServer/domain/UserNovel.java @@ -113,5 +113,6 @@ public void deleteEvaluation() { public void restore() { this.isDeleted = false; + deleteEvaluation(); } } From 1463b7657045980bb5fa08ce4054821902f792f1 Mon Sep 17 00:00:00 2001 From: rinarina0429 Date: Wed, 6 Aug 2025 03:11:50 +0900 Subject: [PATCH 8/8] =?UTF-8?q?[FIX]=20=EC=9E=91=ED=92=88=20=EA=B4=80?= =?UTF-8?q?=EC=8B=AC=20=EB=93=B1=EB=A1=9D=20=EA=B3=BC=EC=A0=95=EC=97=90?= =?UTF-8?q?=EC=84=9C=20soft=20delete=20=EB=90=98=EC=97=88=EB=8D=98=20UserN?= =?UTF-8?q?ovel=20=EC=B2=98=EB=A6=AC=20=EA=B3=BC=EC=A0=95=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../WSSServer/service/NovelService.java | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) 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) {