비밀번호 찾기 API 추가 #4
| @@ -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() | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -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()) | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -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) | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,3 @@ | |||||||
|  | package kr.co.vividnext.sodalive.member | ||||||
|  |  | ||||||
|  | data class ForgotPasswordRequest(val email: String) | ||||||
| @@ -193,4 +193,9 @@ class MemberController(private val service: MemberService) { | |||||||
|         @RequestParam("image") multipartFile: MultipartFile, |         @RequestParam("image") multipartFile: MultipartFile, | ||||||
|         @AuthenticationPrincipal user: User |         @AuthenticationPrincipal user: User | ||||||
|     ) = ApiResponse.ok(service.profileImageUpdate(multipartFile, user)) |     ) = ApiResponse.ok(service.profileImageUpdate(multipartFile, user)) | ||||||
|  |  | ||||||
|  |     @PostMapping("/forgot-password") | ||||||
|  |     fun forgotPassword( | ||||||
|  |         @RequestBody request: ForgotPasswordRequest | ||||||
|  |     ) = ApiResponse.ok(service.forgotPassword(request)) | ||||||
| } | } | ||||||
|   | |||||||
| @@ -39,6 +39,7 @@ interface MemberQueryRepository { | |||||||
|     fun getMessageRecipientPushToken(messageId: Long): GetMessageRecipientPushTokenResponse |     fun getMessageRecipientPushToken(messageId: Long): GetMessageRecipientPushTokenResponse | ||||||
|     fun getIndividualRecipientPushTokens(recipients: List<Long>, isAuth: Boolean): Map<String, List<List<String>>> |     fun getIndividualRecipientPushTokens(recipients: List<Long>, isAuth: Boolean): Map<String, List<List<String>>> | ||||||
|     fun getChangeNicknamePrice(memberId: Long): GetChangeNicknamePriceResponse |     fun getChangeNicknamePrice(memberId: Long): GetChangeNicknamePriceResponse | ||||||
|  |     fun getMemberByEmail(email: String): Member? | ||||||
| } | } | ||||||
|  |  | ||||||
| @Repository | @Repository | ||||||
| @@ -229,4 +230,11 @@ class MemberQueryRepositoryImpl( | |||||||
|             } |             } | ||||||
|         ) |         ) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     override fun getMemberByEmail(email: String): Member? { | ||||||
|  |         return queryFactory | ||||||
|  |             .selectFrom(member) | ||||||
|  |             .where(member.email.eq(email)) | ||||||
|  |             .fetchOne() | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -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.can.use.CanUsage | ||||||
| import kr.co.vividnext.sodalive.common.ApiResponse | import kr.co.vividnext.sodalive.common.ApiResponse | ||||||
| import kr.co.vividnext.sodalive.common.SodaException | 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.jwt.TokenProvider | ||||||
| import kr.co.vividnext.sodalive.live.room.detail.GetRoomDetailUser | import kr.co.vividnext.sodalive.live.room.detail.GetRoomDetailUser | ||||||
| import kr.co.vividnext.sodalive.member.block.BlockMember | 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.tag.MemberTagRepository | ||||||
| import kr.co.vividnext.sodalive.member.token.MemberTokenRepository | import kr.co.vividnext.sodalive.member.token.MemberTokenRepository | ||||||
| import kr.co.vividnext.sodalive.utils.generateFileName | import kr.co.vividnext.sodalive.utils.generateFileName | ||||||
|  | import kr.co.vividnext.sodalive.utils.generatePassword | ||||||
| import org.springframework.beans.factory.annotation.Value | import org.springframework.beans.factory.annotation.Value | ||||||
| import org.springframework.data.repository.findByIdOrNull | import org.springframework.data.repository.findByIdOrNull | ||||||
| import org.springframework.security.authentication.UsernamePasswordAuthenticationToken | 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.stereotype.Service | ||||||
| import org.springframework.transaction.annotation.Transactional | import org.springframework.transaction.annotation.Transactional | ||||||
| import org.springframework.web.multipart.MultipartFile | 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 java.util.concurrent.locks.ReentrantReadWriteLock | ||||||
| import kotlin.concurrent.write | import kotlin.concurrent.write | ||||||
|  |  | ||||||
| @@ -62,6 +67,7 @@ class MemberService( | |||||||
|     private val nicknameChangeLogRepository: NicknameChangeLogRepository, |     private val nicknameChangeLogRepository: NicknameChangeLogRepository, | ||||||
|     private val memberTagRepository: MemberTagRepository, |     private val memberTagRepository: MemberTagRepository, | ||||||
|  |  | ||||||
|  |     private val emailService: SendEmailService, | ||||||
|     private val canPaymentService: CanPaymentService, |     private val canPaymentService: CanPaymentService, | ||||||
|     private val memberNotificationService: MemberNotificationService, |     private val memberNotificationService: MemberNotificationService, | ||||||
|  |  | ||||||
| @@ -546,6 +552,32 @@ class MemberService( | |||||||
|         return "$cloudFrontHost/${member.profileImage!!}" |         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<String, String>() | ||||||
|  |         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 { |     private fun getOrCreateLock(memberId: Long): ReentrantReadWriteLock { | ||||||
|         return tokenLocks.computeIfAbsent(memberId) { ReentrantReadWriteLock() } |         return tokenLocks.computeIfAbsent(memberId) { ReentrantReadWriteLock() } | ||||||
|     } |     } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user