From 248e57b08cdd1cb5de396c9dfa05dae219e6d67f Mon Sep 17 00:00:00 2001 From: Klaus Date: Sat, 19 Aug 2023 03:21:39 +0900 Subject: [PATCH] =?UTF-8?q?=EB=B9=84=EB=B0=80=EB=B2=88=ED=98=B8=20?= =?UTF-8?q?=EC=B0=BE=EA=B8=B0=20API=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sodalive/configs/AmazonSESConfig.kt | 30 +++++++++++++++++ .../sodalive/email/SendEmailService.kt | 18 +++++++++++ .../sodalive/email/TemplatedEmailSenderDto.kt | 21 ++++++++++++ .../sodalive/member/ForgotPasswordRequest.kt | 3 ++ .../sodalive/member/MemberController.kt | 5 +++ .../sodalive/member/MemberRepository.kt | 8 +++++ .../sodalive/member/MemberService.kt | 32 +++++++++++++++++++ 7 files changed, 117 insertions(+) create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/configs/AmazonSESConfig.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/email/SendEmailService.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/email/TemplatedEmailSenderDto.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/member/ForgotPasswordRequest.kt diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/configs/AmazonSESConfig.kt b/src/main/kotlin/kr/co/vividnext/sodalive/configs/AmazonSESConfig.kt new file mode 100644 index 0000000..bff0d3b --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/configs/AmazonSESConfig.kt @@ -0,0 +1,30 @@ +package kr.co.vividnext.sodalive.configs + +import com.amazonaws.auth.AWSStaticCredentialsProvider +import com.amazonaws.auth.BasicAWSCredentials +import com.amazonaws.services.simpleemail.AmazonSimpleEmailService +import com.amazonaws.services.simpleemail.AmazonSimpleEmailServiceClientBuilder +import org.springframework.beans.factory.annotation.Value +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration + +@Configuration +class AmazonSESConfig( + @Value("\${cloud.aws.credentials.access-key}") + private val accessKey: String, + @Value("\${cloud.aws.credentials.secret-key}") + private val secretKey: String, + @Value("\${cloud.aws.region.static}") + private val region: String +) { + @Bean + fun amazonSimpleEmailService(): AmazonSimpleEmailService { + val basicAWSCredentials = BasicAWSCredentials(accessKey, secretKey) + val awsStaticCredentialsProvider = AWSStaticCredentialsProvider(basicAWSCredentials) + + return AmazonSimpleEmailServiceClientBuilder.standard() + .withCredentials(awsStaticCredentialsProvider) + .withRegion(region) + .build() + } +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/email/SendEmailService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/email/SendEmailService.kt new file mode 100644 index 0000000..ac2601c --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/email/SendEmailService.kt @@ -0,0 +1,18 @@ +package kr.co.vividnext.sodalive.email + +import com.amazonaws.services.simpleemail.AmazonSimpleEmailService +import org.springframework.stereotype.Service + +@Service +class SendEmailService(private val amazonSimpleEmailService: AmazonSimpleEmailService) { + fun sendTemplatedEmail(template: String, templateData: String, receiver: String) { + val senderDto = TemplatedEmailSenderDto( + senderEmail = "yozmlive.noreply@gmail.com", + template = template, + templateData = templateData, + to = receiver + ) + + amazonSimpleEmailService.sendTemplatedEmail(senderDto.toSendRequest()) + } +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/email/TemplatedEmailSenderDto.kt b/src/main/kotlin/kr/co/vividnext/sodalive/email/TemplatedEmailSenderDto.kt new file mode 100644 index 0000000..26d2f91 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/email/TemplatedEmailSenderDto.kt @@ -0,0 +1,21 @@ +package kr.co.vividnext.sodalive.email + +import com.amazonaws.services.simpleemail.model.Destination +import com.amazonaws.services.simpleemail.model.SendTemplatedEmailRequest + +data class TemplatedEmailSenderDto( + private val senderEmail: String, + private val template: String, + private val templateData: String, + private val to: String +) { + fun toSendRequest(): SendTemplatedEmailRequest { + val destination = Destination().withToAddresses(to) + + return SendTemplatedEmailRequest() + .withTemplate(template) + .withDestination(destination) + .withSource(senderEmail) + .withTemplateData(templateData) + } +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/member/ForgotPasswordRequest.kt b/src/main/kotlin/kr/co/vividnext/sodalive/member/ForgotPasswordRequest.kt new file mode 100644 index 0000000..4b312cb --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/member/ForgotPasswordRequest.kt @@ -0,0 +1,3 @@ +package kr.co.vividnext.sodalive.member + +data class ForgotPasswordRequest(val email: String) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberController.kt b/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberController.kt index cc3128d..f277d4c 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberController.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberController.kt @@ -193,4 +193,9 @@ class MemberController(private val service: MemberService) { @RequestParam("image") multipartFile: MultipartFile, @AuthenticationPrincipal user: User ) = ApiResponse.ok(service.profileImageUpdate(multipartFile, user)) + + @PostMapping("/forgot-password") + fun forgotPassword( + @RequestBody request: ForgotPasswordRequest + ) = ApiResponse.ok(service.forgotPassword(request)) } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberRepository.kt index 32a07e6..5a92a07 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberRepository.kt @@ -39,6 +39,7 @@ interface MemberQueryRepository { fun getMessageRecipientPushToken(messageId: Long): GetMessageRecipientPushTokenResponse fun getIndividualRecipientPushTokens(recipients: List, isAuth: Boolean): Map>> fun getChangeNicknamePrice(memberId: Long): GetChangeNicknamePriceResponse + fun getMemberByEmail(email: String): Member? } @Repository @@ -229,4 +230,11 @@ class MemberQueryRepositoryImpl( } ) } + + override fun getMemberByEmail(email: String): Member? { + return queryFactory + .selectFrom(member) + .where(member.email.eq(email)) + .fetchOne() + } } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberService.kt index 922d022..db4564a 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberService.kt @@ -7,6 +7,7 @@ import kr.co.vividnext.sodalive.can.payment.CanPaymentService import kr.co.vividnext.sodalive.can.use.CanUsage import kr.co.vividnext.sodalive.common.ApiResponse import kr.co.vividnext.sodalive.common.SodaException +import kr.co.vividnext.sodalive.email.SendEmailService import kr.co.vividnext.sodalive.jwt.TokenProvider import kr.co.vividnext.sodalive.live.room.detail.GetRoomDetailUser import kr.co.vividnext.sodalive.member.block.BlockMember @@ -33,6 +34,7 @@ import kr.co.vividnext.sodalive.member.tag.MemberCreatorTag import kr.co.vividnext.sodalive.member.tag.MemberTagRepository import kr.co.vividnext.sodalive.member.token.MemberTokenRepository import kr.co.vividnext.sodalive.utils.generateFileName +import kr.co.vividnext.sodalive.utils.generatePassword import org.springframework.beans.factory.annotation.Value import org.springframework.data.repository.findByIdOrNull import org.springframework.security.authentication.UsernamePasswordAuthenticationToken @@ -46,6 +48,9 @@ import org.springframework.security.crypto.password.PasswordEncoder import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional import org.springframework.web.multipart.MultipartFile +import java.time.LocalDateTime +import java.time.ZoneId +import java.time.format.DateTimeFormatter import java.util.concurrent.locks.ReentrantReadWriteLock import kotlin.concurrent.write @@ -62,6 +67,7 @@ class MemberService( private val nicknameChangeLogRepository: NicknameChangeLogRepository, private val memberTagRepository: MemberTagRepository, + private val emailService: SendEmailService, private val canPaymentService: CanPaymentService, private val memberNotificationService: MemberNotificationService, @@ -546,6 +552,32 @@ class MemberService( return "$cloudFrontHost/${member.profileImage!!}" } + @Transactional + fun forgotPassword(request: ForgotPasswordRequest) { + val member = repository.getMemberByEmail(email = request.email) + ?: throw SodaException("등록되지 않은 계정입니다.\n확인 후 다시 시도해 주세요.") + + val password = generatePassword(12) + member.password = passwordEncoder.encode(password) + + val date = LocalDateTime.now() + .atZone(ZoneId.of("UTC")) + .withZoneSameInstant(ZoneId.of("Asia/Seoul")) + .format(DateTimeFormatter.ofPattern("yyyy년 MM월 dd일, HH:mm")) + + val data = HashMap() + data["password"] = password + data["date"] = date + + val templateData = objectMapper.writeValueAsString(data) + + emailService.sendTemplatedEmail( + template = "sodalive-find-password", + templateData = templateData, + receiver = request.email + ) + } + private fun getOrCreateLock(memberId: Long): ReentrantReadWriteLock { return tokenLocks.computeIfAbsent(memberId) { ReentrantReadWriteLock() } }