Skip to content

Commit

Permalink
feat: User 삭제 API를 개발한다
Browse files Browse the repository at this point in the history
  • Loading branch information
devxb committed Mar 8, 2024
1 parent 1ffe225 commit b15116d
Show file tree
Hide file tree
Showing 9 changed files with 179 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@ protected ResultActions updateTargetPosition(String token, String content) throw
);
}

protected ResultActions deleteUser(String token) throws Exception {
return mockMvc.perform(MockMvcRequestBuilders
.delete(API_VERSION + "/users")
.header(HttpHeaders.AUTHORIZATION, token));
}

@Autowired
final void setMockMvc(MockMvc mockMvc) {
this.mockMvc = mockMvc;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package me.nalab.luffy.api.acceptance.test.user.delete;

import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import java.time.Instant;
import java.util.Set;
import me.nalab.auth.application.common.dto.Payload;
import me.nalab.auth.application.common.utils.JwtUtils;
import me.nalab.auth.mock.api.MockUserRegisterEvent;
import me.nalab.luffy.api.acceptance.test.TargetInitializer;
import me.nalab.luffy.api.acceptance.test.UserInitializer;
import me.nalab.luffy.api.acceptance.test.user.UserAcceptanceTestSupporter;
import me.nalab.user.domain.user.Provider;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.test.context.TestPropertySource;

@SpringBootTest
@AutoConfigureMockMvc
@TestPropertySource("classpath:h2.properties")
@ComponentScan("me.nalab")
@EnableJpaRepositories(basePackages = {"me.nalab"})
@EntityScan(basePackages = {"me.nalab"})
class UserDeleteAcceptanceTest extends UserAcceptanceTestSupporter {

@Autowired
private ApplicationEventPublisher applicationEventPublisher;

@Autowired
private TargetInitializer targetInitializer;

@Autowired
private JwtUtils jwtUtils;

@Autowired
private UserInitializer userInitializer;

@Test
@DisplayName("DELETE /v1/users API는 token에 해당하는 유저가 삭제되면 200 OK를 반환한다.")
void DELETE_USER_SUCCESS() throws Exception {
// given
String nickname = "delete_user_success";
String email = "delete_user_success";

var token = "bearer " + createUserAndSetToken(nickname, email);

// when
var result = deleteUser(token);

// then
result.andExpect(status().isOk());
}

private String createUserAndSetToken(String nickname, String email) {
Long userId = userInitializer.saveUserWithOAuth(Provider.DEFAULT, nickname, email, Instant.now());
Long targetId = targetInitializer.saveTargetAndGetId(nickname, Instant.now());

var token = jwtUtils.createAccessToken(Set.of(new Payload(Payload.Key.USER_ID, String.valueOf(userId)),
new Payload(Payload.Key.TARGET_ID, String.valueOf(targetId))));

applicationEventPublisher.publishEvent(
MockUserRegisterEvent.builder().expectedToken("bearer " + token).expectedId(targetId).build());

return token;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package me.nalab.user.application.port.in;

public interface UserDeleteUseCase {

/*
토큰에 해당하는 유저를 삭제합니다.
*/
void deleteByToken(String token);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package me.nalab.user.application.port.out.persistence;

public interface UserDeletePort {

/*
user id에 해당하는 유저를 삭제합니다.
*/
void deleteUserById(Long userId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package me.nalab.user.application.service;

import lombok.RequiredArgsConstructor;
import me.nalab.user.application.port.in.UserDeleteUseCase;
import me.nalab.user.application.port.out.persistence.LoginedUserGetByTokenPort;
import me.nalab.user.application.port.out.persistence.UserDeletePort;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
public class UserDeleteService implements UserDeleteUseCase {

private final LoginedUserGetByTokenPort loginedUserGetByTokenPort;
private final UserDeletePort userDeletePort;

@Override
@Transactional
public void deleteByToken(String token) {
var tokenInfo = loginedUserGetByTokenPort.decryptToken(getTokenValuePart(token));
userDeletePort.deleteUserById(tokenInfo.getUserId());
}

private String getTokenValuePart(String token) {
return token.split(" ")[1];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package me.nalab.user.jpa.adaptor.create;

import lombok.RequiredArgsConstructor;
import me.nalab.user.application.port.out.persistence.UserDeletePort;
import me.nalab.user.jpa.adaptor.create.repository.UserJpaRepository;
import me.nalab.user.jpa.adaptor.create.repository.UserOAuthInfoJpaRepository;
import org.springframework.stereotype.Repository;

@Repository
@RequiredArgsConstructor
public class UserDeleteAdaptor implements UserDeletePort {

private final UserJpaRepository userJpaRepository;
private final UserOAuthInfoJpaRepository userOAuthInfoJpaRepository;

@Override
public void deleteUserById(Long userId) {
userOAuthInfoJpaRepository.deleteByUserId(userId);
userJpaRepository.deleteById(userId);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
import me.nalab.user.domain.user.User;
import me.nalab.user.jpa.adaptor.create.common.mapper.UserObjectMapper;
import me.nalab.user.jpa.adaptor.create.repository.UserJpaRepository;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;

@Repository
@Service
@RequiredArgsConstructor
public class UserGetAdaptor implements UserGetPort {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,18 @@

import me.nalab.core.data.user.UserOAuthInfoEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;

import java.util.Optional;

@Repository
public interface UserOAuthInfoJpaRepository extends JpaRepository<UserOAuthInfoEntity, Long> {
Optional<UserOAuthInfoEntity> findByProviderAndEmail(String provider, String email);

@Modifying
@Query("delete from UserOAuthInfoEntity u where u.userEntity.id = :userId")
void deleteByUserId(@Param("userId") Long userId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package me.nalab.user.web.adaptor.logined;

import lombok.RequiredArgsConstructor;
import me.nalab.user.application.port.in.UserDeleteUseCase;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/v1")
@RequiredArgsConstructor
public class DeleteUserController {

private final UserDeleteUseCase userDeleteUseCase;

@DeleteMapping("/users")
@ResponseStatus(HttpStatus.OK)
public void deleteUser(@RequestHeader(HttpHeaders.AUTHORIZATION) String token) {
userDeleteUseCase.deleteByToken(token);
}
}

0 comments on commit b15116d

Please sign in to comment.