From 54a18cb2f59733c3fde7a164ee215add80da56b9 Mon Sep 17 00:00:00 2001 From: haiseong Date: Fri, 16 Aug 2024 14:06:00 +0900 Subject: [PATCH] :sparkles: user delete --- .../recipe/repository/RecipeRepository.java | 8 +++ .../user/controller/UserController.java | 7 ++ .../user/repository/UserBlockRepository.java | 4 ++ .../user/repository/UserReportRepository.java | 4 ++ .../pengcook/user/service/UserService.java | 28 ++++++++ .../user/controller/UserControllerTest.java | 24 +++++++ .../user/service/UserServiceTest.java | 64 +++++++++++++++++-- backend/src/test/resources/data/users.sql | 33 +++++++++- 8 files changed, 165 insertions(+), 7 deletions(-) diff --git a/backend/src/main/java/net/pengcook/recipe/repository/RecipeRepository.java b/backend/src/main/java/net/pengcook/recipe/repository/RecipeRepository.java index f5029904..113f8b93 100644 --- a/backend/src/main/java/net/pengcook/recipe/repository/RecipeRepository.java +++ b/backend/src/main/java/net/pengcook/recipe/repository/RecipeRepository.java @@ -59,6 +59,14 @@ List findRecipeIdsByCategoryAndKeyword( int countByAuthorId(long userId); + @Query(""" + SELECT r.id + FROM Recipe r + WHERE r.author.id = :userId + ORDER BY r.id DESC + """) + List findRecipeIdsByUserId(long userId); + @Query(""" SELECT r.id FROM Recipe r diff --git a/backend/src/main/java/net/pengcook/user/controller/UserController.java b/backend/src/main/java/net/pengcook/user/controller/UserController.java index 0ca38479..7404c479 100644 --- a/backend/src/main/java/net/pengcook/user/controller/UserController.java +++ b/backend/src/main/java/net/pengcook/user/controller/UserController.java @@ -15,6 +15,7 @@ import net.pengcook.user.dto.UsernameCheckResponse; import net.pengcook.user.service.UserService; import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -48,6 +49,12 @@ public UpdateProfileResponse updateUserProfile( return userService.updateProfile(userInfo.getId(), updateProfileRequest); } + @DeleteMapping("/user/me") + @ResponseStatus(HttpStatus.NO_CONTENT) + public void deleteUser(@LoginUser UserInfo userInfo) { + userService.deleteUser(userInfo); + } + @GetMapping("/user/username/check") public UsernameCheckResponse checkUsername(@RequestParam @NotBlank String username) { return userService.checkUsername(username); diff --git a/backend/src/main/java/net/pengcook/user/repository/UserBlockRepository.java b/backend/src/main/java/net/pengcook/user/repository/UserBlockRepository.java index 475c32e8..c9826833 100644 --- a/backend/src/main/java/net/pengcook/user/repository/UserBlockRepository.java +++ b/backend/src/main/java/net/pengcook/user/repository/UserBlockRepository.java @@ -8,4 +8,8 @@ @Repository public interface UserBlockRepository extends JpaRepository { List findAllByBlockerId(long id); + + void deleteByBlockeeId(long userId); + + void deleteByBlockerId(long userId); } diff --git a/backend/src/main/java/net/pengcook/user/repository/UserReportRepository.java b/backend/src/main/java/net/pengcook/user/repository/UserReportRepository.java index a8bca94d..b6cda736 100644 --- a/backend/src/main/java/net/pengcook/user/repository/UserReportRepository.java +++ b/backend/src/main/java/net/pengcook/user/repository/UserReportRepository.java @@ -4,4 +4,8 @@ import org.springframework.data.jpa.repository.JpaRepository; public interface UserReportRepository extends JpaRepository { + + void deleteByReporteeId(long userId); + + void deleteByReporterId(long userId); } diff --git a/backend/src/main/java/net/pengcook/user/service/UserService.java b/backend/src/main/java/net/pengcook/user/service/UserService.java index 06b6b017..b888f9a3 100644 --- a/backend/src/main/java/net/pengcook/user/service/UserService.java +++ b/backend/src/main/java/net/pengcook/user/service/UserService.java @@ -1,9 +1,14 @@ package net.pengcook.user.service; import jakarta.transaction.Transactional; +import java.util.List; import java.util.stream.Collectors; import lombok.AllArgsConstructor; +import net.pengcook.authentication.domain.UserInfo; +import net.pengcook.comment.repository.CommentRepository; +import net.pengcook.like.repository.RecipeLikeRepository; import net.pengcook.recipe.repository.RecipeRepository; +import net.pengcook.recipe.service.RecipeService; import net.pengcook.user.domain.BlockedUserGroup; import net.pengcook.user.domain.User; import net.pengcook.user.domain.UserBlock; @@ -27,8 +32,12 @@ @AllArgsConstructor public class UserService { + private final RecipeService recipeService; + private final UserRepository userRepository; private final RecipeRepository recipeRepository; + private final CommentRepository commentRepository; + private final RecipeLikeRepository recipeLikeRepository; private final UserBlockRepository userBlockRepository; private final UserReportRepository userReportRepository; @@ -91,4 +100,23 @@ public BlockedUserGroup getBlockedUserGroup(long blockerId) { .map(UserBlock::getBlockee) .collect(Collectors.collectingAndThen(Collectors.toSet(), BlockedUserGroup::new)); } + + @Transactional + public void deleteUser(UserInfo userInfo) { + User user = userRepository.findById(userInfo.getId()) + .orElseThrow(() -> new NotFoundException("사용자를 찾을 수 없습니다.")); + + commentRepository.deleteByUserId(userInfo.getId()); + recipeLikeRepository.deleteByUserId(userInfo.getId()); + userBlockRepository.deleteByBlockerId(userInfo.getId()); + userBlockRepository.deleteByBlockeeId(userInfo.getId()); + userReportRepository.deleteByReporterId(userInfo.getId()); + userReportRepository.deleteByReporteeId(userInfo.getId()); + List userRecipes = recipeRepository.findRecipeIdsByUserId(userInfo.getId()); + for (Long recipeId : userRecipes) { + recipeService.deleteRecipe(userInfo, recipeId); + } + + userRepository.delete(user); + } } diff --git a/backend/src/test/java/net/pengcook/user/controller/UserControllerTest.java b/backend/src/test/java/net/pengcook/user/controller/UserControllerTest.java index 31e2d03e..b147e627 100644 --- a/backend/src/test/java/net/pengcook/user/controller/UserControllerTest.java +++ b/backend/src/test/java/net/pengcook/user/controller/UserControllerTest.java @@ -19,14 +19,19 @@ import net.pengcook.user.dto.UpdateProfileResponse; import net.pengcook.user.dto.UserBlockRequest; import net.pengcook.user.dto.UserReportRequest; +import net.pengcook.user.repository.UserRepository; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.jdbc.Sql; @WithLoginUserTest @Sql("/data/users.sql") class UserControllerTest extends RestDocsSetting { + @Autowired + UserRepository userRepository; + @Test @WithLoginUser(email = "loki@pengcook.net") @DisplayName("로그인된 사용자의 정보를 조회한다.") @@ -286,4 +291,23 @@ void blockUser() { .body("blocker.id", is(1)) .body("blockee.id", is(2)); } + + @Test + @WithLoginUser(email = "loki@pengcook.net") + @DisplayName("사용자를 삭제한다.") + void deleteUser() { + RestAssured.given(spec).log().all() + .filter(document(DEFAULT_RESTDOCS_PATH, + "사용자를 삭제합니다.", + "사용자 삭제 API" + )) + .contentType(ContentType.JSON) + .when().delete("/user/me") + .then().log().all() + .statusCode(204); + + boolean exists = userRepository.existsByEmail("loki@pengcook.net"); + + assertThat(exists).isFalse(); + } } diff --git a/backend/src/test/java/net/pengcook/user/service/UserServiceTest.java b/backend/src/test/java/net/pengcook/user/service/UserServiceTest.java index d1769435..9d6c3fc5 100644 --- a/backend/src/test/java/net/pengcook/user/service/UserServiceTest.java +++ b/backend/src/test/java/net/pengcook/user/service/UserServiceTest.java @@ -4,6 +4,10 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertAll; +import net.pengcook.authentication.domain.UserInfo; +import net.pengcook.comment.repository.CommentRepository; +import net.pengcook.like.repository.RecipeLikeRepository; +import net.pengcook.recipe.repository.RecipeRepository; import net.pengcook.user.domain.BlockedUserGroup; import net.pengcook.user.domain.UserReport; import net.pengcook.user.dto.ProfileResponse; @@ -19,20 +23,22 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; -import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; -import org.springframework.context.annotation.Import; +import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.jdbc.Sql; -@DataJpaTest -@Import(UserService.class) -@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @Sql(scripts = "/data/users.sql") class UserServiceTest { @Autowired UserRepository userRepository; @Autowired + RecipeRepository recipeRepository; + @Autowired + CommentRepository commentRepository; + @Autowired + RecipeLikeRepository recipeLikeRepository; + @Autowired UserReportRepository userReportRepository; @Autowired UserService userService; @@ -185,4 +191,50 @@ void getBlockedUserGroup() { () -> assertThat(blockedUserGroup.isBlocked(4L)).isFalse() ); } + + @Test + @DisplayName("사용자를 삭제한다.") + void deleteUser() { + UserInfo userInfo = new UserInfo(1L, "loki@pengcook.net"); + + userService.deleteUser(userInfo); + + assertThat(userRepository.existsById(userInfo.getId())).isFalse(); + } + + @Test + @DisplayName("사용자를 삭제하면 사용자가 작성했던 모든 게시글도 지운다.") + void deleteUserWithRecipes() { + UserInfo userInfo = new UserInfo(1L, "loki@pengcook.net"); + + userService.deleteUser(userInfo); + boolean deletedUserRecipes = recipeRepository.findAll().stream() + .noneMatch(recipe -> recipe.getAuthor().getId() == userInfo.getId()); + + assertThat(deletedUserRecipes).isTrue(); + } + + @Test + @DisplayName("사용자를 삭제하면 사용자가 작성했던 모든 댓글도 지운다.") + void deleteUserWithComments() { + UserInfo userInfo = new UserInfo(1L, "loki@pengcook.net"); + + userService.deleteUser(userInfo); + boolean deletedUserComments = commentRepository.findAll().stream() + .noneMatch(comment -> comment.getUser().getId() == userInfo.getId()); + + assertThat(deletedUserComments).isTrue(); + } + + @Test + @DisplayName("사용자를 삭제하면 사용자가 작성했던 모든 좋아요도 지운다.") + void deleteUserWithLikes() { + UserInfo userInfo = new UserInfo(1L, "loki@pengcook.net"); + + userService.deleteUser(userInfo); + boolean deletedUserLikes = recipeLikeRepository.findAll().stream() + .noneMatch(like -> like.getUser().getId() == userInfo.getId()); + + assertThat(deletedUserLikes).isTrue(); + } } diff --git a/backend/src/test/resources/data/users.sql b/backend/src/test/resources/data/users.sql index 3ce348fe..5468c55e 100644 --- a/backend/src/test/resources/data/users.sql +++ b/backend/src/test/resources/data/users.sql @@ -21,7 +21,11 @@ ALTER TABLE ingredient_recipe ALTER COLUMN id RESTART; TRUNCATE TABLE recipe_step; ALTER TABLE recipe_step ALTER COLUMN id RESTART; -ALTER TABLE users ALTER COLUMN id RESTART WITH 1; +TRUNCATE TABLE comment; +ALTER TABLE comment ALTER COLUMN id RESTART; + +TRUNCATE TABLE recipe_like; +ALTER TABLE recipe_like ALTER COLUMN id RESTART; TRUNCATE TABLE user_block; ALTER TABLE user_block ALTER COLUMN id RESTART WITH 1; @@ -152,10 +156,37 @@ VALUES (1, 1, 'REQUIRED'), -- 김치볶음밥 (17, 13, 'REQUIRED'), -- 베지터블 스프 (18, 14, 'OPTIONAL'); -- 카레라이스 +INSERT INTO comment (user_id, recipe_id, message, created_at) +VALUES ('2', '1', 'great', '2024-01-01'), + ('1', '1', 'thank you','2024-01-02'), + ('1', '3', 'thank you','2024-01-02'), + ('1', '4', 'thank you too','2024-01-02'), + ('1', '5', 'thank you a lot','2024-01-02'), + ('2', '2', 'good', '2024-05-05'); + +INSERT INTO recipe_like (recipe_id, user_id) +VALUES (1, 1), + (1, 2), + (1, 3), + (1, 4), + (1, 5), + (2, 3), + (2, 4), + (2, 5), + (2, 6), + (2, 7), + (4, 4), + (4, 1), + (4, 3), + (4, 6), + (4, 5), + (5, 1); + INSERT INTO recipe_step (recipe_id, image, description, sequence) VALUES (1, '레시피1 설명1 이미지', '레시피1 설명1', 1), (1, '레시피1 설명3 이미지', '레시피1 설명3', 3), (1, '레시피1 설명2 이미지', '레시피1 설명2', 2); + INSERT INTO user_block (blocker_id, blockee_id) VALUES (1, 2), (1, 3)