Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

회원 탈퇴 api 추가 #284

Merged
merged 9 commits into from
Aug 11, 2023
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.ddang.ddang.authentication.application;

import com.ddang.ddang.user.infrastructure.persistence.JpaUserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class AuthenticationUserService {

private final JpaUserRepository userRepository;

public boolean isWithdrawal(final Long userId) {
return userRepository.existsByIdAndDeletedIsTrue(userId);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.ddang.ddang.authentication.configuration;

import com.ddang.ddang.authentication.application.AuthenticationUserService;
import com.ddang.ddang.authentication.application.BlackListTokenService;
import com.ddang.ddang.authentication.infrastructure.jwt.PrivateClaims;
import com.ddang.ddang.authentication.domain.TokenDecoder;
Expand All @@ -19,6 +20,7 @@
public class AuthenticationInterceptor implements HandlerInterceptor {

private final BlackListTokenService blackListTokenService;
private final AuthenticationUserService authenticationUserService;
private final TokenDecoder tokenDecoder;
private final AuthenticationStore store;

Expand All @@ -42,6 +44,10 @@ public boolean preHandle(
new InvalidTokenException("유효한 토큰이 아닙니다.")
);

if (authenticationUserService.isWithdrawal(privateClaims.userId())) {
throw new InvalidTokenException("유효한 토큰이 아닙니다.");
}

store.set(new AuthenticationUserInfo(privateClaims.userId()));
return true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,12 @@ public ReadUserDto readById(final Long userId) {

return ReadUserDto.from(user);
}

@Transactional
public void deleteById(final Long userId) {
final User user = userRepository.findByIdAndDeletedIsFalse(userId)
.orElseThrow(() -> new UserNotFoundException("사용자 정보를 사용할 수 없습니다."));

user.withdrawal();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
@Table(name = "users")
public class User extends BaseTimeEntity {

private static final boolean DELETED_STATUS = true;

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
Expand All @@ -36,6 +38,9 @@ public class User extends BaseTimeEntity {
@Column(unique = true)
private String oauthId;

@Column(name = "is_deleted")
private boolean deleted = false;

@Builder
private User(
final String name,
Expand All @@ -48,4 +53,8 @@ private User(
this.reliability = reliability;
this.oauthId = oauthId;
}

public void withdrawal() {
this.deleted = DELETED_STATUS;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,8 @@
public interface JpaUserRepository extends JpaRepository<User, Long> {

Optional<User> findByOauthId(final String oauthId);

Optional<User> findByIdAndDeletedIsFalse(final Long id);

boolean existsByIdAndDeletedIsTrue(final Long id);
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import com.ddang.ddang.user.presentation.dto.ReadUserResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
Expand All @@ -25,4 +26,12 @@ public ResponseEntity<ReadUserResponse> readById(@AuthenticateUser final Authent

return ResponseEntity.ok(response);
}

@DeleteMapping("/withdrawal")
public ResponseEntity<Void> delete(@AuthenticateUser final AuthenticationUserInfo userInfo) {
userService.deleteById(userInfo.userId());

return ResponseEntity.noContent()
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
alter table users add is_deleted bit;

UPDATE users SET is_deleted = 0 where auctioneer_count is null;
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
import com.ddang.ddang.auction.application.dto.ReadRegionsDto;
import com.ddang.ddang.auction.application.exception.AuctionNotFoundException;
import com.ddang.ddang.auction.presentation.dto.request.CreateAuctionRequest;
import com.ddang.ddang.authentication.application.AuthenticationUserService;
import com.ddang.ddang.authentication.application.BlackListTokenService;
import com.ddang.ddang.authentication.configuration.AuthenticationInterceptor;
import com.ddang.ddang.authentication.configuration.AuthenticationPrincipalArgumentResolver;
Expand Down Expand Up @@ -99,6 +100,9 @@ class AuctionControllerTest {
@MockBean
BlackListTokenService blackListTokenService;

@MockBean
AuthenticationUserService authenticationUserService;

@Autowired
AuctionController auctionController;

Expand All @@ -119,6 +123,7 @@ void setUp(@Autowired RestDocumentationContextProvider provider) {
final AuthenticationStore store = new AuthenticationStore();
final AuthenticationInterceptor interceptor = new AuthenticationInterceptor(
blackListTokenService,
authenticationUserService,
mockTokenDecoder,
store
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package com.ddang.ddang.authentication.application;

import static org.assertj.core.api.Assertions.assertThat;

import com.ddang.ddang.configuration.IsolateDatabase;
import com.ddang.ddang.user.domain.User;
import com.ddang.ddang.user.infrastructure.persistence.JpaUserRepository;
import org.junit.jupiter.api.DisplayNameGeneration;
import org.junit.jupiter.api.DisplayNameGenerator;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;

@IsolateDatabase
@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)
@SuppressWarnings("NonAsciiCharacters")
class AuthenticationUserServiceTest {

@Autowired
JpaUserRepository userRepository;

@Autowired
AuthenticationUserService authenticationUserService;

@Test
void 회원탈퇴한_회원의_id를_전달하면_참을_반환한다() {
// given
final User user = User.builder()
.name("회원")
.profileImage("profile.png")
.reliability(4.7d)
.oauthId("12345")
.build();

user.withdrawal();
userRepository.save(user);

// when
final boolean actual = authenticationUserService.isWithdrawal(user.getId());

// then
assertThat(actual).isTrue();
}

@Test
void 회원탈퇴하지_않거나_회원가입하지_않은_회원의_id를_전달하면_거짓을_반환한다() {
// given
final User user = User.builder()
.name("회원")
.profileImage("profile.png")
.reliability(4.7d)
.oauthId("12345")
.build();

userRepository.save(user);

// when
final boolean actual = authenticationUserService.isWithdrawal(user.getId());

// then
assertThat(actual).isFalse();
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,26 @@
package com.ddang.ddang.bid.presentation;

import static org.hamcrest.Matchers.is;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName;
import static org.springframework.restdocs.headers.HeaderDocumentation.requestHeaders;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields;
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import com.ddang.ddang.auction.application.exception.AuctionNotFoundException;
import com.ddang.ddang.authentication.application.AuthenticationUserService;
import com.ddang.ddang.authentication.application.BlackListTokenService;
import com.ddang.ddang.authentication.configuration.AuthenticationInterceptor;
import com.ddang.ddang.authentication.configuration.AuthenticationPrincipalArgumentResolver;
Expand All @@ -19,6 +39,9 @@
import com.ddang.ddang.exception.GlobalExceptionHandler;
import com.ddang.ddang.user.application.exception.UserNotFoundException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayNameGeneration;
import org.junit.jupiter.api.DisplayNameGenerator;
Expand All @@ -42,29 +65,6 @@
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;

import static org.hamcrest.Matchers.is;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName;
import static org.springframework.restdocs.headers.HeaderDocumentation.requestHeaders;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields;
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@WebMvcTest(controllers = {BidController.class},
excludeFilters = {
@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = WebMvcConfigurer.class),
Expand All @@ -83,6 +83,9 @@ class BidControllerTest {
@MockBean
BlackListTokenService blackListTokenService;

@MockBean
AuthenticationUserService authenticationUserService;

@Autowired
BidController bidController;

Expand All @@ -103,6 +106,7 @@ void setUp(@Autowired RestDocumentationContextProvider provider) {
final AuthenticationStore store = new AuthenticationStore();
final AuthenticationInterceptor interceptor = new AuthenticationInterceptor(
blackListTokenService,
authenticationUserService,
mockTokenDecoder,
store
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,26 @@
package com.ddang.ddang.chat.presentation;

import static org.hamcrest.Matchers.is;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import com.ddang.ddang.auction.application.exception.AuctionNotFoundException;
import com.ddang.ddang.auction.domain.Auction;
import com.ddang.ddang.auction.domain.BidUnit;
import com.ddang.ddang.auction.domain.Price;
import com.ddang.ddang.auction.domain.exception.WinnerNotFoundException;
import com.ddang.ddang.authentication.application.AuthenticationUserService;
import com.ddang.ddang.authentication.application.BlackListTokenService;
import com.ddang.ddang.authentication.configuration.AuthenticationInterceptor;
import com.ddang.ddang.authentication.configuration.AuthenticationPrincipalArgumentResolver;
Expand Down Expand Up @@ -38,6 +54,10 @@
import com.ddang.ddang.user.application.exception.UserNotFoundException;
import com.ddang.ddang.user.domain.User;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayNameGeneration;
import org.junit.jupiter.api.DisplayNameGenerator;
Expand All @@ -53,26 +73,6 @@
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.time.LocalDateTime;
import java.util.Collections;
import java.util.List;
import java.util.Optional;

import static org.hamcrest.Matchers.is;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@WebMvcTest(controllers = {ChatRoomController.class},
excludeFilters = {
@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = WebMvcConfigurer.class),
Expand All @@ -92,6 +92,9 @@ class ChatRoomControllerTest {
@MockBean
MessageService messageService;

@MockBean
AuthenticationUserService authenticationUserService;

@Autowired
ChatRoomController chatRoomController;

Expand All @@ -108,7 +111,8 @@ void setUp() {

final AuthenticationStore store = new AuthenticationStore();
final AuthenticationInterceptor interceptor = new AuthenticationInterceptor(
blackListTokenService,
blackListTokenService,
authenticationUserService,
mockTokenDecoder,
store
);
Expand Down
Loading