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