Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .kotlin/errors/errors-1727090346428.log
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
kotlin version: 2.0.20
error message: The daemon has terminated unexpectedly on startup attempt #1 with error code: 0. The daemon process output:
1. Kotlin compile daemon is ready

2 changes: 2 additions & 0 deletions api/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ dependencies {
implementation(project(":applicant:applicant-jpa-adapter"))
implementation(project(":applicant:applicant-web-adapter"))
implementation(project(":applicant:applicant-s3-adapter"))
implementation(project(":applicant:applicant-redis-adapter"))
implementation(project(":applicant:applicant-smtp-adapter"))

implementation(project(":admission:admission-application"))
implementation(project(":admission:admission-jpa-adapter"))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.daegusw.apply.applicant.application.port.`in`.web

interface SmtpUseCase {
suspend fun verify(email: String, code: String): String
suspend fun send(email: String): String
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.daegusw.apply.applicant.application.port.out.redis

interface RedisPort {
fun save(key: String, value: String, expiration: Long)
fun delete(key: String)
fun get(key: String): String?
fun update(key: String, value: String)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.daegusw.apply.applicant.application.port.out.smtp

interface SmtpPort {
suspend fun send(e: String) : String
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.daegusw.apply.applicant.application.service

import com.daegusw.apply.applicant.application.port.`in`.web.SmtpUseCase
import com.daegusw.apply.applicant.application.port.out.redis.RedisPort
import com.daegusw.apply.applicant.application.port.out.smtp.SmtpPort
import org.springframework.stereotype.Service

@Service
class SmtpService(
private val stmpPort: SmtpPort,
private val redisPort: RedisPort,
) : SmtpUseCase {

override suspend fun verify(email: String, code: String): String {
val value = redisPort.get(email)
if((value != null) && value == code){
redisPort.update(email, "verified")
return "verified"
}
throw RuntimeException("Smtp verification failed")
}

override suspend fun send(email: String): String {
val code = stmpPort.send(email)
redisPort.save(email,code,3L) //3분
return "smtp success"
}

}
4 changes: 4 additions & 0 deletions applicant/applicant-redis-adapter/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
dependencies {
implementation("org.springframework.boot:spring-boot-starter-data-redis")
implementation(project(":applicant:applicant-application"))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.daegusw.apply.applicant.redis.adapter

import com.daegusw.apply.applicant.application.port.out.redis.RedisPort
import org.springframework.data.redis.core.RedisTemplate
import org.springframework.stereotype.Component
import java.time.Duration

@Component
class RedisAdapter(
private val redisTemplate: RedisTemplate<String, String>,
) : RedisPort {
override fun save(key: String, value: String, expiration: Long) {
try{
redisTemplate.opsForValue().set(key, value, Duration.ofMinutes(expiration))

}catch(e:Exception){
throw e
}
}

override fun delete(key: String) {
try{
redisTemplate.delete(key)
}catch(e:Exception){
throw e
}
}

override fun get(key: String): String? {
try {
return redisTemplate.opsForValue().get(key)
}catch(e:Exception){
throw e
}
}

override fun update(key: String, value: String) {
try{
redisTemplate.opsForValue().set(key, value)
}catch(e:Exception){
throw e
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.daegusw.apply.applicant.redis.adapter.common

import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.data.redis.connection.RedisConnectionFactory
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory
import org.springframework.data.redis.core.RedisTemplate
import org.springframework.data.redis.repository.configuration.EnableRedisRepositories
import org.springframework.data.redis.serializer.StringRedisSerializer

@Configuration
@EnableRedisRepositories
class RedisConfig(
private val redisProperties: RedisProperties,
) {
@Bean
fun redisConnectionFactory(): RedisConnectionFactory {
return LettuceConnectionFactory(redisProperties.host, redisProperties.port)
}

@Bean
fun redisTemplate(): RedisTemplate<*, *> {
return RedisTemplate<Any, Any>().apply {
this.connectionFactory = redisConnectionFactory()
this.keySerializer = StringRedisSerializer()
this.hashKeySerializer = StringRedisSerializer()
this.valueSerializer = StringRedisSerializer()
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.daegusw.apply.applicant.redis.adapter.common

import org.springframework.beans.factory.annotation.Value
import org.springframework.context.annotation.PropertySource
import org.springframework.stereotype.Component

@Component
@PropertySource("classpath:application.yml")
class RedisProperties (
@Value("\${spring.redis.host:host}") val host: String,
@Value("\${spring.redis.port:6379}") val port: Int,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
spring:
redis:
host: ${redis.host}
port: 6379
7 changes: 7 additions & 0 deletions applicant/applicant-smtp-adapter/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
dependencies {
implementation("org.springframework.boot:spring-boot-starter-thymeleaf")
implementation("aws.sdk.kotlin:ses:0.16.0")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4")
implementation ("org.springframework.boot:spring-boot-starter-mail")
implementation(project(":applicant:applicant-application"))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package com.daegusw.apply.applicant.smtp.adapter

import aws.sdk.kotlin.runtime.auth.credentials.StaticCredentialsProvider
import aws.sdk.kotlin.services.ses.SesClient
import aws.sdk.kotlin.services.ses.model.*
import com.daegusw.apply.applicant.application.port.out.smtp.SmtpPort
import com.daegusw.apply.applicant.smtp.adapter.common.ApplicantSmtpProperties
import org.springframework.stereotype.Component
import org.thymeleaf.context.Context
import org.thymeleaf.spring5.SpringTemplateEngine

@Component
class SmtpAdapter(
private val applicantSmtpProperties: ApplicantSmtpProperties,
private val templateEngine: SpringTemplateEngine,
) : SmtpPort {
override suspend fun send(e: String) : String {
try{
val code = (1..5).map { (0..9).random() }.joinToString("")
val context = Context()
context.setVariable("code", code)

val emailRequest = SendEmailRequest {
destination = Destination {
toAddresses = listOf(e)
}

message = Message {
subject = Content {
data = "인증 번호"
}

body = Body {
html = Content {
data = templateEngine.process("code", context)
}
}
}

source = applicantSmtpProperties.sendEmailTo
}

SesClient {
region = applicantSmtpProperties.region

credentialsProvider = StaticCredentialsProvider {
accessKeyId = applicantSmtpProperties.accessKey
secretAccessKey = applicantSmtpProperties.secretKey
}
}.use {
it.sendEmail(emailRequest)
}
return code
}catch (e:Exception){
throw RuntimeException()
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.daegusw.apply.applicant.smtp.adapter.common

import org.springframework.beans.factory.annotation.Value
import org.springframework.context.annotation.PropertySource
import org.springframework.stereotype.Component

@Component
@PropertySource("classpath:application.yml")
class ApplicantSmtpProperties (
@Value("\${cloud.aws.credentials.access-key:access-key}") val accessKey : String,
@Value("\${cloud.aws.credentials.secret-key:secret-key}") val secretKey : String,
@Value("\${cloud.aws.credentials.send-mail-to:send-mail-to}") val sendEmailTo : String,
@Value("\${cloud.aws.region:region}") val region : String,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
cloud:
aws:
credentials:
access-key: ${aws.credentials.accessKey}
secret-key: ${aws.credentials.secretKey}
send-mail-to: ${aws.credentials.sendMail}
region: ${aws.region}
spring:
thymeleaf:
prefix: classpath:/templates
suffix: .html
mode: HTML
encoding: UTF-8
check-template-location: true
cache: false
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
</head>
<body>
<div style="font-family: 'Apple SD Gothic Neo', 'sans-serif' !important; width: 540px; height: 600px; border-top: 4px solid #2A7AF3; margin: 100px auto; padding: 30px 0; box-sizing: border-box;">
<h1 style="margin: 0; padding: 0 5px; font-size: 28px; font-weight: 400;">
<span style="color: #2A7AF3">IDA 인증번호</span> 안내입니다.
</h1>
<p style="font-size: 16px; line-height: 26px; margin-top: 20px; padding: 0 5px;">
아래 <b style="color: #2A7AF3">인증번호</b>를 <b>3분내로</b> 입력해주세요.
</p>

<div style="color: #FFF; text-decoration: none; text-align: center;">
<p style="display: inline-block; width: 210px; height: 45px; margin: 30px 5px 40px; background: #2A7AF3; line-height: 45px; vertical-align: middle; font-size: 16px;" th:text="${code}"></p>
</div>

<div style="border-top: 1px solid #DDD; padding: 5px;">
<p style="font-size: 13px; line-height: 21px; color: #555;">
아무에게도 이 번호를 공유하지마세요. 회원 정보가 유출되는 문제가 발생 할 수 있습니다.<br/>
</p>
</div>
</div>
</body>
</html>
1 change: 1 addition & 0 deletions applicant/applicant-web-adapter/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ dependencies {

implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-validation")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.daegusw.apply.applicant.web.adapter.api

import com.daegusw.apply.applicant.application.port.`in`.web.SmtpUseCase
import com.daegusw.apply.applicant.web.adapter.api.request.SmtpRequest
import com.daegusw.apply.applicant.web.adapter.api.response.SmtpResponse
import org.springframework.http.HttpStatus
import org.springframework.web.bind.annotation.*
import javax.validation.Valid
import javax.validation.constraints.NotEmpty

@RestController
@RequestMapping("/applicant/stmp")
class SmtpController(
private val smtpUseCase: SmtpUseCase
) {
@ResponseStatus(HttpStatus.OK)
@GetMapping
suspend fun send(@NotEmpty(message = "email is required")@RequestParam email: String) : SmtpResponse {
return SmtpResponse(smtpUseCase.send(email) + "로 전송 완료")
}

@ResponseStatus(HttpStatus.OK)
@PostMapping
suspend fun verify(@Valid @RequestBody smtpRequest: SmtpRequest) : SmtpResponse {
return SmtpResponse(smtpUseCase.verify(smtpRequest.email,smtpRequest.code))
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.daegusw.apply.applicant.web.adapter.api.request

import javax.validation.constraints.Email
import javax.validation.constraints.NotEmpty

data class SmtpRequest(
@field:Email(message = "email is required")
val email: String,
@field:NotEmpty(message = "code is required")
val code: String,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.daegusw.apply.applicant.web.adapter.api.response

data class SmtpResponse(
val message : String,
)
Loading