diff --git a/api/src/main/kotlin/com/few/api/domain/member/usecase/SaveMemberUseCase.kt b/api/src/main/kotlin/com/few/api/domain/member/usecase/SaveMemberUseCase.kt index 829aff523..92d80636d 100644 --- a/api/src/main/kotlin/com/few/api/domain/member/usecase/SaveMemberUseCase.kt +++ b/api/src/main/kotlin/com/few/api/domain/member/usecase/SaveMemberUseCase.kt @@ -1,86 +1,46 @@ package com.few.api.domain.member.usecase import com.few.api.config.crypto.IdEncryption +import com.few.api.domain.member.usecase.dto.SaveMemberTxCaseIn import com.few.api.domain.member.usecase.dto.SaveMemberUseCaseIn import com.few.api.domain.member.usecase.dto.SaveMemberUseCaseOut -import com.few.api.exception.common.InsertException +import com.few.api.domain.member.usecase.transaction.SaveMemberTxCase import com.few.api.repo.dao.member.MemberDao -import com.few.api.repo.dao.member.command.InsertMemberCommand -import com.few.api.repo.dao.member.command.UpdateDeletedMemberTypeCommand import com.few.api.repo.dao.member.query.SelectMemberByEmailNotConsiderDeletedAtQuery -import com.few.data.common.code.MemberType import com.few.email.service.member.SendAuthEmailService import com.few.email.service.member.dto.Content import com.few.email.service.member.dto.SendAuthEmailArgs import org.springframework.stereotype.Component import org.springframework.transaction.annotation.Transactional import java.net.URL -import java.util.* @Component class SaveMemberUseCase( private val memberDao: MemberDao, private val sendAuthEmailService: SendAuthEmailService, private val idEncryption: IdEncryption, + private val saveMemberTxCase: SaveMemberTxCase, ) { - companion object { - private const val AUTH_HEAD_COMMENT = "few 로그인 링크입니다." - private const val AUTH_SUB_COMMENT = "로그인하시려면 아래 버튼을 눌러주세요!" - private const val SIGNUP_HEAD_COMMENT = "few에 가입해주셔서 감사합니다." - private const val SIGNUP_SUB_COMMENT = "가입하신 이메일 주소를 확인해주세요." - } - @Transactional fun execute(useCaseIn: SaveMemberUseCaseIn): SaveMemberUseCaseOut { /** email을 통해 가입 이력이 있는지 확인 */ - val isSignUpBeforeMember = SelectMemberByEmailNotConsiderDeletedAtQuery( - email = useCaseIn.email + val (headComment, subComment, memberId) = memberDao.selectMemberByEmail( + SelectMemberByEmailNotConsiderDeletedAtQuery( + email = useCaseIn.email + ) ).let { - memberDao.selectMemberByEmail(it) - } - - var headComment = AUTH_HEAD_COMMENT - var subComment = AUTH_SUB_COMMENT - var email = "" - - /** 가입 이력이 없다면 회원 가입 처리 */ - val token = if (Objects.isNull(isSignUpBeforeMember)) { - headComment = SIGNUP_HEAD_COMMENT - subComment = SIGNUP_SUB_COMMENT - email = useCaseIn.email - memberDao.insertMember( - InsertMemberCommand( - email = useCaseIn.email, - memberType = MemberType.PREAUTH + /** 가입 이력 여부를 기준으로 가입 처리 */ + saveMemberTxCase.execute( + SaveMemberTxCaseIn( + record = it, + email = useCaseIn.email ) - ) ?: throw InsertException("member.insertfail.record") - } else { - /** 삭제한 회원이라면 회원 타입을 PREAUTH로 변경 */ - if (isSignUpBeforeMember!!.isDeleted) { - val isUpdate = memberDao.updateMemberType( - UpdateDeletedMemberTypeCommand( - id = isSignUpBeforeMember.memberId, - memberType = MemberType.PREAUTH - ) - ) - if (isUpdate != 1L) { - throw InsertException("member.updatefail.record") - } - - memberDao.selectMemberByEmail( - SelectMemberByEmailNotConsiderDeletedAtQuery( - email = useCaseIn.email - ) - )?.memberId ?: throw InsertException("member.selectfail.record") - } else { - /** 이미 가입한 회원이라면 회원 ID를 반환 */ - isSignUpBeforeMember.memberId - } - }.let { - /** 회원 ID를 암호화하여 토큰으로 사용 */ - idEncryption.encrypt(it.toString()) + ) } + /** 회원 ID를 암호화하여 토큰으로 사용 */ + val token = idEncryption.encrypt(memberId.toString()) + runCatching { sendAuthEmailService.send( SendAuthEmailArgs( @@ -90,7 +50,7 @@ class SaveMemberUseCase( content = Content( headComment = headComment, subComment = subComment, - email = email, + email = useCaseIn.email, confirmLink = URL("https://www.fewletter.com/auth/validation/complete?auth_token=$token") ) ) diff --git a/api/src/main/kotlin/com/few/api/domain/member/usecase/dto/SaveMemberTxCaseIn.kt b/api/src/main/kotlin/com/few/api/domain/member/usecase/dto/SaveMemberTxCaseIn.kt new file mode 100644 index 000000000..2e8d2f756 --- /dev/null +++ b/api/src/main/kotlin/com/few/api/domain/member/usecase/dto/SaveMemberTxCaseIn.kt @@ -0,0 +1,8 @@ +package com.few.api.domain.member.usecase.dto + +import com.few.api.repo.dao.member.record.MemberIdAndIsDeletedRecord + +data class SaveMemberTxCaseIn( + val record: MemberIdAndIsDeletedRecord?, + val email: String, +) \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/member/usecase/dto/SaveMemberTxCaseOut.kt b/api/src/main/kotlin/com/few/api/domain/member/usecase/dto/SaveMemberTxCaseOut.kt new file mode 100644 index 000000000..acda7c26d --- /dev/null +++ b/api/src/main/kotlin/com/few/api/domain/member/usecase/dto/SaveMemberTxCaseOut.kt @@ -0,0 +1,7 @@ +package com.few.api.domain.member.usecase.dto + +data class SaveMemberTxCaseOut( + val headComment: String, + val subComment: String, + val memberId: Long, +) \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/member/usecase/transaction/SaveMemberTxCase.kt b/api/src/main/kotlin/com/few/api/domain/member/usecase/transaction/SaveMemberTxCase.kt new file mode 100644 index 000000000..f3a564a90 --- /dev/null +++ b/api/src/main/kotlin/com/few/api/domain/member/usecase/transaction/SaveMemberTxCase.kt @@ -0,0 +1,76 @@ +package com.few.api.domain.member.usecase.transaction + +import com.few.api.domain.member.usecase.dto.SaveMemberTxCaseIn +import com.few.api.domain.member.usecase.dto.SaveMemberTxCaseOut +import com.few.api.exception.common.InsertException +import com.few.api.repo.dao.member.MemberDao +import com.few.api.repo.dao.member.command.InsertMemberCommand +import com.few.api.repo.dao.member.command.UpdateDeletedMemberTypeCommand +import com.few.api.repo.dao.member.record.MemberIdAndIsDeletedRecord +import com.few.data.common.code.MemberType +import org.springframework.stereotype.Component +import org.springframework.transaction.annotation.Transactional +import java.util.* + +@Component +class SaveMemberTxCase( + private val memberDao: MemberDao, +) { + companion object { + private const val AUTH_HEAD_COMMENT = "few 로그인 링크입니다." + private const val AUTH_SUB_COMMENT = "로그인하시려면 아래 버튼을 눌러주세요!" + private const val SIGNUP_HEAD_COMMENT = "few에 가입해주셔서 감사합니다." + private const val SIGNUP_SUB_COMMENT = "가입하신 이메일 주소를 확인해주세요." + } + + @Transactional + fun execute(dto: SaveMemberTxCaseIn): SaveMemberTxCaseOut { + return dto.record?.let { signUpBeforeMember -> + signUpBeforeMember.takeIf { it.isDeleted }?.let { + ifDeletedMember(it.memberId) + } ?: ifMember(signUpBeforeMember) + } ?: ifNewMember(dto.email) + } + + private fun ifDeletedMember(memberId: Long): SaveMemberTxCaseOut { + memberDao.updateMemberType( + UpdateDeletedMemberTypeCommand( + id = memberId, + memberType = MemberType.PREAUTH + ) + ).also { + if (it != 1L) { + throw InsertException("member.updatefail.record") + } + } + + return SaveMemberTxCaseOut( + headComment = AUTH_HEAD_COMMENT, + subComment = AUTH_SUB_COMMENT, + memberId = memberId + ) + } + + private fun ifMember(signUpBeforeMember: MemberIdAndIsDeletedRecord): SaveMemberTxCaseOut { + return SaveMemberTxCaseOut( + headComment = AUTH_HEAD_COMMENT, + subComment = AUTH_SUB_COMMENT, + memberId = signUpBeforeMember.memberId + ) + } + + private fun ifNewMember(email: String): SaveMemberTxCaseOut { + return memberDao.insertMember( + InsertMemberCommand( + email = email, + memberType = MemberType.PREAUTH + ) + )?.let { + SaveMemberTxCaseOut( + headComment = SIGNUP_HEAD_COMMENT, + subComment = SIGNUP_SUB_COMMENT, + memberId = it + ) + } ?: throw InsertException("member.insertfail.record") + } +} \ No newline at end of file diff --git a/api/src/test/kotlin/com/few/api/domain/member/usecase/SaveMemberUseCaseTest.kt b/api/src/test/kotlin/com/few/api/domain/member/usecase/SaveMemberUseCaseTest.kt index 84a483522..36ace2ed8 100644 --- a/api/src/test/kotlin/com/few/api/domain/member/usecase/SaveMemberUseCaseTest.kt +++ b/api/src/test/kotlin/com/few/api/domain/member/usecase/SaveMemberUseCaseTest.kt @@ -1,9 +1,10 @@ package com.few.api.domain.member.usecase import com.few.api.config.crypto.IdEncryption +import com.few.api.domain.member.usecase.dto.SaveMemberTxCaseOut import com.few.api.domain.member.usecase.dto.SaveMemberUseCaseIn +import com.few.api.domain.member.usecase.transaction.SaveMemberTxCase import com.few.api.repo.dao.member.MemberDao -import com.few.api.repo.dao.member.command.UpdateDeletedMemberTypeCommand import com.few.api.repo.dao.member.query.SelectMemberByEmailNotConsiderDeletedAtQuery import com.few.api.repo.dao.member.record.MemberIdAndIsDeletedRecord import com.few.email.service.member.SendAuthEmailService @@ -19,13 +20,15 @@ class SaveMemberUseCaseTest : BehaviorSpec({ lateinit var memberDao: MemberDao lateinit var sendAuthEmailService: SendAuthEmailService lateinit var idEncryption: IdEncryption + lateinit var saveMemberTxCase: SaveMemberTxCase lateinit var useCase: SaveMemberUseCase beforeContainer { memberDao = mockk() sendAuthEmailService = mockk() idEncryption = mockk() - useCase = SaveMemberUseCase(memberDao, sendAuthEmailService, idEncryption) + saveMemberTxCase = mockk() + useCase = SaveMemberUseCase(memberDao, sendAuthEmailService, idEncryption, saveMemberTxCase) } given("회원가입/로그인 요청이 온 상황에서") { @@ -35,7 +38,11 @@ class SaveMemberUseCaseTest : BehaviorSpec({ `when`("요청의 이메일이 가입 이력이 없는 경우") { every { memberDao.selectMemberByEmail(any(SelectMemberByEmailNotConsiderDeletedAtQuery::class)) } returns null - every { memberDao.insertMember(any()) } returns 1L + every { saveMemberTxCase.execute(any()) } returns SaveMemberTxCaseOut( + headComment = "headComment", + subComment = "subComment", + memberId = 1L + ) val token = "encryptedToken" every { idEncryption.encrypt(any()) } returns token @@ -47,8 +54,7 @@ class SaveMemberUseCaseTest : BehaviorSpec({ useCaseOut.isSendAuthEmail shouldBe true verify(exactly = 1) { memberDao.selectMemberByEmail(any(SelectMemberByEmailNotConsiderDeletedAtQuery::class)) } - verify(exactly = 1) { memberDao.insertMember(any()) } - verify(exactly = 0) { memberDao.updateMemberType(any(UpdateDeletedMemberTypeCommand::class)) } + verify(exactly = 1) { saveMemberTxCase.execute(any()) } verify(exactly = 1) { idEncryption.encrypt(any()) } verify(exactly = 1) { sendAuthEmailService.send(any()) } } @@ -61,6 +67,12 @@ class SaveMemberUseCaseTest : BehaviorSpec({ isDeleted = false ) + every { saveMemberTxCase.execute(any()) } returns SaveMemberTxCaseOut( + headComment = "headComment", + subComment = "subComment", + memberId = 1L + ) + val token = "encryptedToken" every { idEncryption.encrypt(any()) } returns token @@ -71,8 +83,7 @@ class SaveMemberUseCaseTest : BehaviorSpec({ useCaseOut.isSendAuthEmail shouldBe true verify(exactly = 1) { memberDao.selectMemberByEmail(any(SelectMemberByEmailNotConsiderDeletedAtQuery::class)) } - verify(exactly = 0) { memberDao.insertMember(any()) } - verify(exactly = 0) { memberDao.updateMemberType(any(UpdateDeletedMemberTypeCommand::class)) } + verify(exactly = 1) { saveMemberTxCase.execute(any()) } verify(exactly = 1) { idEncryption.encrypt(any()) } verify(exactly = 1) { sendAuthEmailService.send(any()) } } @@ -85,7 +96,11 @@ class SaveMemberUseCaseTest : BehaviorSpec({ isDeleted = true ) - every { memberDao.updateMemberType(any(UpdateDeletedMemberTypeCommand::class)) } returns memberId + every { saveMemberTxCase.execute(any()) } returns SaveMemberTxCaseOut( + headComment = "headComment", + subComment = "subComment", + memberId = 1L + ) val token = "encryptedToken" every { idEncryption.encrypt(any()) } returns token @@ -96,9 +111,8 @@ class SaveMemberUseCaseTest : BehaviorSpec({ val useCaseOut = useCase.execute(useCaseIn) useCaseOut.isSendAuthEmail shouldBe true - verify(exactly = 2) { memberDao.selectMemberByEmail(any(SelectMemberByEmailNotConsiderDeletedAtQuery::class)) } - verify(exactly = 0) { memberDao.insertMember(any()) } - verify(exactly = 1) { memberDao.updateMemberType(any(UpdateDeletedMemberTypeCommand::class)) } + verify(exactly = 1) { memberDao.selectMemberByEmail(any(SelectMemberByEmailNotConsiderDeletedAtQuery::class)) } + verify(exactly = 1) { saveMemberTxCase.execute(any()) } verify(exactly = 1) { idEncryption.encrypt(any()) } verify(exactly = 1) { sendAuthEmailService.send(any()) } } @@ -107,8 +121,11 @@ class SaveMemberUseCaseTest : BehaviorSpec({ `when`("인증 이메일 발송에 실패한 경우") { every { memberDao.selectMemberByEmail(any(SelectMemberByEmailNotConsiderDeletedAtQuery::class)) } returns null - val memberId = 1L - every { memberDao.insertMember(any()) } returns memberId + every { saveMemberTxCase.execute(any()) } returns SaveMemberTxCaseOut( + headComment = "headComment", + subComment = "subComment", + memberId = 1L + ) val token = "encryptedToken" every { idEncryption.encrypt(any()) } returns token