diff --git a/README.md b/README.md index fc4c68e..b0dc9f8 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,12 @@ NAVER_SECRET= DIARY_SECRET= CORS_ORIGINS=http://localhost:3000,http://frontend + +MINIO_ENDPOINT= +MINIO_ACCESS_KEY= +MINIO_SECRET_KEY= +MINIO_BUCKET_NAME= +MINIO_PRESIGNED_URL_EXPIRATION= ``` ## 프론트엔드 diff --git a/src/main/kotlin/me/daegyeo/maru/user/adaptor/in/web/UserController.kt b/src/main/kotlin/me/daegyeo/maru/user/adaptor/in/web/UserController.kt new file mode 100644 index 0000000..425e0af --- /dev/null +++ b/src/main/kotlin/me/daegyeo/maru/user/adaptor/in/web/UserController.kt @@ -0,0 +1,45 @@ +package me.daegyeo.maru.user.adaptor.`in`.web + +import me.daegyeo.maru.auth.application.domain.CustomUserDetails +import me.daegyeo.maru.user.adaptor.`in`.web.dto.UpdateUserDto +import me.daegyeo.maru.user.application.port.`in`.DeleteUserUseCase +import me.daegyeo.maru.user.application.port.`in`.UpdateUserUseCase +import me.daegyeo.maru.user.application.port.`in`.command.UpdateUserUseCaseCommand +import org.springframework.security.access.prepost.PreAuthorize +import org.springframework.security.core.annotation.AuthenticationPrincipal +import org.springframework.web.bind.annotation.DeleteMapping +import org.springframework.web.bind.annotation.PutMapping +import org.springframework.web.bind.annotation.RequestBody +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController + +@RestController +@RequestMapping("/user") +class UserController( + private val updateUserUseCase: UpdateUserUseCase, + private val deleteUserUseCase: DeleteUserUseCase, +) { + @PreAuthorize("hasRole('USER')") + @PutMapping + fun updateUser( + @RequestBody body: UpdateUserDto, + @AuthenticationPrincipal auth: CustomUserDetails, + ): Boolean { + updateUserUseCase.updateUser( + auth.userId, + UpdateUserUseCaseCommand( + nickname = body.nickname, + ), + ) + return true + } + + @PreAuthorize("hasRole('USER')") + @DeleteMapping + fun deleteUser( + @AuthenticationPrincipal auth: CustomUserDetails, + ): Boolean { + deleteUserUseCase.deleteUser(auth.userId) + return true + } +} diff --git a/src/main/kotlin/me/daegyeo/maru/user/adaptor/in/web/dto/UpdateUserDto.kt b/src/main/kotlin/me/daegyeo/maru/user/adaptor/in/web/dto/UpdateUserDto.kt new file mode 100644 index 0000000..ac5b7ea --- /dev/null +++ b/src/main/kotlin/me/daegyeo/maru/user/adaptor/in/web/dto/UpdateUserDto.kt @@ -0,0 +1,5 @@ +package me.daegyeo.maru.user.adaptor.`in`.web.dto + +data class UpdateUserDto( + val nickname: String, +) diff --git a/src/main/kotlin/me/daegyeo/maru/user/adaptor/out/persistence/DeleteUserPersistenceAdapter.kt b/src/main/kotlin/me/daegyeo/maru/user/adaptor/out/persistence/DeleteUserPersistenceAdapter.kt new file mode 100644 index 0000000..51e7800 --- /dev/null +++ b/src/main/kotlin/me/daegyeo/maru/user/adaptor/out/persistence/DeleteUserPersistenceAdapter.kt @@ -0,0 +1,13 @@ +package me.daegyeo.maru.user.adaptor.out.persistence + +import me.daegyeo.maru.user.application.port.out.DeleteUserPort +import org.springframework.stereotype.Component +import java.util.UUID + +@Component +class DeleteUserPersistenceAdapter(private val userRepository: UserRepository) : DeleteUserPort { + override fun deleteUser(userId: UUID): Boolean { + userRepository.deleteById(userId) + return true + } +} diff --git a/src/main/kotlin/me/daegyeo/maru/user/adaptor/out/persistence/UpdateUserPersistence.kt b/src/main/kotlin/me/daegyeo/maru/user/adaptor/out/persistence/UpdateUserPersistence.kt index 7b42e23..1fa1eaa 100644 --- a/src/main/kotlin/me/daegyeo/maru/user/adaptor/out/persistence/UpdateUserPersistence.kt +++ b/src/main/kotlin/me/daegyeo/maru/user/adaptor/out/persistence/UpdateUserPersistence.kt @@ -18,7 +18,6 @@ class UpdateUserPersistence(private val userRepository: UserRepository, private val user = userRepository.findById(userId).getOrNull() user?.let { if (inputUser.nickname != null) it.nickname = inputUser.nickname - if (inputUser.deletedAt != null) it.deletedAt = inputUser.deletedAt return userMapper.toDomain(userRepository.save(it)) } return null diff --git a/src/main/kotlin/me/daegyeo/maru/user/application/port/in/DeleteUserUseCase.kt b/src/main/kotlin/me/daegyeo/maru/user/application/port/in/DeleteUserUseCase.kt new file mode 100644 index 0000000..ec3a913 --- /dev/null +++ b/src/main/kotlin/me/daegyeo/maru/user/application/port/in/DeleteUserUseCase.kt @@ -0,0 +1,7 @@ +package me.daegyeo.maru.user.application.port.`in` + +import java.util.UUID + +fun interface DeleteUserUseCase { + fun deleteUser(userId: UUID) +} diff --git a/src/main/kotlin/me/daegyeo/maru/user/application/port/in/command/UpdateUserUseCaseCommand.kt b/src/main/kotlin/me/daegyeo/maru/user/application/port/in/command/UpdateUserUseCaseCommand.kt index b82bdc5..83871af 100644 --- a/src/main/kotlin/me/daegyeo/maru/user/application/port/in/command/UpdateUserUseCaseCommand.kt +++ b/src/main/kotlin/me/daegyeo/maru/user/application/port/in/command/UpdateUserUseCaseCommand.kt @@ -1,8 +1,5 @@ package me.daegyeo.maru.user.application.port.`in`.command -import java.time.ZonedDateTime - data class UpdateUserUseCaseCommand( - val nickname: String?, - val deletedAt: ZonedDateTime?, + val nickname: String, ) diff --git a/src/main/kotlin/me/daegyeo/maru/user/application/port/out/DeleteUserPort.kt b/src/main/kotlin/me/daegyeo/maru/user/application/port/out/DeleteUserPort.kt new file mode 100644 index 0000000..360ac7d --- /dev/null +++ b/src/main/kotlin/me/daegyeo/maru/user/application/port/out/DeleteUserPort.kt @@ -0,0 +1,7 @@ +package me.daegyeo.maru.user.application.port.out + +import java.util.UUID + +fun interface DeleteUserPort { + fun deleteUser(userId: UUID): Boolean +} diff --git a/src/main/kotlin/me/daegyeo/maru/user/application/port/out/dto/UpdateUserDto.kt b/src/main/kotlin/me/daegyeo/maru/user/application/port/out/dto/UpdateUserDto.kt index 97f59cc..c8bd22d 100644 --- a/src/main/kotlin/me/daegyeo/maru/user/application/port/out/dto/UpdateUserDto.kt +++ b/src/main/kotlin/me/daegyeo/maru/user/application/port/out/dto/UpdateUserDto.kt @@ -1,8 +1,5 @@ package me.daegyeo.maru.user.application.port.out.dto -import java.time.ZonedDateTime - data class UpdateUserDto( val nickname: String?, - val deletedAt: ZonedDateTime?, ) diff --git a/src/main/kotlin/me/daegyeo/maru/user/application/service/DeleteUserService.kt b/src/main/kotlin/me/daegyeo/maru/user/application/service/DeleteUserService.kt new file mode 100644 index 0000000..df75718 --- /dev/null +++ b/src/main/kotlin/me/daegyeo/maru/user/application/service/DeleteUserService.kt @@ -0,0 +1,34 @@ +package me.daegyeo.maru.user.application.service + +import me.daegyeo.maru.diary.application.port.`in`.DeleteDiaryUseCase +import me.daegyeo.maru.diary.application.port.`in`.GetAllDiaryUseCase +import me.daegyeo.maru.user.application.port.`in`.DeleteUserUseCase +import me.daegyeo.maru.user.application.port.`in`.GetUserUseCase +import me.daegyeo.maru.user.application.port.out.DeleteUserPort +import org.slf4j.LoggerFactory +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional +import java.util.UUID + +@Service +class DeleteUserService( + private val deleteUserPort: DeleteUserPort, + private val getUserUseCase: GetUserUseCase, + private val getAllDiaryUseCase: GetAllDiaryUseCase, + private val deleteDiaryUseCase: DeleteDiaryUseCase, +) : DeleteUserUseCase { + private val logger = LoggerFactory.getLogger(this::class.java) + + @Transactional + override fun deleteUser(userId: UUID) { + val isExistsUser = getUserUseCase.getUser(userId) + + val diaries = getAllDiaryUseCase.getAllDiaryByUserId(isExistsUser.userId) + diaries.forEach { + deleteDiaryUseCase.deleteDiary(it.diaryId, isExistsUser.userId) + } + deleteUserPort.deleteUser(isExistsUser.userId) + + logger.info("User 데이터를 삭제하고 탈퇴했습니다. $userId") + } +} diff --git a/src/main/kotlin/me/daegyeo/maru/user/application/service/UpdateUserService.kt b/src/main/kotlin/me/daegyeo/maru/user/application/service/UpdateUserService.kt index de8bf20..1a206f9 100644 --- a/src/main/kotlin/me/daegyeo/maru/user/application/service/UpdateUserService.kt +++ b/src/main/kotlin/me/daegyeo/maru/user/application/service/UpdateUserService.kt @@ -19,8 +19,10 @@ class UpdateUserService(private val updateUserPort: UpdateUserPort) : UpdateUser userId: UUID, input: UpdateUserUseCaseCommand, ): User { + val result = + updateUserPort.updateUser(userId, UpdateUserDto(nickname = input.nickname)) + ?: throw ServiceException(UserError.USER_NOT_FOUND) logger.info("User 데이터를 변경했습니다. $userId") - return updateUserPort.updateUser(userId, UpdateUserDto(nickname = input.nickname, deletedAt = input.deletedAt)) - ?: throw ServiceException(UserError.USER_NOT_FOUND) + return result } } diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index 6a31412..fa1482a 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -24,7 +24,7 @@ INFO - log-%d{yyyy-MM-dd}.log + ./logs/log-%d{yyyy-MM-dd}.log 90 100MB diff --git a/src/test/kotlin/me/daegyeo/maru/UserUnitTest.kt b/src/test/kotlin/me/daegyeo/maru/UserUnitTest.kt index 5fe1d9f..e406f6e 100644 --- a/src/test/kotlin/me/daegyeo/maru/UserUnitTest.kt +++ b/src/test/kotlin/me/daegyeo/maru/UserUnitTest.kt @@ -1,5 +1,8 @@ package me.daegyeo.maru +import me.daegyeo.maru.diary.application.domain.Diary +import me.daegyeo.maru.diary.application.port.`in`.DeleteDiaryUseCase +import me.daegyeo.maru.diary.application.port.`in`.GetAllDiaryUseCase import me.daegyeo.maru.shared.constant.Vendor import me.daegyeo.maru.shared.exception.ServiceException import me.daegyeo.maru.user.application.domain.User @@ -7,11 +10,13 @@ import me.daegyeo.maru.user.application.error.UserError import me.daegyeo.maru.user.application.port.`in`.command.CreateUserUseCaseCommand import me.daegyeo.maru.user.application.port.`in`.command.UpdateUserUseCaseCommand import me.daegyeo.maru.user.application.port.out.CreateUserPort +import me.daegyeo.maru.user.application.port.out.DeleteUserPort import me.daegyeo.maru.user.application.port.out.ReadUserPort import me.daegyeo.maru.user.application.port.out.UpdateUserPort import me.daegyeo.maru.user.application.port.out.dto.CreateUserDto import me.daegyeo.maru.user.application.port.out.dto.UpdateUserDto import me.daegyeo.maru.user.application.service.CreateUserService +import me.daegyeo.maru.user.application.service.DeleteUserService import me.daegyeo.maru.user.application.service.GetUserService import me.daegyeo.maru.user.application.service.UpdateUserService import org.junit.jupiter.api.Assertions.assertThrows @@ -27,10 +32,15 @@ import java.util.UUID class UserUnitTest { private val createUserPort = mock(CreateUserPort::class.java) private val readUserPort = mock(ReadUserPort::class.java) + private val deleteUserPort = mock(DeleteUserPort::class.java) private val updatedUserPort = mock(UpdateUserPort::class.java) + private val getUserUseCase = mock(GetUserService::class.java) + private val getAllDiaryUseCase = mock(GetAllDiaryUseCase::class.java) + private val deleteDiaryUseCase = mock(DeleteDiaryUseCase::class.java) private val createUserService = CreateUserService(createUserPort, readUserPort) private val getUserService = GetUserService(readUserPort) private val updateUserService = UpdateUserService(updatedUserPort) + private val deleteUserService = DeleteUserService(deleteUserPort, getUserUseCase, getAllDiaryUseCase, deleteDiaryUseCase) @Test fun `이미 존재하는 이메일로 회원가입 시 오류를 반환함`() { @@ -147,12 +157,12 @@ class UserUnitTest { @Test fun `사용자 정보를 성공적으로 수정함`() { - val input = UpdateUserUseCaseCommand(nickname = "NewNickname", deletedAt = null) + val input = UpdateUserUseCaseCommand(nickname = "NewNickname") val userId = UUID.randomUUID() `when`( updatedUserPort.updateUser( userId, - UpdateUserDto(nickname = "NewNickname", deletedAt = null), + UpdateUserDto(nickname = "NewNickname"), ), ).thenReturn( User( @@ -168,18 +178,18 @@ class UserUnitTest { val result = updateUserService.updateUser(userId, input) - verify(updatedUserPort).updateUser(userId, UpdateUserDto(nickname = "NewNickname", deletedAt = null)) + verify(updatedUserPort).updateUser(userId, UpdateUserDto(nickname = "NewNickname")) assert(result.nickname == "NewNickname") } @Test fun `존재하지 않는 사용자 정보를 수정하면 오류를 반환함`() { - val input = UpdateUserUseCaseCommand(nickname = "NewNickname", deletedAt = null) + val input = UpdateUserUseCaseCommand(nickname = "NewNickname") val userId = UUID.randomUUID() `when`( updatedUserPort.updateUser( userId, - UpdateUserDto(nickname = "NewNickname", deletedAt = null), + UpdateUserDto(nickname = "NewNickname"), ), ).thenReturn(null) @@ -192,7 +202,36 @@ class UserUnitTest { @Test fun `사용자가 성공적으로 탈퇴함`() { - // TODO: Not implemented yet + val userId = UUID.randomUUID() + val user = + User( + userId = userId, + email = "foobar@acme.com", + vendor = Vendor.GOOGLE, + nickname = "FooBar", + createdAt = ZonedDateTime.now(), + updatedAt = ZonedDateTime.now(), + deletedAt = null, + ) + val diaries = + listOf( + Diary( + diaryId = 1L, + title = "제목", + content = "ENCRYPTED_CONTENT", + createdAt = ZonedDateTime.now(), + updatedAt = ZonedDateTime.now(), + deletedAt = null, + ), + ) + + `when`(getUserUseCase.getUser(userId)).thenReturn(user) + `when`(getAllDiaryUseCase.getAllDiaryByUserId(userId)).thenReturn(diaries) + + deleteUserService.deleteUser(userId) + + verify(deleteDiaryUseCase).deleteDiary(1L, userId) + verify(deleteUserPort).deleteUser(userId) } @Test