diff --git a/src/main/kotlin/apply/application/EvaluationDtos.kt b/src/main/kotlin/apply/application/EvaluationDtos.kt index f8f079063..d7b7f6170 100644 --- a/src/main/kotlin/apply/application/EvaluationDtos.kt +++ b/src/main/kotlin/apply/application/EvaluationDtos.kt @@ -171,10 +171,11 @@ data class EvaluationTargetData( data class MailTargetResponse( val email: String, - val name: String? = null + val name: String? = null, + val id: Long, ) { - constructor(memberResponse: MemberResponse) : this(memberResponse.email, memberResponse.name) - constructor(member: Member) : this(member.email, member.name) + constructor(memberResponse: MemberResponse) : this(memberResponse.email, memberResponse.name, memberResponse.id) + constructor(member: Member) : this(member.email, member.name, member.id) } data class EvaluationItemScoreData( diff --git a/src/main/kotlin/apply/application/MailTargetService.kt b/src/main/kotlin/apply/application/MailTargetService.kt index 2d0d0a630..85604cf72 100644 --- a/src/main/kotlin/apply/application/MailTargetService.kt +++ b/src/main/kotlin/apply/application/MailTargetService.kt @@ -4,7 +4,6 @@ import apply.domain.evaluationtarget.EvaluationStatus import apply.domain.evaluationtarget.EvaluationTarget import apply.domain.evaluationtarget.EvaluationTargetRepository import apply.domain.member.MemberRepository -import apply.domain.member.findAllByEmailIn import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional @@ -17,13 +16,12 @@ class MailTargetService( fun findMailTargets(evaluationId: Long, evaluationStatus: EvaluationStatus? = null): List { val memberIds = findEvaluationTargets(evaluationId, evaluationStatus).map { it.memberId } return memberRepository.findAllById(memberIds) - .map { MailTargetResponse(it.email, it.name) } + .map { MailTargetResponse(it) } } - fun findAllByEmails(emails: List): List { - val members = memberRepository.findAllByEmailIn(emails) - val anonymousEmails = emails - members.map { it.email } - return members.map { MailTargetResponse(it) } + anonymousEmails.map { MailTargetResponse(it) } + fun findAllByMemberIds(memberIds: List): List { + val members = memberRepository.findAllById(memberIds) + return members.map { MailTargetResponse(it) } } private fun findEvaluationTargets(evaluationId: Long, evaluationStatus: EvaluationStatus?): List { diff --git a/src/main/kotlin/apply/application/mail/MailData.kt b/src/main/kotlin/apply/application/mail/MailData.kt index 392f38225..057c0d3a1 100644 --- a/src/main/kotlin/apply/application/mail/MailData.kt +++ b/src/main/kotlin/apply/application/mail/MailData.kt @@ -20,7 +20,7 @@ data class MailData( var sender: String = "", @field:NotEmpty - var recipients: List = emptyList(), + var recipients: List = emptyList(), @field:NotNull var sentTime: LocalDateTime = LocalDateTime.now(), diff --git a/src/main/kotlin/apply/application/mail/MailService.kt b/src/main/kotlin/apply/application/mail/MailService.kt index 6bb4230a4..b684f6bb7 100644 --- a/src/main/kotlin/apply/application/mail/MailService.kt +++ b/src/main/kotlin/apply/application/mail/MailService.kt @@ -91,7 +91,7 @@ class MailService( @Async fun sendMailsByBcc(request: MailData, files: Map) { val body = generateMailBody(request) - val recipients = request.recipients + mailProperties.username + val recipients = memberRepository.findAllById(request.recipients).map { it.email } + mailProperties.username // TODO: 성공과 실패를 분리하여 히스토리 관리 val succeeded = mutableListOf() diff --git a/src/main/kotlin/apply/config/DatabaseInitializer.kt b/src/main/kotlin/apply/config/DatabaseInitializer.kt index 61fb724e8..ad35f8ea1 100644 --- a/src/main/kotlin/apply/config/DatabaseInitializer.kt +++ b/src/main/kotlin/apply/config/DatabaseInitializer.kt @@ -449,7 +449,7 @@ class DatabaseInitializer( subject = "[우아한테크코스] 프리코스를 진행하는 목적과 사전 준비", body = "안녕하세요.", sender = "woowa_course@woowahan.com", - recipients = listOf("a@email.com", "b@email.com", "c@email.com", "d@email.com"), + recipients = listOf(1L, 2L, 3L, 4L), sentTime = createLocalDateTime(2020, 11, 5, 10) ) ) diff --git a/src/main/kotlin/apply/domain/mail/MailHistory.kt b/src/main/kotlin/apply/domain/mail/MailHistory.kt index 40f9e8c12..fd8ff6c0e 100644 --- a/src/main/kotlin/apply/domain/mail/MailHistory.kt +++ b/src/main/kotlin/apply/domain/mail/MailHistory.kt @@ -1,7 +1,7 @@ package apply.domain.mail import support.domain.BaseEntity -import support.domain.StringToListConverter +import support.domain.StringToLongListConverter import java.time.LocalDateTime import javax.persistence.Column import javax.persistence.Convert @@ -21,9 +21,9 @@ class MailHistory( val sender: String, @Column(nullable = false) - @Convert(converter = StringToListConverter::class) + @Convert(converter = StringToLongListConverter::class) @Lob - val recipients: List, + val recipients: List, @Column(nullable = false) val sentTime: LocalDateTime = LocalDateTime.now(), diff --git a/src/main/kotlin/apply/ui/admin/mail/MailForm.kt b/src/main/kotlin/apply/ui/admin/mail/MailForm.kt index 9c9a1f3c7..aea7534f7 100644 --- a/src/main/kotlin/apply/ui/admin/mail/MailForm.kt +++ b/src/main/kotlin/apply/ui/admin/mail/MailForm.kt @@ -23,7 +23,6 @@ import org.springframework.core.io.ByteArrayResource import support.views.BindingFormLayout import support.views.NO_NAME import support.views.addSortableColumn -import support.views.createEnterBox import support.views.createErrorSmallButton import support.views.createNormalButton import support.views.createUpload @@ -43,6 +42,7 @@ class MailForm( private val mailTargetsGrid: Grid = createMailTargetsGrid(mailTargets) private val recipientFilter: Component = createRecipientFilter() private val fileUpload: Upload = createFileUpload() + private var mailData: MailData? = null init { add(subject, sender, recipientFilter, mailTargetsGrid, body, fileUpload) @@ -60,20 +60,11 @@ class MailForm( private fun createRecipientFilter(): Component { return HorizontalLayout( - createTargetEnterBox(), createIndividualLoadButton(), createGroupLoadButton() ).apply { defaultVerticalComponentAlignment = FlexComponent.Alignment.END } } - private fun createTargetEnterBox(): Component { - return createEnterBox("받는사람") { - if (it.isNotBlank()) { - refreshGrid { mailTargets.add(MailTargetResponse(it, NO_NAME)) } - } - } - } - private fun createIndividualLoadButton(): Button { return createNormalButton("개별 불러오기") { IndividualMailTargetDialog(memberService) { @@ -133,15 +124,16 @@ class MailForm( return null } return bindDefaultOrNull()?.apply { - recipients = mailTargets.map { it.email }.toList() + recipients = mailTargets.map { it.id }.toList() attachments = uploadFile } } override fun fill(data: MailData) { + mailData = data fillDefault(data) toReadOnlyMode() - refreshGrid { mailTargets.addAll(mailTargetService.findAllByEmails(data.recipients)) } + refreshGrid { mailTargets.addAll(mailTargetService.findAllByMemberIds(data.recipients)) } } private fun toReadOnlyMode() { diff --git a/src/main/kotlin/support/domain/StringToListConverter.kt b/src/main/kotlin/support/domain/StringToListConverter.kt index f375d0a1f..3e26f1b44 100644 --- a/src/main/kotlin/support/domain/StringToListConverter.kt +++ b/src/main/kotlin/support/domain/StringToListConverter.kt @@ -2,18 +2,24 @@ package support.domain import javax.persistence.AttributeConverter import javax.persistence.Converter +import kotlin.reflect.KClass -@Converter -class StringToListConverter : AttributeConverter, String> { - override fun convertToDatabaseColumn(recipients: List): String { - return recipients.joinToString(COMMA) +abstract class StringToListConverter( + private val type: KClass, + private val transform: (String) -> T, +) : AttributeConverter, String> { + override fun convertToDatabaseColumn(attribute: List): String { + return attribute.joinToString(COMMA) } - override fun convertToEntityAttribute(dbData: String): List { - return dbData.split(COMMA) + override fun convertToEntityAttribute(dbData: String): List { + return dbData.split(COMMA).map { transform(it) } } companion object { private const val COMMA: String = "," } } + +@Converter +class StringToLongListConverter : StringToListConverter(Long::class, String::toLong) diff --git a/src/test/kotlin/apply/MailHistoryFixtures.kt.kt b/src/test/kotlin/apply/MailHistoryFixtures.kt.kt index fa828d416..3417ff3e4 100644 --- a/src/test/kotlin/apply/MailHistoryFixtures.kt.kt +++ b/src/test/kotlin/apply/MailHistoryFixtures.kt.kt @@ -7,14 +7,14 @@ import java.time.LocalDateTime private const val SUBJECT: String = "메일제목" private const val BODY: String = "메일 본문 입니다." private const val SENDER: String = "woowacourse@email.com" -private val RECIPIENTS: List = listOf("test1@email.com", "test2@email.com") +private val RECIPIENTS: List = listOf(1L, 2L) private val SENT_TIME: LocalDateTime = LocalDateTime.now() fun createMailHistory( subject: String = SUBJECT, body: String = BODY, sender: String = SENDER, - recipients: List = RECIPIENTS, + recipients: List = RECIPIENTS, sentTime: LocalDateTime = SENT_TIME, id: Long = 0L ): MailHistory { @@ -25,7 +25,7 @@ fun createMailData( subject: String = SUBJECT, body: String = BODY, sender: String = SENDER, - recipients: List = RECIPIENTS, + recipients: List = RECIPIENTS, sentTime: LocalDateTime = SENT_TIME, id: Long = 0L ): MailData { diff --git a/src/test/kotlin/apply/application/MailTargetServiceTest.kt b/src/test/kotlin/apply/application/MailTargetServiceTest.kt index 5da258a2f..0708192e1 100644 --- a/src/test/kotlin/apply/application/MailTargetServiceTest.kt +++ b/src/test/kotlin/apply/application/MailTargetServiceTest.kt @@ -10,7 +10,6 @@ import apply.domain.evaluationtarget.EvaluationStatus.PENDING import apply.domain.evaluationtarget.EvaluationStatus.WAITING import apply.domain.evaluationtarget.EvaluationTargetRepository import apply.domain.member.MemberRepository -import apply.domain.member.findAllByEmailIn import io.kotest.core.spec.style.BehaviorSpec import io.kotest.matchers.collections.shouldBeEmpty import io.kotest.matchers.collections.shouldContainExactlyInAnyOrder @@ -69,7 +68,7 @@ class MailTargetServiceTest : BehaviorSpec({ Then("평가 대상자의 이름 및 이메일을 확인할 수 있다") { actual shouldHaveSize 1 - actual[0] shouldBe MailTargetResponse(member.email, member.name) + actual[0] shouldBe MailTargetResponse(member) } } } @@ -88,7 +87,7 @@ class MailTargetServiceTest : BehaviorSpec({ Then("평가 대상자의 이름 및 이메일을 확인할 수 있다") { actual shouldHaveSize 1 - actual[0] shouldBe MailTargetResponse(member.email, member.name) + actual[0] shouldBe MailTargetResponse(member) } } } @@ -107,7 +106,7 @@ class MailTargetServiceTest : BehaviorSpec({ Then("평가 대상자의 이름 및 이메일을 확인할 수 있다") { actual shouldHaveSize 1 - actual[0] shouldBe MailTargetResponse(member.email, member.name) + actual[0] shouldBe MailTargetResponse(member) } } } @@ -141,16 +140,21 @@ class MailTargetServiceTest : BehaviorSpec({ } } - Given("특정 이메일을 가진 회원이 없는 경우") { - val email = "test1@email.com" + // TODO[#754]: 탈퇴한 회원의 경우 default 정보가 노출된다. + Given("메일 이력을 통해 회원 id 목록을 확인할 수 있는 경우") { + val members = listOf( + createMember(id = 1L), + createMember(id = 2L), + createMember(id = 3L) + ) - every { memberRepository.findAllByEmailIn(any()) } returns emptyList() + every { memberRepository.findAllById(any()) } returns members - When("해당 이메일로 이메일 정보를 조회하면") { - val actual = mailTargetService.findAllByEmails(listOf(email)) + When("회원 id를 사용해 메일 수신자 정보를 조회하면") { + val actual = mailTargetService.findAllByMemberIds(listOf(1L, 2L, 3L, 4L)) - Then("이름이 비어있는 것을 확인할 수 있다") { - actual[0] shouldBe MailTargetResponse(email, null) + Then("현재 회원인 메일 수신자만 확인할 수 있다") { + actual shouldHaveSize 3 } } } diff --git a/src/test/kotlin/apply/ui/api/EvaluationTargetRestControllerTest.kt b/src/test/kotlin/apply/ui/api/EvaluationTargetRestControllerTest.kt index abdd283c7..4337ec0cc 100644 --- a/src/test/kotlin/apply/ui/api/EvaluationTargetRestControllerTest.kt +++ b/src/test/kotlin/apply/ui/api/EvaluationTargetRestControllerTest.kt @@ -121,7 +121,7 @@ class EvaluationTargetRestControllerTest : RestControllerTest() { @EnumSource(names = ["PASS", "FAIL", "WAITING"]) @ParameterizedTest fun `메일 발송 대상(합격자)들의 이메일 정보를 조회한다`(enumStatus: EvaluationStatus) { - val responses = listOf(MailTargetResponse("roki@woowacourse.com", "김로키")) + val responses = listOf(MailTargetResponse("roki@woowacourse.com", "김로키", 1L)) every { mailTargetService.findMailTargets(any(), any()) } returns responses mockMvc.get("/api/recruitments/{recruitmentId}/evaluations/{evaluationId}/targets/emails", 1L, 1L) {