From f115d7f8fb55651b278906e634c347bd83f57ed5 Mon Sep 17 00:00:00 2001 From: wlsh44 Date: Mon, 10 Nov 2025 09:17:58 +0900 Subject: [PATCH 1/9] =?UTF-8?q?feat:=20=EA=B0=9C=EB=B0=9C=20=EC=B4=88?= =?UTF-8?q?=EA=B8=B0=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/chooz/common/dev/DataInitConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/chooz/common/dev/DataInitConfig.java b/src/main/java/com/chooz/common/dev/DataInitConfig.java index f6418ca2..35e6da5b 100644 --- a/src/main/java/com/chooz/common/dev/DataInitConfig.java +++ b/src/main/java/com/chooz/common/dev/DataInitConfig.java @@ -12,7 +12,7 @@ public class DataInitConfig { private final DataInitializer dataInitializer; - @PostConstruct +// @PostConstruct public void init() { dataInitializer.init(); } From ecf04fce859f61f23d3e6e9fda5500928604d9a5 Mon Sep 17 00:00:00 2001 From: wlsh44 Date: Mon, 10 Nov 2025 09:30:55 +0900 Subject: [PATCH 2/9] =?UTF-8?q?feat:=20thumbnail=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../post/application/PostCommandService.java | 13 +---- .../com/chooz/thumbnail/domain/Thumbnail.java | 47 ------------------- .../thumbnail/domain/ThumbnailRepository.java | 16 ------- 3 files changed, 1 insertion(+), 75 deletions(-) delete mode 100644 src/main/java/com/chooz/thumbnail/domain/Thumbnail.java delete mode 100644 src/main/java/com/chooz/thumbnail/domain/ThumbnailRepository.java diff --git a/src/main/java/com/chooz/post/application/PostCommandService.java b/src/main/java/com/chooz/post/application/PostCommandService.java index 9c0469c0..c100ad13 100644 --- a/src/main/java/com/chooz/post/application/PostCommandService.java +++ b/src/main/java/com/chooz/post/application/PostCommandService.java @@ -6,15 +6,13 @@ import com.chooz.common.exception.ErrorCode; import com.chooz.post.application.dto.PostClosedNotificationEvent; import com.chooz.post.domain.CloseOption; +import com.chooz.post.domain.PollChoice; import com.chooz.post.domain.PollOption; import com.chooz.post.domain.Post; -import com.chooz.post.domain.PollChoice; import com.chooz.post.domain.PostRepository; import com.chooz.post.presentation.dto.CreatePostRequest; import com.chooz.post.presentation.dto.CreatePostResponse; import com.chooz.post.presentation.dto.UpdatePostRequest; -import com.chooz.thumbnail.domain.Thumbnail; -import com.chooz.thumbnail.domain.ThumbnailRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -30,13 +28,11 @@ public class PostCommandService { private final PostRepository postRepository; private final ShareUrlService shareUrlService; - private final ThumbnailRepository thumbnailRepository; private final PostValidator postValidator; private final EventPublisher eventPublisher; public CreatePostResponse create(Long userId, CreatePostRequest request) { Post post = createPost(userId, request); - savePostThumbnail(post); return new CreatePostResponse(post.getId(), post.getShareUrl()); } @@ -73,13 +69,6 @@ private List createPollChoices(CreatePostRequest request) { .collect(Collectors.toList()); } - private void savePostThumbnail(Post post) { - PollChoice thumbnailPollChoice = post.getPollChoices().getFirst(); - thumbnailRepository.save( - Thumbnail.create(post.getId(), thumbnailPollChoice.getId(), thumbnailPollChoice.getImageUrl()) - ); - } - @Transactional public void delete(Long userId, Long postId) { Post post = postRepository.findById(postId) diff --git a/src/main/java/com/chooz/thumbnail/domain/Thumbnail.java b/src/main/java/com/chooz/thumbnail/domain/Thumbnail.java deleted file mode 100644 index 3cb02d28..00000000 --- a/src/main/java/com/chooz/thumbnail/domain/Thumbnail.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.chooz.thumbnail.domain; - -import jakarta.persistence.Entity; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import jakarta.persistence.Table; -import lombok.AccessLevel; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; - -import static com.chooz.common.util.Validator.validateNull; - -@Getter -@Entity -@Table(name = "thumbnails") -@NoArgsConstructor(access = AccessLevel.PROTECTED) -public class Thumbnail { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - private Long postId; - - private Long pollChoiceId; - - private String thumbnailUrl; - - @Builder - public Thumbnail(Long id, Long postId, Long pollChoiceId, String thumbnailUrl) { - validateNull(postId, pollChoiceId, thumbnailUrl); - this.id = id; - this.postId = postId; - this.pollChoiceId = pollChoiceId; - this.thumbnailUrl = thumbnailUrl; - } - - public static Thumbnail create(Long postId, Long pollChoiceId, String thumbnailUrl) { - return new Thumbnail(null, postId, pollChoiceId, thumbnailUrl); - } - - public boolean isThumbnailOf(Long postId) { - return this.postId.equals(postId); - } -} diff --git a/src/main/java/com/chooz/thumbnail/domain/ThumbnailRepository.java b/src/main/java/com/chooz/thumbnail/domain/ThumbnailRepository.java deleted file mode 100644 index b0e59b1d..00000000 --- a/src/main/java/com/chooz/thumbnail/domain/ThumbnailRepository.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.chooz.thumbnail.domain; - -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; - -import java.util.Collection; -import java.util.List; -import java.util.Optional; - -@Repository -public interface ThumbnailRepository extends JpaRepository { - - Optional findByPostId(Long postId); - - List findByPostIdIn(Collection postIds); -} From 0eb4009e3e619c2394f70d0c595831494993c603 Mon Sep 17 00:00:00 2001 From: wlsh44 Date: Mon, 10 Nov 2025 10:13:24 +0900 Subject: [PATCH 3/9] =?UTF-8?q?feat:=20=EA=B2=8C=EC=8B=9C=EA=B8=80=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C=20=EC=8B=9C=20=EC=97=B0=EA=B4=80=20=ED=85=8C?= =?UTF-8?q?=EC=9D=B4=EB=B8=94=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../comment/application/CommentCommandService.java | 5 +++++ .../com/chooz/comment/domain/CommentRepository.java | 1 + .../chooz/post/application/PostCommandService.java | 8 +++++++- .../java/com/chooz/post/domain/PostRepository.java | 4 ++++ .../com/chooz/post/persistence/PostJpaRepository.java | 4 ++++ .../chooz/post/persistence/PostRepositoryImpl.java | 11 +++++++++++ .../java/com/chooz/vote/application/VoteService.java | 4 ++++ .../java/com/chooz/vote/domain/VoteRepository.java | 1 + .../com/chooz/vote/persistence/VoteJpaRepository.java | 3 +-- .../chooz/vote/persistence/VoteRepositoryImpl.java | 5 +++++ 10 files changed, 43 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/chooz/comment/application/CommentCommandService.java b/src/main/java/com/chooz/comment/application/CommentCommandService.java index 3308bcb1..efc8fda2 100644 --- a/src/main/java/com/chooz/comment/application/CommentCommandService.java +++ b/src/main/java/com/chooz/comment/application/CommentCommandService.java @@ -60,4 +60,9 @@ public void deleteComment(Long postId, Long commentId, Long userId) { commentRepository.delete(comment); eventPublisher.publish(DeleteEvent.of(comment.getId(), comment.getClass().getSimpleName().toUpperCase())); } + + public void deleteComments(Long postId) { + commentLikeCommandService.deleteCommentLikeByCommentId(postId); + commentRepository.deleteAllByPostId(postId); + } } diff --git a/src/main/java/com/chooz/comment/domain/CommentRepository.java b/src/main/java/com/chooz/comment/domain/CommentRepository.java index 500edb2e..fb9e645d 100644 --- a/src/main/java/com/chooz/comment/domain/CommentRepository.java +++ b/src/main/java/com/chooz/comment/domain/CommentRepository.java @@ -31,4 +31,5 @@ Slice findByPostId( List findByPostIdAndDeletedFalse(@NotNull Long postId); + void deleteAllByPostId(Long postId); } diff --git a/src/main/java/com/chooz/post/application/PostCommandService.java b/src/main/java/com/chooz/post/application/PostCommandService.java index c100ad13..f9e7b286 100644 --- a/src/main/java/com/chooz/post/application/PostCommandService.java +++ b/src/main/java/com/chooz/post/application/PostCommandService.java @@ -1,5 +1,6 @@ package com.chooz.post.application; +import com.chooz.comment.application.CommentCommandService; import com.chooz.common.event.DeleteEvent; import com.chooz.common.event.EventPublisher; import com.chooz.common.exception.BadRequestException; @@ -13,6 +14,7 @@ import com.chooz.post.presentation.dto.CreatePostRequest; import com.chooz.post.presentation.dto.CreatePostResponse; import com.chooz.post.presentation.dto.UpdatePostRequest; +import com.chooz.vote.application.VoteService; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -29,6 +31,8 @@ public class PostCommandService { private final PostRepository postRepository; private final ShareUrlService shareUrlService; private final PostValidator postValidator; + private final CommentCommandService commentCommandService; + private final VoteService voteService; private final EventPublisher eventPublisher; public CreatePostResponse create(Long userId, CreatePostRequest request) { @@ -73,7 +77,9 @@ private List createPollChoices(CreatePostRequest request) { public void delete(Long userId, Long postId) { Post post = postRepository.findById(postId) .orElseThrow(() -> new BadRequestException(ErrorCode.POST_NOT_FOUND)); - post.delete(userId); + voteService.delete(postId); + commentCommandService.deleteComments(postId); + postRepository.delete(postId); eventPublisher.publish(DeleteEvent.of(post.getId(), post.getClass().getSimpleName().toUpperCase())); } diff --git a/src/main/java/com/chooz/post/domain/PostRepository.java b/src/main/java/com/chooz/post/domain/PostRepository.java index 7b370280..96ff8c5f 100644 --- a/src/main/java/com/chooz/post/domain/PostRepository.java +++ b/src/main/java/com/chooz/post/domain/PostRepository.java @@ -35,4 +35,8 @@ public interface PostRepository { Slice findVotedPostsWithVoteCount(Long userId, Long authorId, Long postId, Pageable pageable); Optional findByIdAndUserId(Long postId, Long userId); + + void deleteAllByUserId(Long userId); + + void delete(Long postId); } diff --git a/src/main/java/com/chooz/post/persistence/PostJpaRepository.java b/src/main/java/com/chooz/post/persistence/PostJpaRepository.java index 0c9f6a48..0377b5c8 100644 --- a/src/main/java/com/chooz/post/persistence/PostJpaRepository.java +++ b/src/main/java/com/chooz/post/persistence/PostJpaRepository.java @@ -72,4 +72,8 @@ public interface PostJpaRepository extends JpaRepository { Optional findCommentActiveByPostId(@Param("postId") Long postId); Optional findByIdAndUserIdAndDeletedFalse(Long postId, Long userId); + + void deleteAllByUserId(Long userId); + + List findAllByUserId(Long userId); } diff --git a/src/main/java/com/chooz/post/persistence/PostRepositoryImpl.java b/src/main/java/com/chooz/post/persistence/PostRepositoryImpl.java index 6432f971..8a3ada88 100644 --- a/src/main/java/com/chooz/post/persistence/PostRepositoryImpl.java +++ b/src/main/java/com/chooz/post/persistence/PostRepositoryImpl.java @@ -19,6 +19,7 @@ public class PostRepositoryImpl implements PostRepository { private final PostJpaRepository postJpaRepository; private final PostQueryDslRepository postQueryDslRepository; + private final PostRepository postRepository; @Override public Optional findById(Long postId) { @@ -79,4 +80,14 @@ public Slice findVotedPostsWithVoteCount(Long userId, Long au public Optional findByIdAndUserId(Long postId, Long userId) { return postJpaRepository.findByIdAndUserIdAndDeletedFalse(postId, userId); } + + @Override + public void deleteAllByUserId(Long userId) { + postJpaRepository.deleteAllByUserId(userId); + } + + @Override + public void delete(Long postId) { + postRepository.delete(postId); + } } diff --git a/src/main/java/com/chooz/vote/application/VoteService.java b/src/main/java/com/chooz/vote/application/VoteService.java index 06fb4984..665a09d9 100644 --- a/src/main/java/com/chooz/vote/application/VoteService.java +++ b/src/main/java/com/chooz/vote/application/VoteService.java @@ -49,4 +49,8 @@ public List findVoteResult(Long userId, Long postId) { return voteResultReader.getVoteResult(totalVoteList, post); } + + public void delete(Long postId) { + voteRepository.deleteAllByPostId(postId); + } } diff --git a/src/main/java/com/chooz/vote/domain/VoteRepository.java b/src/main/java/com/chooz/vote/domain/VoteRepository.java index ecef0f16..bbc2351c 100644 --- a/src/main/java/com/chooz/vote/domain/VoteRepository.java +++ b/src/main/java/com/chooz/vote/domain/VoteRepository.java @@ -14,4 +14,5 @@ public interface VoteRepository { long countVoterByPostId(Long postId); + void deleteAllByPostId(Long postId); } diff --git a/src/main/java/com/chooz/vote/persistence/VoteJpaRepository.java b/src/main/java/com/chooz/vote/persistence/VoteJpaRepository.java index e44a0a1e..857b9861 100644 --- a/src/main/java/com/chooz/vote/persistence/VoteJpaRepository.java +++ b/src/main/java/com/chooz/vote/persistence/VoteJpaRepository.java @@ -2,8 +2,6 @@ import com.chooz.vote.domain.Vote; import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; import java.util.List; @@ -16,4 +14,5 @@ public interface VoteJpaRepository extends JpaRepository { List findByPostIdAndDeletedFalse(Long id); + void deleteAllByPostId(Long postId); } diff --git a/src/main/java/com/chooz/vote/persistence/VoteRepositoryImpl.java b/src/main/java/com/chooz/vote/persistence/VoteRepositoryImpl.java index 90afda0e..2b2c9949 100644 --- a/src/main/java/com/chooz/vote/persistence/VoteRepositoryImpl.java +++ b/src/main/java/com/chooz/vote/persistence/VoteRepositoryImpl.java @@ -38,4 +38,9 @@ public List findByPostIdAndDeletedFalse(Long id) { public long countVoterByPostId(Long postId) { return voteQueryDslRepository.countVoterByPostId(postId); } + + @Override + public void deleteAllByPostId(Long postId) { + voteRepository.deleteAllByPostId(postId); + } } From d89ef828ac1675f5a18515f78a5c6a5676018d64 Mon Sep 17 00:00:00 2001 From: wlsh44 Date: Mon, 10 Nov 2025 10:13:38 +0900 Subject: [PATCH 4/9] =?UTF-8?q?feat:=20=ED=9A=8C=EC=9B=90=20=ED=83=88?= =?UTF-8?q?=ED=87=B4=EC=8B=9C=20=EA=B4=80=EB=A0=A8=20=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=84=B0=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../chooz/auth/application/AuthService.java | 12 ++----- .../auth/application/WithdrawHandler.java | 36 +++++++++++++++++++ .../auth/domain/SocialAccountRepository.java | 2 ++ .../domain/NotificationRepository.java | 2 ++ .../NotificationJpaRepository.java | 2 ++ .../NotificationRepositoryImpl.java | 5 +++ .../post/persistence/PostRepositoryImpl.java | 5 ++- 7 files changed, 51 insertions(+), 13 deletions(-) create mode 100644 src/main/java/com/chooz/auth/application/WithdrawHandler.java diff --git a/src/main/java/com/chooz/auth/application/AuthService.java b/src/main/java/com/chooz/auth/application/AuthService.java index 742e8c7e..8ea8d17c 100644 --- a/src/main/java/com/chooz/auth/application/AuthService.java +++ b/src/main/java/com/chooz/auth/application/AuthService.java @@ -8,12 +8,8 @@ import com.chooz.auth.domain.SocialAccount; import com.chooz.auth.domain.SocialAccountRepository; import com.chooz.auth.presentation.dto.TokenResponse; -import com.chooz.common.exception.BadRequestException; -import com.chooz.common.exception.ErrorCode; import com.chooz.user.application.UserService; import com.chooz.user.domain.Role; -import com.chooz.user.domain.User; -import com.chooz.user.domain.UserRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -29,7 +25,7 @@ public class AuthService { private final OAuthService oAuthService; private final SocialAccountRepository socialAccountRepository; private final UserService userService; - private final UserRepository userRepository; + private final WithdrawHandler withdrawHandler; public TokenResponse oauthSignIn(String code, String redirectUri) { OAuthUserInfo oAuthUserInfo = oAuthService.getUserInfo(code, redirectUri); @@ -62,11 +58,7 @@ public void signOut(Long userId) { jwtService.removeToken(userId); } - @Transactional public void withdraw(Long userId) { - User user = userRepository.findById(userId) - .orElseThrow(() -> new BadRequestException(ErrorCode.USER_NOT_FOUND)); - jwtService.removeToken(userId); - userRepository.delete(user); + withdrawHandler.withdraw(userId); } } diff --git a/src/main/java/com/chooz/auth/application/WithdrawHandler.java b/src/main/java/com/chooz/auth/application/WithdrawHandler.java new file mode 100644 index 00000000..b10deb76 --- /dev/null +++ b/src/main/java/com/chooz/auth/application/WithdrawHandler.java @@ -0,0 +1,36 @@ +package com.chooz.auth.application; + +import com.chooz.auth.application.jwt.JwtService; +import com.chooz.auth.domain.SocialAccountRepository; +import com.chooz.common.exception.BadRequestException; +import com.chooz.common.exception.ErrorCode; +import com.chooz.notification.domain.NotificationRepository; +import com.chooz.post.application.PostCommandService; +import com.chooz.post.persistence.PostJpaRepository; +import com.chooz.user.domain.User; +import com.chooz.user.domain.UserRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class WithdrawHandler { + + private final UserRepository userRepository; + private final JwtService jwtService; + private final SocialAccountRepository socialAccountRepository; + private final PostJpaRepository postRepository; + private final NotificationRepository notificationRepository; + private final PostCommandService postCommandService; + + public void withdraw(Long userId) { + User user = userRepository.findById(userId) + .orElseThrow(() -> new BadRequestException(ErrorCode.USER_NOT_FOUND)); + jwtService.removeToken(userId); + socialAccountRepository.deleteByUserId(userId); + notificationRepository.deleteAllByUserId(userId); + postRepository.findAllByUserId(userId) + .forEach(post -> postCommandService.delete(userId, post.getId())); + userRepository.delete(user); + } +} diff --git a/src/main/java/com/chooz/auth/domain/SocialAccountRepository.java b/src/main/java/com/chooz/auth/domain/SocialAccountRepository.java index 10f763f7..6094e387 100644 --- a/src/main/java/com/chooz/auth/domain/SocialAccountRepository.java +++ b/src/main/java/com/chooz/auth/domain/SocialAccountRepository.java @@ -9,4 +9,6 @@ public interface SocialAccountRepository extends JpaRepository { Optional findBySocialIdAndProvider(String socialId, Provider provider); + + void deleteByUserId(Long userId); } diff --git a/src/main/java/com/chooz/notification/domain/NotificationRepository.java b/src/main/java/com/chooz/notification/domain/NotificationRepository.java index 968ca92d..c99413cd 100644 --- a/src/main/java/com/chooz/notification/domain/NotificationRepository.java +++ b/src/main/java/com/chooz/notification/domain/NotificationRepository.java @@ -9,4 +9,6 @@ public interface NotificationRepository { Optional findNotificationById(Long id); boolean existsByReceiverIdAndIsReadFalseAndDeletedFalse(Long userId); List findByTargetIdAndType(Long targetId, TargetType targetType); + + void deleteAllByUserId(Long userId); } diff --git a/src/main/java/com/chooz/notification/persistence/NotificationJpaRepository.java b/src/main/java/com/chooz/notification/persistence/NotificationJpaRepository.java index 42d24a75..c6d0ee7f 100644 --- a/src/main/java/com/chooz/notification/persistence/NotificationJpaRepository.java +++ b/src/main/java/com/chooz/notification/persistence/NotificationJpaRepository.java @@ -23,4 +23,6 @@ public interface NotificationJpaRepository extends JpaRepository findByTargetIdAndType(@Param("targetId") Long targetId, @Param("targetType") TargetType targetType); + + void deleteAllByReceiverId(Long receiverId); } diff --git a/src/main/java/com/chooz/notification/persistence/NotificationRepositoryImpl.java b/src/main/java/com/chooz/notification/persistence/NotificationRepositoryImpl.java index 9fcbbffc..906ba747 100644 --- a/src/main/java/com/chooz/notification/persistence/NotificationRepositoryImpl.java +++ b/src/main/java/com/chooz/notification/persistence/NotificationRepositoryImpl.java @@ -40,4 +40,9 @@ public boolean existsByReceiverIdAndIsReadFalseAndDeletedFalse(Long userId) { public List findByTargetIdAndType(Long targetId, TargetType targetType) { return notificationJpaRepository.findByTargetIdAndType(targetId, targetType); } + + @Override + public void deleteAllByUserId(Long userId) { + notificationJpaRepository.deleteAllByReceiverId(userId); + } } \ No newline at end of file diff --git a/src/main/java/com/chooz/post/persistence/PostRepositoryImpl.java b/src/main/java/com/chooz/post/persistence/PostRepositoryImpl.java index 8a3ada88..b58c31e3 100644 --- a/src/main/java/com/chooz/post/persistence/PostRepositoryImpl.java +++ b/src/main/java/com/chooz/post/persistence/PostRepositoryImpl.java @@ -1,10 +1,10 @@ package com.chooz.post.persistence; +import com.chooz.post.application.dto.FeedDto; import com.chooz.post.application.dto.PostWithVoteCount; import com.chooz.post.domain.CommentActive; import com.chooz.post.domain.Post; import com.chooz.post.domain.PostRepository; -import com.chooz.post.application.dto.FeedDto; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; @@ -19,7 +19,6 @@ public class PostRepositoryImpl implements PostRepository { private final PostJpaRepository postJpaRepository; private final PostQueryDslRepository postQueryDslRepository; - private final PostRepository postRepository; @Override public Optional findById(Long postId) { @@ -88,6 +87,6 @@ public void deleteAllByUserId(Long userId) { @Override public void delete(Long postId) { - postRepository.delete(postId); + postJpaRepository.deleteById(postId); } } From 1b41d4bad320c6ec76970d80cf96f1e7741787b9 Mon Sep 17 00:00:00 2001 From: wlsh44 Date: Mon, 10 Nov 2025 10:36:00 +0900 Subject: [PATCH 5/9] =?UTF-8?q?feat:=20s3=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/chooz/image/application/ImageService.java | 4 ++++ .../com/chooz/image/application/S3Client.java | 3 ++- .../chooz/image/infrastructure/AwsS3Client.java | 15 ++++++++++++++- .../post/application/PostCommandService.java | 3 +++ 4 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/chooz/image/application/ImageService.java b/src/main/java/com/chooz/image/application/ImageService.java index a0e156b5..b246d3c1 100644 --- a/src/main/java/com/chooz/image/application/ImageService.java +++ b/src/main/java/com/chooz/image/application/ImageService.java @@ -36,4 +36,8 @@ private String getSignedGetUrl(String filePath) { URI domain = URI.create(imageProperties.endpoint()); return domain.resolve(filePath).toString(); } + + public void deleteImage(String assetUrl) { + s3Client.deleteImage(assetUrl); + } } diff --git a/src/main/java/com/chooz/image/application/S3Client.java b/src/main/java/com/chooz/image/application/S3Client.java index dcfd33be..70db5dc0 100644 --- a/src/main/java/com/chooz/image/application/S3Client.java +++ b/src/main/java/com/chooz/image/application/S3Client.java @@ -1,8 +1,9 @@ package com.chooz.image.application; import com.chooz.image.application.dto.PresignedUrlRequestDto; -import com.chooz.image.presentation.dto.PresignedUrlRequest; public interface S3Client { String getPresignedPutUrl(PresignedUrlRequestDto presignedUrlRequestDto); + + void deleteImage(String assetUrl); } diff --git a/src/main/java/com/chooz/image/infrastructure/AwsS3Client.java b/src/main/java/com/chooz/image/infrastructure/AwsS3Client.java index fa252094..0b74deaf 100644 --- a/src/main/java/com/chooz/image/infrastructure/AwsS3Client.java +++ b/src/main/java/com/chooz/image/infrastructure/AwsS3Client.java @@ -4,6 +4,7 @@ import com.chooz.image.application.dto.PresignedUrlRequestDto; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; +import software.amazon.awssdk.services.s3.model.DeleteObjectRequest; import software.amazon.awssdk.services.s3.model.PutObjectRequest; import software.amazon.awssdk.services.s3.presigner.S3Presigner; import software.amazon.awssdk.services.s3.presigner.model.PutObjectPresignRequest; @@ -17,13 +18,16 @@ public class AwsS3Client implements S3Client { private final String bucket; private final S3Presigner s3Presigner; + private final software.amazon.awssdk.services.s3.S3Client s3Client; public AwsS3Client( @Value("${spring.cloud.aws.s3.bucket}") String bucket, - S3Presigner s3Presigner + S3Presigner s3Presigner, + software.amazon.awssdk.services.s3.S3Client s3Client ) { this.bucket = bucket; this.s3Presigner = s3Presigner; + this.s3Client = s3Client; } @Override @@ -45,4 +49,13 @@ private PutObjectPresignRequest buildPresignedRequest(PresignedUrlRequestDto dto .putObjectRequest(requestBuilder.build()) .build(); } + + @Override + public void deleteImage(String assetUrl) { + DeleteObjectRequest deleteObjectRequest = DeleteObjectRequest.builder() + .bucket(bucket) + .key(assetUrl) + .build(); + s3Client.deleteObject(deleteObjectRequest); + } } diff --git a/src/main/java/com/chooz/post/application/PostCommandService.java b/src/main/java/com/chooz/post/application/PostCommandService.java index f9e7b286..b00232cf 100644 --- a/src/main/java/com/chooz/post/application/PostCommandService.java +++ b/src/main/java/com/chooz/post/application/PostCommandService.java @@ -5,6 +5,7 @@ import com.chooz.common.event.EventPublisher; import com.chooz.common.exception.BadRequestException; import com.chooz.common.exception.ErrorCode; +import com.chooz.image.application.ImageService; import com.chooz.post.application.dto.PostClosedNotificationEvent; import com.chooz.post.domain.CloseOption; import com.chooz.post.domain.PollChoice; @@ -34,6 +35,7 @@ public class PostCommandService { private final CommentCommandService commentCommandService; private final VoteService voteService; private final EventPublisher eventPublisher; + private final ImageService imageService; public CreatePostResponse create(Long userId, CreatePostRequest request) { Post post = createPost(userId, request); @@ -79,6 +81,7 @@ public void delete(Long userId, Long postId) { .orElseThrow(() -> new BadRequestException(ErrorCode.POST_NOT_FOUND)); voteService.delete(postId); commentCommandService.deleteComments(postId); + imageService.deleteImage(post.getImageUrl()); postRepository.delete(postId); eventPublisher.publish(DeleteEvent.of(post.getId(), post.getClass().getSimpleName().toUpperCase())); } From f168bbb6969c851ab3de9d480f4a343bc259c82d Mon Sep 17 00:00:00 2001 From: wlsh44 Date: Mon, 10 Nov 2025 11:24:09 +0900 Subject: [PATCH 6/9] =?UTF-8?q?chore:=20=EC=84=A4=EC=A0=95=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20=EA=B0=B1=EC=8B=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server-config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server-config b/server-config index ffce8ef1..691d936b 160000 --- a/server-config +++ b/server-config @@ -1 +1 @@ -Subproject commit ffce8ef11e0fd7453570cb63fa0ada64d46b2c0d +Subproject commit 691d936b31e68ae7516be47904988de069070a51 From d53742991b56f07ab397dc2d95795aa152239ac2 Mon Sep 17 00:00:00 2001 From: wlsh44 Date: Mon, 10 Nov 2025 11:23:31 +0900 Subject: [PATCH 7/9] =?UTF-8?q?feat:=20=EC=B9=B4=EC=B9=B4=EC=98=A4=20?= =?UTF-8?q?=EC=86=8C=EC=85=9C=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=ED=9A=8C?= =?UTF-8?q?=EC=9B=90=20=ED=83=88=ED=87=B4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/application/WithdrawHandler.java | 9 ++++++++- .../application/oauth/KakaoOAuthClient.java | 7 +++++++ .../auth/application/oauth/OAuthService.java | 19 +++++++++++++++++++ .../oauth/dto/KakaoUnlinkResponse.java | 4 ++++ .../auth/domain/SocialAccountRepository.java | 3 +++ .../chooz/common/config/KakaoOAuthConfig.java | 1 + 6 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/chooz/auth/application/oauth/dto/KakaoUnlinkResponse.java diff --git a/src/main/java/com/chooz/auth/application/WithdrawHandler.java b/src/main/java/com/chooz/auth/application/WithdrawHandler.java index b10deb76..d8968626 100644 --- a/src/main/java/com/chooz/auth/application/WithdrawHandler.java +++ b/src/main/java/com/chooz/auth/application/WithdrawHandler.java @@ -1,6 +1,8 @@ package com.chooz.auth.application; import com.chooz.auth.application.jwt.JwtService; +import com.chooz.auth.application.oauth.OAuthService; +import com.chooz.auth.domain.SocialAccount; import com.chooz.auth.domain.SocialAccountRepository; import com.chooz.common.exception.BadRequestException; import com.chooz.common.exception.ErrorCode; @@ -22,15 +24,20 @@ public class WithdrawHandler { private final PostJpaRepository postRepository; private final NotificationRepository notificationRepository; private final PostCommandService postCommandService; + private final OAuthService oAuthService; public void withdraw(Long userId) { User user = userRepository.findById(userId) .orElseThrow(() -> new BadRequestException(ErrorCode.USER_NOT_FOUND)); jwtService.removeToken(userId); - socialAccountRepository.deleteByUserId(userId); notificationRepository.deleteAllByUserId(userId); postRepository.findAllByUserId(userId) .forEach(post -> postCommandService.delete(userId, post.getId())); + + socialAccountRepository.findByUserId(userId).ifPresent(socialAccount -> { + socialAccountRepository.deleteByUserId(userId); + oAuthService.withdraw(socialAccount.getSocialId()); + }); userRepository.delete(user); } } diff --git a/src/main/java/com/chooz/auth/application/oauth/KakaoOAuthClient.java b/src/main/java/com/chooz/auth/application/oauth/KakaoOAuthClient.java index 990e2f7a..07fbd97f 100644 --- a/src/main/java/com/chooz/auth/application/oauth/KakaoOAuthClient.java +++ b/src/main/java/com/chooz/auth/application/oauth/KakaoOAuthClient.java @@ -1,6 +1,7 @@ package com.chooz.auth.application.oauth; import com.chooz.auth.application.oauth.dto.KakaoAuthResponse; +import com.chooz.auth.application.oauth.dto.KakaoUnlinkResponse; import com.chooz.auth.application.oauth.dto.KakaoUserInfoResponse; import org.springframework.util.MultiValueMap; import org.springframework.web.bind.annotation.RequestHeader; @@ -18,4 +19,10 @@ public interface KakaoOAuthClient { @GetExchange("https://kapi.kakao.com/v2/user/me") KakaoUserInfoResponse fetchUserInfo(@RequestHeader(name = AUTHORIZATION) String bearerToken); + + @PostExchange(url = "https://kapi.kakao.com/v1/user/unlink", contentType = APPLICATION_FORM_URLENCODED_VALUE) + KakaoUnlinkResponse unlink( + @RequestHeader(name = AUTHORIZATION) String bearerToken, + @RequestParam("params") MultiValueMap params + ); } diff --git a/src/main/java/com/chooz/auth/application/oauth/OAuthService.java b/src/main/java/com/chooz/auth/application/oauth/OAuthService.java index a76d24b2..b2bc6b28 100644 --- a/src/main/java/com/chooz/auth/application/oauth/OAuthService.java +++ b/src/main/java/com/chooz/auth/application/oauth/OAuthService.java @@ -17,6 +17,7 @@ public class OAuthService { private static final String BEARER = "Bearer "; + private static final String KAKAO_AK = "KakaoAK "; private final KakaoOAuthConfig kakaoOAuthConfig; private final KakaoOAuthClient kakaoOAuthClient; @@ -43,4 +44,22 @@ private MultiValueMap tokenRequestParams(String authCode, String params.add("client_secret", kakaoOAuthConfig.clientSecret()); return params; } + + public void withdraw(String socialId) { + try { + kakaoOAuthClient.unlink( + KAKAO_AK + kakaoOAuthConfig.adminKey(), + unlinkRequestParams(socialId) + ); + } catch (Exception e) { + log.warn("회원 탈퇴 실패", e); + } + } + + private MultiValueMap unlinkRequestParams(String socialId) { + MultiValueMap params = new LinkedMultiValueMap<>(); + params.add("target_id_type", "user_id"); + params.add("target_id", socialId); + return params; + } } diff --git a/src/main/java/com/chooz/auth/application/oauth/dto/KakaoUnlinkResponse.java b/src/main/java/com/chooz/auth/application/oauth/dto/KakaoUnlinkResponse.java new file mode 100644 index 00000000..e11a3afa --- /dev/null +++ b/src/main/java/com/chooz/auth/application/oauth/dto/KakaoUnlinkResponse.java @@ -0,0 +1,4 @@ +package com.chooz.auth.application.oauth.dto; + +public record KakaoUnlinkResponse(String id) { +} diff --git a/src/main/java/com/chooz/auth/domain/SocialAccountRepository.java b/src/main/java/com/chooz/auth/domain/SocialAccountRepository.java index 6094e387..1311dc76 100644 --- a/src/main/java/com/chooz/auth/domain/SocialAccountRepository.java +++ b/src/main/java/com/chooz/auth/domain/SocialAccountRepository.java @@ -3,6 +3,7 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; +import java.util.List; import java.util.Optional; @Repository @@ -11,4 +12,6 @@ public interface SocialAccountRepository extends JpaRepository findBySocialIdAndProvider(String socialId, Provider provider); void deleteByUserId(Long userId); + + Optional findByUserId(Long userId); } diff --git a/src/main/java/com/chooz/common/config/KakaoOAuthConfig.java b/src/main/java/com/chooz/common/config/KakaoOAuthConfig.java index 3b213aca..b06d13bb 100644 --- a/src/main/java/com/chooz/common/config/KakaoOAuthConfig.java +++ b/src/main/java/com/chooz/common/config/KakaoOAuthConfig.java @@ -7,6 +7,7 @@ public record KakaoOAuthConfig( String authorizationUri, String clientId, String clientSecret, + String adminKey, String[] scope, String userInfoUri ) { From 477501effa78743a7d5e7cd631aab2c206c61620 Mon Sep 17 00:00:00 2001 From: wlsh44 Date: Mon, 10 Nov 2025 11:34:19 +0900 Subject: [PATCH 8/9] =?UTF-8?q?test:=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=A0=95=EB=A6=AC=20=EB=B0=8F=20s3=20mock?= =?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 --- .../application/PostCommandServiceTest.java | 12 +----------- .../post/application/PostQueryServiceTest.java | 5 ----- .../com/chooz/support/IntegrationTest.java | 17 +++++++++++++++++ .../support/fixture/ThumbnailFixture.java | 18 ------------------ .../java/com/chooz/support/mock/AwsS3Mock.java | 17 +++++++++++++++++ 5 files changed, 35 insertions(+), 34 deletions(-) delete mode 100644 src/test/java/com/chooz/support/fixture/ThumbnailFixture.java create mode 100644 src/test/java/com/chooz/support/mock/AwsS3Mock.java diff --git a/src/test/java/com/chooz/post/application/PostCommandServiceTest.java b/src/test/java/com/chooz/post/application/PostCommandServiceTest.java index 620c259b..fec7d522 100644 --- a/src/test/java/com/chooz/post/application/PostCommandServiceTest.java +++ b/src/test/java/com/chooz/post/application/PostCommandServiceTest.java @@ -13,8 +13,6 @@ import com.chooz.support.fixture.PostFixture; import com.chooz.support.fixture.UserFixture; import com.chooz.support.fixture.VoteFixture; -import com.chooz.thumbnail.domain.Thumbnail; -import com.chooz.thumbnail.domain.ThumbnailRepository; import com.chooz.user.domain.User; import com.chooz.user.domain.UserRepository; import com.chooz.vote.domain.VoteRepository; @@ -46,9 +44,6 @@ public class PostCommandServiceTest extends IntegrationTest { @MockitoBean ShareUrlService shareUrlService; - @Autowired - ThumbnailRepository thumbnailRepository; - @Autowired VoteRepository voteRepository; @@ -76,7 +71,6 @@ void create() throws Exception { //then Post post = postRepository.findById(response.postId()).get(); - Thumbnail thumbnail = thumbnailRepository.findByPostId(post.getId()).get(); List pollChoices = post.getPollChoices(); assertAll( () -> assertThat(post.getDescription()).isEqualTo("description"), @@ -89,11 +83,7 @@ void create() throws Exception { () -> assertThat(pollChoices.get(0).getImageUrl()).isEqualTo("http://image1.com"), () -> assertThat(pollChoices.get(0).getTitle()).isEqualTo("title1"), () -> assertThat(pollChoices.get(1).getImageUrl()).isEqualTo("http://image2.com"), - () -> assertThat(pollChoices.get(1).getTitle()).isEqualTo("title2"), - - () -> assertThat(thumbnail.getThumbnailUrl()).isEqualTo("http://image1.com"), - () -> assertThat(thumbnail.getPostId()).isEqualTo(post.getId()), - () -> assertThat(thumbnail.getPollChoiceId()).isEqualTo(pollChoices.get(0).getId()) + () -> assertThat(pollChoices.get(1).getTitle()).isEqualTo("title2") ); } diff --git a/src/test/java/com/chooz/post/application/PostQueryServiceTest.java b/src/test/java/com/chooz/post/application/PostQueryServiceTest.java index f6791a5e..b189b3a5 100644 --- a/src/test/java/com/chooz/post/application/PostQueryServiceTest.java +++ b/src/test/java/com/chooz/post/application/PostQueryServiceTest.java @@ -19,7 +19,6 @@ import com.chooz.support.fixture.PostFixture; import com.chooz.support.fixture.UserFixture; import com.chooz.support.fixture.VoteFixture; -import com.chooz.thumbnail.domain.ThumbnailRepository; import com.chooz.user.domain.User; import com.chooz.user.domain.UserRepository; import com.chooz.vote.application.VoteService; @@ -35,7 +34,6 @@ import static com.chooz.support.fixture.CommentFixture.createDefaultComment; import static com.chooz.support.fixture.PostFixture.createDefaultPost; import static com.chooz.support.fixture.PostFixture.createPostBuilder; -import static com.chooz.support.fixture.ThumbnailFixture.createDefaultThumbnail; import static com.chooz.support.fixture.UserFixture.createDefaultUser; import static com.chooz.support.fixture.UserFixture.createUserBuilder; import static com.chooz.support.fixture.VoteFixture.createDefaultVote; @@ -61,8 +59,6 @@ class PostQueryServiceTest extends IntegrationTest { @Autowired CommentRepository commentRepository; - @Autowired - ThumbnailRepository thumbnailRepository; @Autowired private VoteService voteService; @@ -476,7 +472,6 @@ private List createPosts(User user, int size) { for (int i = 0; i < size; i++) { Post post = postRepository.save(createDefaultPost(user.getId())); posts.add(post); - thumbnailRepository.save(createDefaultThumbnail(post.getId(), post.getPollChoices().get(0).getId())); } return posts; } diff --git a/src/test/java/com/chooz/support/IntegrationTest.java b/src/test/java/com/chooz/support/IntegrationTest.java index f20a8cbf..db1ffae4 100644 --- a/src/test/java/com/chooz/support/IntegrationTest.java +++ b/src/test/java/com/chooz/support/IntegrationTest.java @@ -1,11 +1,28 @@ package com.chooz.support; +import com.chooz.image.application.S3Client; +import com.chooz.support.mock.AwsS3Mock; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.context.annotation.Primary; import org.springframework.test.context.ActiveProfiles; import org.springframework.transaction.annotation.Transactional; +@Import(IntegrationTest.IntegrationTestConfig.class) @ActiveProfiles("test") @Transactional @SpringBootTest public abstract class IntegrationTest { + + @Configuration + public static class IntegrationTestConfig { + + @Bean + @Primary + public S3Client s3ClientMock() { + return new AwsS3Mock(); + } + } } diff --git a/src/test/java/com/chooz/support/fixture/ThumbnailFixture.java b/src/test/java/com/chooz/support/fixture/ThumbnailFixture.java deleted file mode 100644 index 30b33c75..00000000 --- a/src/test/java/com/chooz/support/fixture/ThumbnailFixture.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.chooz.support.fixture; - -import com.chooz.thumbnail.domain.Thumbnail; -import com.chooz.vote.domain.Vote; - -public class ThumbnailFixture { - - public static Thumbnail createDefaultThumbnail(Long postId, Long pollChoiceId) { - return Thumbnail.create(postId, pollChoiceId, "http://example.com/image"); - } - - public static Thumbnail.ThumbnailBuilder createThumbnailBuilder() { - return Thumbnail.builder() - .postId(1L) - .pollChoiceId(1L) - .thumbnailUrl("http://example.com/image"); - } -} diff --git a/src/test/java/com/chooz/support/mock/AwsS3Mock.java b/src/test/java/com/chooz/support/mock/AwsS3Mock.java new file mode 100644 index 00000000..250ead49 --- /dev/null +++ b/src/test/java/com/chooz/support/mock/AwsS3Mock.java @@ -0,0 +1,17 @@ +package com.chooz.support.mock; + +import com.chooz.image.application.S3Client; +import com.chooz.image.application.dto.PresignedUrlRequestDto; + +public class AwsS3Mock implements S3Client { + + @Override + public String getPresignedPutUrl(PresignedUrlRequestDto presignedUrlRequestDto) { + return ""; + } + + @Override + public void deleteImage(String assetUrl) { + + } +} From f5fa570353152de9fb19eda0febb235afa27da67 Mon Sep 17 00:00:00 2001 From: wlsh44 Date: Mon, 10 Nov 2025 14:31:07 +0900 Subject: [PATCH 9/9] =?UTF-8?q?feat:=20=EC=9C=A0=EC=A0=80=20=ED=94=84?= =?UTF-8?q?=EB=A1=9C=ED=95=84=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/chooz/auth/application/WithdrawHandler.java | 5 +++++ src/main/java/com/chooz/user/domain/User.java | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/chooz/auth/application/WithdrawHandler.java b/src/main/java/com/chooz/auth/application/WithdrawHandler.java index d8968626..4f959d67 100644 --- a/src/main/java/com/chooz/auth/application/WithdrawHandler.java +++ b/src/main/java/com/chooz/auth/application/WithdrawHandler.java @@ -6,6 +6,7 @@ import com.chooz.auth.domain.SocialAccountRepository; import com.chooz.common.exception.BadRequestException; import com.chooz.common.exception.ErrorCode; +import com.chooz.image.application.S3Client; import com.chooz.notification.domain.NotificationRepository; import com.chooz.post.application.PostCommandService; import com.chooz.post.persistence.PostJpaRepository; @@ -25,6 +26,7 @@ public class WithdrawHandler { private final NotificationRepository notificationRepository; private final PostCommandService postCommandService; private final OAuthService oAuthService; + private final S3Client s3Client; public void withdraw(Long userId) { User user = userRepository.findById(userId) @@ -38,6 +40,9 @@ public void withdraw(Long userId) { socialAccountRepository.deleteByUserId(userId); oAuthService.withdraw(socialAccount.getSocialId()); }); + if (!User.DEFAULT_PROFILE_URL.equals(user.getProfileUrl())) { + s3Client.deleteImage(user.getProfileUrl()); + } userRepository.delete(user); } } diff --git a/src/main/java/com/chooz/user/domain/User.java b/src/main/java/com/chooz/user/domain/User.java index 5b75fae9..2295bea0 100644 --- a/src/main/java/com/chooz/user/domain/User.java +++ b/src/main/java/com/chooz/user/domain/User.java @@ -25,7 +25,7 @@ @NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) public class User extends BaseEntity { - private static final String DEFAULT_PROFILE_URL = "https://cdn.chooz.site/default_profile.png"; + public static final String DEFAULT_PROFILE_URL = "https://cdn.chooz.site/default_profile.png"; @Id @GeneratedValue(strategy = GenerationType.IDENTITY)