Skip to content

Commit

Permalink
[YS-268] 매칭 공고 전송 수동 트리거 API 구현 & 공고 memberName 변경 (#83)
Browse files Browse the repository at this point in the history
  • Loading branch information
chock-cho authored Feb 6, 2025
1 parent c581e74 commit f3eafa3
Show file tree
Hide file tree
Showing 14 changed files with 158 additions and 8 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.dobby.backend.application.service

import com.dobby.backend.application.usecase.quartz.TriggerSchedulerUseCase
import jakarta.transaction.Transactional
import org.springframework.stereotype.Service

@Service
class SchedulerService(
private val schedulerTriggerUseCase: TriggerSchedulerUseCase
) {
@Transactional
fun triggerSendMatchingEmailJob() {
schedulerTriggerUseCase.execute(TriggerSchedulerUseCase.Input("matching_email_send_job"))
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package com.dobby.backend.application.usecase.member.email

import com.dobby.backend.application.usecase.UseCase
import com.dobby.backend.domain.exception.ContactEmailDuplicateException
import com.dobby.backend.domain.exception.EmailDomainNotFoundException
import com.dobby.backend.domain.gateway.UrlGeneratorGateway
import com.dobby.backend.domain.gateway.email.EmailGateway
import com.dobby.backend.domain.gateway.member.MemberGateway
import com.dobby.backend.domain.model.experiment.ExperimentPost
import com.dobby.backend.util.EmailUtils
import java.time.LocalDate
Expand All @@ -12,7 +14,8 @@ import java.time.format.DateTimeFormatter

class SendMatchingEmailUseCase(
private val emailGateway: EmailGateway,
private val urlGeneratorGateway: UrlGeneratorGateway
private val urlGeneratorGateway: UrlGeneratorGateway,
private val memberGateway: MemberGateway
): UseCase<SendMatchingEmailUseCase.Input, SendMatchingEmailUseCase.Output>{

data class Input(
Expand All @@ -28,8 +31,9 @@ class SendMatchingEmailUseCase(

override fun execute(input: Input): Output {
validateEmail(input.contactEmail)

val (title, content) = getFormattedEmail(input.contactEmail, input.experimentPosts)
val member = memberGateway.findByContactEmail(input.contactEmail)
?: throw ContactEmailDuplicateException
val (title, content) = getFormattedEmail(member.name, input.experimentPosts)

return try {
emailGateway.sendEmail(input.contactEmail, title, content)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.dobby.backend.application.usecase.quartz

import com.dobby.backend.application.usecase.UseCase
import com.dobby.backend.domain.gateway.SchedulerTriggerGateway

class TriggerSchedulerUseCase(
private val schedulerTriggerGateway: SchedulerTriggerGateway
): UseCase<TriggerSchedulerUseCase.Input, TriggerSchedulerUseCase.Output> {

data class Input(
val jobName: String,
val jobGroup: String = "DEFAULT"
)

object Output

override fun execute(input: Input): Output {
schedulerTriggerGateway.triggerJob(input.jobName, input.jobGroup)
return Output
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.dobby.backend.domain.gateway

interface SchedulerTriggerGateway {
fun triggerJob(jobName: String, jobGroup: String)
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ interface MemberGateway {
fun save(savedMember: Member) : Member
fun existsByContactEmail(contactEmail: String) : Boolean
fun findContactEmailByMemberId(memberId: String): String
fun findByContactEmail(contactEmail: String): Member?
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ class SchedulerConfig {
return TriggerBuilder.newTrigger()
.forJob(sendEmailMatchingJobDetail())
.withIdentity("send_matching_email_trigger", "DEFAULT")
.startNow()
.withSchedule(
CronScheduleBuilder.dailyAtHourAndMinute(8,0)
.inTimeZone(TimeZone.getTimeZone("Asia/Seoul"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ interface MemberRepository : JpaRepository<MemberEntity, String> {
fun findByOauthEmailAndStatus(oauthEmail: String, status: MemberStatus): MemberEntity?
fun existsByContactEmail(contactEmail: String): Boolean
fun findContactEmailById(memberId: String): String
fun findByContactEmail(contactEmail: String): MemberEntity?
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.dobby.backend.infrastructure.gateway

import com.dobby.backend.domain.gateway.SchedulerTriggerGateway
import org.quartz.Scheduler
import org.quartz.JobKey
import org.springframework.stereotype.Component

@Component
class SchedulerTriggerGatewayImpl(
private val scheduler: Scheduler
) : SchedulerTriggerGateway {
override fun triggerJob(jobName: String, jobGroup: String) {
val jobKey = JobKey.jobKey(jobName, jobGroup)
if (scheduler.checkExists(jobKey)) {
scheduler.triggerJob(jobKey)
} else {
throw IllegalArgumentException("Job with name $jobName and group $jobGroup does not exist.")
}
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,10 @@ class MemberGatewayImpl(
override fun findContactEmailByMemberId(memberId: String): String {
return memberRepository.findContactEmailById(memberId)
}

override fun findByContactEmail(contactEmail: String): Member? {
return memberRepository
.findByContactEmail(contactEmail)
?.let(MemberEntity::toDomain)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class WebSecurityConfig {
handlerExceptionResolver: HandlerExceptionResolver
): SecurityFilterChain = httpSecurity
.securityMatcher("/v1/auth/**",
"/v1/members/signup/**", "/v1/emails/**",
"/v1/members/signup/**", "/v1/emails/**", "/v1/scheduler/**",
"/v1/experiment-posts/counts", "/v1/experiment-posts/search", "/v1/experiment-posts/{postId}/apply-method")
.csrf { it.disable() }
.cors(Customizer.withDefaults())
Expand All @@ -41,7 +41,7 @@ class WebSecurityConfig {
.authorizeHttpRequests {
it.requestMatchers(
"/v1/auth/**",
"/v1/members/signup/**", "/v1/emails/**",
"/v1/members/signup/**", "/v1/emails/**", "/v1/scheduler/**",
"/v1/experiment-posts/counts", "/v1/experiment-posts/search", "/v1/experiment-posts/{postId}/apply-method"
).permitAll()
it.anyRequest().authenticated()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.dobby.backend.presentation.api.controller

import com.dobby.backend.application.service.SchedulerService
import io.swagger.v3.oas.annotations.Operation
import io.swagger.v3.oas.annotations.tags.Tag
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController

@Tag(name = "스케줄러 수동 트리거 API - /v1/scheduler")
@RestController
@RequestMapping("/v1/scheduler")
class SchedulerController(
private val schedulerService: SchedulerService
) {
@GetMapping("/email-trigger")
@Operation(
summary = "매칭 공고 이메일 전송 수동 트리거 API",
description = "이메일 UT 검증을 위한 테스트 API입니다. 실제 비즈니스 로직이 아니기에, 추후 조정될 여지가 있습니다."
)
fun triggerSendMatchingEmailJob() {
schedulerService.triggerSendMatchingEmailJob()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import io.kotest.core.spec.style.BehaviorSpec
import io.kotest.matchers.shouldBe
import io.mockk.*

class EmailCodeSendUseCaseTest : BehaviorSpec({
class SendEmailCodeUseCaseTest : BehaviorSpec({

val verificationGateway: VerificationGateway = mockk()
val emailGateway: EmailGateway = mockk()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import com.dobby.backend.infrastructure.database.entity.enums.areaInfo.Region
import com.dobby.backend.domain.exception.EmailDomainNotFoundException
import com.dobby.backend.domain.gateway.UrlGeneratorGateway
import com.dobby.backend.domain.gateway.email.EmailGateway
import com.dobby.backend.domain.gateway.member.MemberGateway
import com.dobby.backend.domain.model.experiment.ExperimentPost
import io.kotest.core.spec.style.BehaviorSpec
import io.kotest.matchers.shouldBe
Expand All @@ -20,11 +21,12 @@ import io.mockk.verify
import java.time.LocalDate
import java.time.LocalDateTime

class EmailMatchSendUseCaseTest : BehaviorSpec({
class SendMatchingEmailUseCaseTest : BehaviorSpec({

val emailGateway = mockk<EmailGateway>(relaxed = true)
val memberGateway = mockk<MemberGateway>(relaxed = true)
val urlGeneratorGateway = mockk<UrlGeneratorGateway>(relaxed = true)
val sendMatchingEmailUseCase = SendMatchingEmailUseCase(emailGateway, urlGeneratorGateway)
val sendMatchingEmailUseCase = SendMatchingEmailUseCase(emailGateway, urlGeneratorGateway, memberGateway)

given("이메일 매칭 발송을 실행할 때") {

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.dobby.backend.application.usecase.quartz

import com.dobby.backend.domain.gateway.SchedulerTriggerGateway
import io.kotest.core.spec.style.BehaviorSpec
import io.kotest.matchers.shouldBe
import io.mockk.*

class TriggerSchedulerUseCaseTest : BehaviorSpec({

// Mock 객체 생성
val schedulerTriggerGateway = mockk<SchedulerTriggerGateway>(relaxed = true)

// UseCase 생성
val triggerSchedulerUseCase = TriggerSchedulerUseCase(schedulerTriggerGateway)

beforeTest {
clearMocks(schedulerTriggerGateway)
}

given("스케줄러 트리거 실행 요청이 들어왔을 때") {

`when`("유효한 jobName과 jobGroup을 전달하면") {
val input = TriggerSchedulerUseCase.Input(jobName = "testJob", jobGroup = "testGroup")

every { schedulerTriggerGateway.triggerJob(input.jobName, input.jobGroup) } just Runs // Void method

then("스케줄러 트리거가 정상적으로 실행되어야 한다") {
val output = triggerSchedulerUseCase.execute(input)

output shouldBe TriggerSchedulerUseCase.Output
verify(exactly = 1) { schedulerTriggerGateway.triggerJob(input.jobName, input.jobGroup) }
}
}

`when`("jobGroup을 지정하지 않고 jobName만 전달하면") {
val input = TriggerSchedulerUseCase.Input(jobName = "testJob")

every { schedulerTriggerGateway.triggerJob(input.jobName, "DEFAULT") } just Runs

then("기본 jobGroup인 'DEFAULT'로 실행되어야 한다") {
val output = triggerSchedulerUseCase.execute(input)

output shouldBe TriggerSchedulerUseCase.Output
verify(exactly = 1) { schedulerTriggerGateway.triggerJob(input.jobName, "DEFAULT") }
}
}
}
})

0 comments on commit f3eafa3

Please sign in to comment.