Skip to content

Commit

Permalink
fix: ✏️ Users are unable to delete their accounts while they hold own…
Browse files Browse the repository at this point in the history
…ership of any chatrooms (#198)

* feat: add method of find existence that user has chatroom's ownership anyone on repository

* feat: add method in domain service

* feat: add 409 error for user's signout blocking because user has chat room ownership

* feat: resolve todo annotation

* test: add delete test about has ownership chatroom user can't delete account
  • Loading branch information
psychology50 authored Nov 13, 2024
1 parent 6694609 commit af58ae7
Show file tree
Hide file tree
Showing 6 changed files with 53 additions and 7 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package kr.co.pennyway.api.apis.users.service;

import kr.co.pennyway.domain.domains.device.service.DeviceTokenService;
import kr.co.pennyway.domain.domains.member.service.ChatMemberService;
import kr.co.pennyway.domain.domains.oauth.service.OauthService;
import kr.co.pennyway.domain.domains.spending.service.SpendingCustomCategoryService;
import kr.co.pennyway.domain.domains.spending.service.SpendingService;
Expand All @@ -27,6 +28,8 @@ public class UserDeleteService {
private final OauthService oauthService;
private final DeviceTokenService deviceTokenService;

private final ChatMemberService chatMemberService;

private final SpendingService spendingService;
private final SpendingCustomCategoryService spendingCustomCategoryService;

Expand All @@ -36,13 +39,14 @@ public class UserDeleteService {
* hard delete가 수행되어야 할 데이터는 삭제하지 않으며, 사용자 데이터 유지 기간이 만료될 때 DBA가 수행한다.
*
* @param userId
* @todo [2024-05-03] 채팅 기능이 추가되는 경우 채팅방장 탈퇴를 제한해야 하며, 추가로 삭제될 엔티티 삭제 로직을 추가해야 한다.
*/
@Transactional
public void execute(Long userId) {
if (!userService.isExistUser(userId)) throw new UserErrorException(UserErrorCode.NOT_FOUND);

// TODO: [2024-05-03] 하나라도 채팅방의 방장으로 참여하는 경우 삭제 불가능 처리
if (chatMemberService.hasUserChatRoomOwnership(userId)) {
throw new UserErrorException(UserErrorCode.HAS_OWNERSHIP_CHAT_ROOM);
}

oauthService.deleteOauthsByUserIdInQuery(userId);
deviceTokenService.deleteDevicesByUserIdInQuery(userId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@
import kr.co.pennyway.api.apis.users.service.UserDeleteService;
import kr.co.pennyway.api.config.ExternalApiDBTestConfig;
import kr.co.pennyway.api.config.TestJpaConfig;
import kr.co.pennyway.api.config.fixture.DeviceTokenFixture;
import kr.co.pennyway.api.config.fixture.SpendingCustomCategoryFixture;
import kr.co.pennyway.api.config.fixture.SpendingFixture;
import kr.co.pennyway.api.config.fixture.UserFixture;
import kr.co.pennyway.api.config.fixture.*;
import kr.co.pennyway.domain.config.JpaConfig;
import kr.co.pennyway.domain.domains.chatroom.domain.ChatRoom;
import kr.co.pennyway.domain.domains.chatroom.repository.ChatRoomRepository;
import kr.co.pennyway.domain.domains.device.domain.DeviceToken;
import kr.co.pennyway.domain.domains.device.service.DeviceTokenService;
import kr.co.pennyway.domain.domains.member.repository.ChatMemberRepository;
import kr.co.pennyway.domain.domains.member.service.ChatMemberService;
import kr.co.pennyway.domain.domains.oauth.domain.Oauth;
import kr.co.pennyway.domain.domains.oauth.service.OauthService;
import kr.co.pennyway.domain.domains.oauth.type.Provider;
Expand Down Expand Up @@ -40,7 +41,7 @@
@Slf4j
@ExtendWith(MockitoExtension.class)
@DataJpaTest(properties = "spring.jpa.hibernate.ddl-auto=create")
@ContextConfiguration(classes = {JpaConfig.class, UserDeleteService.class, UserService.class, OauthService.class, DeviceTokenService.class, SpendingService.class, SpendingCustomCategoryService.class})
@ContextConfiguration(classes = {JpaConfig.class, UserDeleteService.class, UserService.class, OauthService.class, DeviceTokenService.class, SpendingService.class, SpendingCustomCategoryService.class, ChatMemberService.class})
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@Import(TestJpaConfig.class)
public class UserDeleteServiceTest extends ExternalApiDBTestConfig {
Expand All @@ -62,6 +63,12 @@ public class UserDeleteServiceTest extends ExternalApiDBTestConfig {
@Autowired
private SpendingCustomCategoryService spendingCustomCategoryService;

@Autowired
private ChatRoomRepository chatRoomRepository;

@Autowired
private ChatMemberRepository chatMemberRepository;

@Test
@Transactional
@DisplayName("사용자가 삭제된 유저를 조회하려는 경우 NOT_FOUND 에러를 반환한다.")
Expand Down Expand Up @@ -144,6 +151,20 @@ void deleteAccountWithSpending() {
assertTrue("지출 카테고리가 삭제되어 있어야 한다.", spendingCustomCategoryService.readSpendingCustomCategory(category.getId()).isEmpty());
}

@Test
@Transactional
@DisplayName("사용자가 채팅방장으로 등록된 채팅방이 하나 이상 존재하는 경우, 삭제할 수 없다.")
void deleteAccountWithOwnershipChatRoom() {
// given
User user = userService.createUser(UserFixture.GENERAL_USER.toUser());
ChatRoom chatRoom = chatRoomRepository.save(ChatRoomFixture.PUBLIC_CHAT_ROOM.toEntity(1L));
chatMemberRepository.save(ChatMemberFixture.ADMIN.toEntity(user, chatRoom));

// when - then
UserErrorException ex = assertThrows(UserErrorException.class, () -> userDeleteService.execute(user.getId()));
assertEquals("채팅방장으로 등록된 채팅방이 하나 이상 존재하는 경우, 삭제할 수 없다.", UserErrorCode.HAS_OWNERSHIP_CHAT_ROOM, ex.getBaseErrorCode());
}

private Oauth createOauth(Provider provider, String providerId, User user) {
Oauth oauth = Oauth.of(provider, providerId, user);
return oauthService.createOauth(oauth);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ public interface CustomChatMemberRepository {
*/
boolean existsByChatRoomIdAndUserId(Long chatRoomId, Long userId);

/**
* 해당 유저가 채팅방장으로 가입한 채팅방이 존재하는지 확인한다.
*/
boolean existsOwnershipChatRoomByUserId(Long userId);

/**
* 채팅방의 관리자 정보를 조회한다.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,16 @@ public boolean existsByChatRoomIdAndUserId(Long chatRoomId, Long userId) {
.fetchFirst() != null;
}

@Override
public boolean existsOwnershipChatRoomByUserId(Long userId) {
return queryFactory.select(ConstantImpl.create(1))
.from(chatMember)
.where(chatMember.user.id.eq(userId)
.and(chatMember.role.eq(ChatMemberRole.ADMIN))
.and(chatMember.deletedAt.isNull()))
.fetchFirst() != null;
}

@Override
public Optional<ChatMemberResult.Detail> findAdminByChatRoomId(Long chatRoomId) {
ChatMemberResult.Detail result =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,11 @@ public boolean isExists(Long chatRoomId, Long userId) {
return chatMemberRepository.existsByChatRoomIdAndUserId(chatRoomId, userId);
}

@Transactional(readOnly = true)
public boolean hasUserChatRoomOwnership(Long userId) {
return chatMemberRepository.existsOwnershipChatRoomByUserId(userId);
}

@Transactional(readOnly = true)
public long countActiveMembers(Long chatRoomId) {
return chatMemberRepository.countByChatRoomIdAndActive(chatRoomId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public enum UserErrorCode implements BaseErrorCode {
ALREADY_SIGNUP(StatusCode.CONFLICT, ReasonCode.RESOURCE_ALREADY_EXISTS, "이미 회원가입한 유저입니다."),
ALREADY_EXIST_USERNAME(StatusCode.CONFLICT, ReasonCode.RESOURCE_ALREADY_EXISTS, "이미 존재하는 아이디입니다."),
ALREADY_EXIST_PHONE(StatusCode.CONFLICT, ReasonCode.RESOURCE_ALREADY_EXISTS, "이미 존재하는 휴대폰 번호입니다."),
HAS_OWNERSHIP_CHAT_ROOM(StatusCode.CONFLICT, ReasonCode.RESOURCE_ALREADY_EXISTS, "채팅방의 방장으로 참여하고 있는 경우 삭제할 수 없습니다."),

/* 404 NOT_FOUND */
NOT_FOUND(StatusCode.NOT_FOUND, ReasonCode.REQUESTED_RESOURCE_NOT_FOUND, "유저를 찾을 수 없습니다."),
Expand Down

0 comments on commit af58ae7

Please sign in to comment.