Skip to content

Commit 969187d

Browse files
authored
Merge pull request #84 from NMNB-Team/v1/feat/#83-withdrawal-of-membership
2 parents 6cc322a + b278b7a commit 969187d

8 files changed

Lines changed: 77 additions & 0 deletions

File tree

nmnb-application/src/main/kotlin/nmnb/application/global/auth/controller/AuthController.kt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import nmnb.common.response.status.SuccessStatus
1818
import nmnb.domain.auth.SocialType
1919
import nmnb.domain.user.User
2020
import org.springframework.web.bind.annotation.GetMapping
21+
import org.springframework.web.bind.annotation.PatchMapping
2122
import org.springframework.web.bind.annotation.PathVariable
2223
import org.springframework.web.bind.annotation.PostMapping
2324
import org.springframework.web.bind.annotation.RequestMapping
@@ -76,4 +77,16 @@ class AuthController(
7677
): BaseResponse<Any> {
7778
return BaseResponse.onSuccess(SuccessStatus.OK, authService.logout(user, deviceId, accessToken, refreshToken))
7879
}
80+
81+
@Operation(summary = "회원탈퇴 API", description = "soft delete를 수행합니다. 데이터는 남아있으나, 조회에서 제외됩니다._숙희")
82+
@ApiResponses(
83+
ApiResponse(responseCode = "COMMON200", description = "성공입니다."),
84+
)
85+
@TokenApiResponse
86+
@PatchMapping("/withdraw")
87+
fun withdraw(
88+
@Parameter(name = "user", hidden = true) @AuthUser user: User,
89+
): BaseResponse<Any> {
90+
return BaseResponse.onSuccess(SuccessStatus.OK, authService.withdraw(user))
91+
}
7992
}

nmnb-application/src/main/kotlin/nmnb/application/global/auth/service/AuthService.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,5 @@ interface AuthService {
99
fun signInWithSocial(accessCode: String, type: SocialType, deviceId: String): AuthUserResponse
1010
fun refreshToken(refreshToken: String, deviceId: String): AuthTokenResponse
1111
fun logout(user: User, deviceId: String, accessToken: String, refreshToken: String)
12+
fun withdraw(user: User)
1213
}

nmnb-application/src/main/kotlin/nmnb/application/global/auth/service/AuthServiceImpl.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,4 +78,9 @@ class AuthServiceImpl(
7878

7979
blacklistService.register(accessToken)
8080
}
81+
82+
@Transactional
83+
override fun withdraw(user: User) {
84+
userRepository.delete(user)
85+
}
8186
}

nmnb-application/src/main/kotlin/nmnb/application/global/config/SecurityConfig.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ class SecurityConfig(
3030
"/v1/api/users/profile",
3131
"/v1/api/auth/refresh",
3232
"/v1/api/auth/logout",
33+
"/v1/api/auth/withdraw",
3334
)
3435

3536
@Bean

nmnb-application/src/test/kotlin/nmnb/application/ControllerTestSupport.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import nmnb.application.global.auth.generator.ExtractAccessTokenArgumentResolver
1313
import nmnb.application.global.auth.generator.ExtractDeviceIdArgumentResolver
1414
import nmnb.application.global.auth.generator.ExtractRefreshTokenArgumentResolver
1515
import nmnb.application.global.auth.service.AuthService
16+
import nmnb.application.global.common.utils.ResponseUtils
1617
import nmnb.application.global.config.SecurityConfig
1718
import nmnb.application.global.infrastructure.security.BlacklistService
1819
import nmnb.application.global.infrastructure.security.JwtProvider
@@ -67,4 +68,7 @@ abstract class ControllerTestSupport {
6768

6869
@MockBean
6970
protected lateinit var blacklistService: BlacklistService
71+
72+
@MockBean
73+
protected lateinit var responseUtils: ResponseUtils
7074
}

nmnb-application/src/test/kotlin/nmnb/application/global/auth/controller/AuthControllerTest.kt

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import org.mockito.kotlin.whenever
1212
import org.springframework.security.test.context.support.WithMockUser
1313
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf
1414
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
15+
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch
1516
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post
1617
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath
1718
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
@@ -70,6 +71,28 @@ class AuthControllerTest() : ControllerTestSupport() {
7071
.andExpect(jsonPath("$.code").value(SuccessStatus.OK.code))
7172
}
7273

74+
@DisplayName("회원탈퇴에 성공한다.")
75+
@WithMockUser
76+
@Test
77+
fun withdraw() {
78+
// given
79+
val deviceId = "deviceId"
80+
val accessToken = "access.token"
81+
val user = User.fixture()
82+
83+
mockUserAuthentication(user, accessToken, deviceId)
84+
85+
// when & then
86+
mockMvc.perform(
87+
patch("/v1/api/auth/withdraw")
88+
.header("X-Access-Token", accessToken)
89+
.header("Device-Id", deviceId)
90+
.with(csrf()),
91+
)
92+
.andExpect(status().isOk)
93+
.andExpect(jsonPath("$.code").value(SuccessStatus.OK.code))
94+
}
95+
7396
private fun mockUserAuthentication(user: User, accessToken: String, deviceId: String) {
7497
whenever(jwtProvider.getEmailWithValidation(accessToken)).thenReturn(user.email)
7598
whenever(userRepository.findByEmail(user.email)).thenReturn(user)

nmnb-application/src/test/kotlin/nmnb/application/global/auth/service/AuthServiceImplTest.kt

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package nmnb.application.global.auth.service
22

3+
import jakarta.persistence.EntityManager
4+
import jakarta.persistence.PersistenceContext
35
import nmnb.application.IntegrationTestSupport
46
import nmnb.application.global.auth.service.dto.KakaoAccount
57
import nmnb.application.global.auth.service.dto.KakaoProfile
@@ -46,6 +48,9 @@ class AuthServiceImplTest : IntegrationTestSupport() {
4648
@MockBean
4749
private lateinit var jwtProvider: JwtProvider
4850

51+
@PersistenceContext
52+
private lateinit var em: EntityManager
53+
4954
@AfterEach
5055
fun tearDown() {
5156
userRepository.deleteAllInBatch()
@@ -152,4 +157,23 @@ class AuthServiceImplTest : IntegrationTestSupport() {
152157
verify(refreshTokenService).deleteRefreshToken(id, refreshToken)
153158
verify(blacklistService).register(accessToken)
154159
}
160+
161+
@Test
162+
@DisplayName("회원탈퇴에 성공한다. soft delete 되어 데이터는 남아있지만, 조회에서는 제외된다. ")
163+
fun withdraw() {
164+
// given
165+
val user = User.fixture()
166+
userRepository.save(user)
167+
168+
// when
169+
authService.withdraw(user)
170+
171+
// then
172+
val deleteValue = em.createNativeQuery("SELECT deleted FROM users u WHERE u.user_id = ?")
173+
.setParameter(1, user.id)
174+
.singleResult
175+
assertThat(deleteValue).isEqualTo(true)
176+
177+
assertThat(userRepository.findByEmail(user.email)).isNull()
178+
}
155179
}

nmnb-domain/src/main/kotlin/nmnb/domain/user/User.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,13 @@ import nmnb.common.domain.SignUpStatus
1414
import nmnb.domain.JpaBaseEntity
1515
import nmnb.domain.post.Post
1616
import nmnb.domain.user.generator.annotation.UserId
17+
import org.hibernate.annotations.SQLDelete
18+
import org.hibernate.annotations.SQLRestriction
1719
import java.util.UUID
1820

1921
@Entity
22+
@SQLDelete(sql = "UPDATE users SET deleted = true WHERE user_id = ?")
23+
@SQLRestriction("deleted = false")
2024
@Table(name = "users")
2125
class User(
2226
@Column(nullable = false, unique = true)
@@ -32,6 +36,8 @@ class User(
3236
@Column(name = "user_id")
3337
val id: String? = null
3438

39+
var deleted: Boolean = false
40+
3541
@Enumerated(EnumType.STRING)
3642
var petOwnershipStatus: PetOwnershipStatus = PetOwnershipStatus.UNKNOWN
3743

0 commit comments

Comments
 (0)