Skip to content
Merged
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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=
```

## 프론트엔드
Expand Down
Original file line number Diff line number Diff line change
@@ -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
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package me.daegyeo.maru.user.adaptor.`in`.web.dto

data class UpdateUserDto(
val nickname: String,
)
Original file line number Diff line number Diff line change
@@ -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
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package me.daegyeo.maru.user.application.port.`in`

import java.util.UUID

fun interface DeleteUserUseCase {
fun deleteUser(userId: UUID)
}
Original file line number Diff line number Diff line change
@@ -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,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package me.daegyeo.maru.user.application.port.out

import java.util.UUID

fun interface DeleteUserPort {
fun deleteUser(userId: UUID): Boolean
}
Original file line number Diff line number Diff line change
@@ -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?,
)
Original file line number Diff line number Diff line change
@@ -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")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
2 changes: 1 addition & 1 deletion src/main/resources/logback.xml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
<level>INFO</level>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>log-%d{yyyy-MM-dd}.log</fileNamePattern>
<fileNamePattern>./logs/log-%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>90</maxHistory>
<totalSizeCap>100MB</totalSizeCap>
</rollingPolicy>
Expand Down
51 changes: 45 additions & 6 deletions src/test/kotlin/me/daegyeo/maru/UserUnitTest.kt
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
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
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
Expand All @@ -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 `이미 존재하는 이메일로 회원가입 시 오류를 반환함`() {
Expand Down Expand Up @@ -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(
Expand All @@ -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)

Expand All @@ -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
Expand Down