diff --git a/src/main/kotlin/org/careerseekers/csmailservice/cache/TemporaryPasswordsCache.kt b/src/main/kotlin/org/careerseekers/csmailservice/cache/TemporaryPasswordsCache.kt new file mode 100644 index 0000000..294ddb2 --- /dev/null +++ b/src/main/kotlin/org/careerseekers/csmailservice/cache/TemporaryPasswordsCache.kt @@ -0,0 +1,23 @@ +package org.careerseekers.csmailservice.cache + +import org.careerseekers.csmailservice.dto.TemporaryPasswordDto +import org.springframework.cache.CacheManager +import org.springframework.data.redis.core.RedisTemplate +import org.springframework.stereotype.Component + +@Component +class TemporaryPasswordsCache( + override val redisTemplate: RedisTemplate, + cacheManager: CacheManager, +) : CacheRetriever { + override val cacheKey = "temporaryPasswords" + private val cache = cacheManager.getCache(cacheKey) + + override fun getItemFromCache(key: Any): TemporaryPasswordDto? { + val password = cache?.get(key)?.let { + it.get() as? TemporaryPasswordDto + } + + return password + } +} \ No newline at end of file diff --git a/src/main/kotlin/org/careerseekers/csmailservice/cache/VerificationCodesCache.kt b/src/main/kotlin/org/careerseekers/csmailservice/cache/VerificationCodesCache.kt index 25b9619..ba22483 100644 --- a/src/main/kotlin/org/careerseekers/csmailservice/cache/VerificationCodesCache.kt +++ b/src/main/kotlin/org/careerseekers/csmailservice/cache/VerificationCodesCache.kt @@ -14,7 +14,7 @@ class VerificationCodesCache( private val cache = cacheManager.getCache(cacheKey) override fun loadItemToCache(item: VerificationCodeDto) { - cache?.put(item.userId, item) + cache?.put(item.userEmail, item) } diff --git a/src/main/kotlin/org/careerseekers/csmailservice/config/RedisTemplatesConfig.kt b/src/main/kotlin/org/careerseekers/csmailservice/config/RedisTemplatesConfig.kt index 7a30415..91c0424 100644 --- a/src/main/kotlin/org/careerseekers/csmailservice/config/RedisTemplatesConfig.kt +++ b/src/main/kotlin/org/careerseekers/csmailservice/config/RedisTemplatesConfig.kt @@ -1,6 +1,7 @@ package org.careerseekers.csmailservice.config import org.careerseekers.csmailservice.dto.CachesDto +import org.careerseekers.csmailservice.dto.TemporaryPasswordDto import org.careerseekers.csmailservice.dto.UsersCacheDto import org.careerseekers.csmailservice.dto.VerificationCodeDto import org.careerseekers.csmailservice.serializers.PolymorphicRedisSerializer @@ -51,4 +52,22 @@ class RedisTemplatesConfig( template.afterPropertiesSet() return template } + + @Bean + @Qualifier("temporaryPasswords") + fun temporaryPasswordsRedisTemplate( + connectionFactory: RedisConnectionFactory + ): RedisTemplate { + val template = RedisTemplate() + template.connectionFactory = connectionFactory + + template.keySerializer = StringRedisSerializer() + template.valueSerializer = serializer + + template.hashKeySerializer = StringRedisSerializer() + template.hashValueSerializer = serializer + + template.afterPropertiesSet() + return template + } } \ No newline at end of file diff --git a/src/main/kotlin/org/careerseekers/csmailservice/dto/CachesDto.kt b/src/main/kotlin/org/careerseekers/csmailservice/dto/CachesDto.kt index 3320cc9..7085e22 100644 --- a/src/main/kotlin/org/careerseekers/csmailservice/dto/CachesDto.kt +++ b/src/main/kotlin/org/careerseekers/csmailservice/dto/CachesDto.kt @@ -29,19 +29,29 @@ data class UsersCacheDto( val role: UsersRoles, val avatarId: Long, val verified: Boolean, + val isMentor: Boolean, ) : CachesDto() @Serializable @SerialName("VerificationCodeDto") data class VerificationCodeDto( - val userId: Long, + val userEmail: String, val code: String, var retries: Int ) : CachesDto() +@Serializable +@SerialName("TemporaryPasswordDto") +data class TemporaryPasswordDto( + val email: String, + val password: String +) : CachesDto() + val cacheModule = SerializersModule { polymorphic(CachesDto::class) { subclass(UsersCacheDto::class, UsersCacheDto.serializer()) + subclass(VerificationCodeDto::class, VerificationCodeDto.serializer()) + subclass(TemporaryPasswordDto::class, TemporaryPasswordDto.serializer()) } } diff --git a/src/main/kotlin/org/careerseekers/csmailservice/dto/KafkaMessagesDto.kt b/src/main/kotlin/org/careerseekers/csmailservice/dto/KafkaMessagesDto.kt index 2ab3f8f..6cbfda2 100644 --- a/src/main/kotlin/org/careerseekers/csmailservice/dto/KafkaMessagesDto.kt +++ b/src/main/kotlin/org/careerseekers/csmailservice/dto/KafkaMessagesDto.kt @@ -12,6 +12,8 @@ sealed class KafkaMessagesDto : DtoClass @Serializable @SerialName("email_sending_task") class EmailSendingTaskDto( - val token: String, + val email: String? = null, + val token: String? = null, + val user: UsersCacheDto? = null, val eventType: MailEventTypes, ) : KafkaMessagesDto() \ No newline at end of file diff --git a/src/main/kotlin/org/careerseekers/csmailservice/enums/MailEventTypes.kt b/src/main/kotlin/org/careerseekers/csmailservice/enums/MailEventTypes.kt index 3089809..86ed2de 100644 --- a/src/main/kotlin/org/careerseekers/csmailservice/enums/MailEventTypes.kt +++ b/src/main/kotlin/org/careerseekers/csmailservice/enums/MailEventTypes.kt @@ -1,6 +1,8 @@ package org.careerseekers.csmailservice.enums enum class MailEventTypes { - REGISTRATION, + PRE_REGISTRATION, + MENTOR_AND_USER_REGISTRATION, + EXPERT_REGISTRATION, PASSWORD_RESET } \ No newline at end of file diff --git a/src/main/kotlin/org/careerseekers/csmailservice/io/converters/extensions/RpcUserToCacheUser.kt b/src/main/kotlin/org/careerseekers/csmailservice/io/converters/extensions/RpcUserToCacheUser.kt index 23d36ee..1cd9532 100644 --- a/src/main/kotlin/org/careerseekers/csmailservice/io/converters/extensions/RpcUserToCacheUser.kt +++ b/src/main/kotlin/org/careerseekers/csmailservice/io/converters/extensions/RpcUserToCacheUser.kt @@ -17,5 +17,6 @@ fun User.toCache() : UsersCacheDto { role = UsersRoles.valueOf(this.role), avatarId = this.avatarId, verified = this.verified, + isMentor = this.isMentor, ) } \ No newline at end of file diff --git a/src/main/kotlin/org/careerseekers/csmailservice/services/EmailProcessingService.kt b/src/main/kotlin/org/careerseekers/csmailservice/services/EmailProcessingService.kt index 8fa1a54..07dbea6 100644 --- a/src/main/kotlin/org/careerseekers/csmailservice/services/EmailProcessingService.kt +++ b/src/main/kotlin/org/careerseekers/csmailservice/services/EmailProcessingService.kt @@ -2,8 +2,10 @@ package org.careerseekers.csmailservice.services import org.careerseekers.csmailservice.dto.EmailSendingTaskDto import org.careerseekers.csmailservice.enums.MailEventTypes +import org.springframework.mail.javamail.JavaMailSender interface EmailProcessingService { + val mailer: JavaMailSender val eventType: MailEventTypes fun processEmail(message: EmailSendingTaskDto) diff --git a/src/main/kotlin/org/careerseekers/csmailservice/services/ExpertRegistrationEmailService.kt b/src/main/kotlin/org/careerseekers/csmailservice/services/ExpertRegistrationEmailService.kt new file mode 100644 index 0000000..48d423e --- /dev/null +++ b/src/main/kotlin/org/careerseekers/csmailservice/services/ExpertRegistrationEmailService.kt @@ -0,0 +1,50 @@ +package org.careerseekers.csmailservice.services + +import org.careerseekers.csmailservice.cache.TemporaryPasswordsCache +import org.careerseekers.csmailservice.dto.EmailSendingTaskDto +import org.careerseekers.csmailservice.enums.MailEventTypes +import org.careerseekers.csmailservice.exceptions.BadRequestException +import org.springframework.beans.factory.annotation.Value +import org.springframework.mail.SimpleMailMessage +import org.springframework.mail.javamail.JavaMailSender +import org.springframework.stereotype.Service + +@Service +class ExpertRegistrationEmailService( + override val mailer: JavaMailSender, + private val temporaryPasswordsCache: TemporaryPasswordsCache, +) : EmailProcessingService { + + @Value("\${spring.mail.username}") + private val senderEmail: String? = null + + override val eventType = MailEventTypes.EXPERT_REGISTRATION + + override fun processEmail(message: EmailSendingTaskDto) { + message.user?.let { user -> + val cacheItem = + temporaryPasswordsCache.getItemFromCache(user.email) ?: throw BadRequestException("Password not found") + + SimpleMailMessage().apply { + from = senderEmail + setTo(user.email) + subject = "Регистрация эксперта в системе Искатели профессий" + text = """ + Уважаемый(-ая) ${user.lastName} ${user.firstName} ${user.patronymic}! + Вас зарегистрировали как Эксперта на чемпионат Искатели профессий. + По ссылке ниже вы можете перейти в личный кабинет Эксперта, где сможете подробно изучить свои возможности и обязанности: + https://github.com/career-seekers + + Реквизиты для входа в личный кабинет: + Логин: ${user.email} + Пароль: ${cacheItem.password} + + Спасибо, + Команда поддержки Искателей профессий. + """.trimIndent() + }.also { + mailer.send(it) + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/org/careerseekers/csmailservice/services/MentorAndUserRegistrationEmailService.kt b/src/main/kotlin/org/careerseekers/csmailservice/services/MentorAndUserRegistrationEmailService.kt new file mode 100644 index 0000000..7a06287 --- /dev/null +++ b/src/main/kotlin/org/careerseekers/csmailservice/services/MentorAndUserRegistrationEmailService.kt @@ -0,0 +1,39 @@ +package org.careerseekers.csmailservice.services + +import org.careerseekers.csmailservice.dto.EmailSendingTaskDto +import org.careerseekers.csmailservice.enums.MailEventTypes +import org.springframework.beans.factory.annotation.Value +import org.springframework.mail.SimpleMailMessage +import org.springframework.mail.javamail.JavaMailSender +import org.springframework.stereotype.Service + +@Service +class MentorAndUserRegistrationEmailService( + override val mailer: JavaMailSender +) : EmailProcessingService { + @Value("\${spring.mail.username}") + private val senderEmail: String? = null + + override val eventType = MailEventTypes.MENTOR_AND_USER_REGISTRATION + + override fun processEmail(message: EmailSendingTaskDto) { + message.user?.let { user -> + SimpleMailMessage().apply { + from = senderEmail + setTo(user.email) + subject = "Регистрация наставника в системе Искатели профессий" + text = """ + Уважаемый(-ая) ${user.lastName} ${user.firstName} ${user.patronymic}! + Вы зарегистрировались как наставник чемпионата Искатели профессий. + По ссылке ниже вы можете перейти в личный кабинет Наставника, где можете подробно изучить свои возможности и обязанности: + https://github.com/career-seekers + + Спасибо, + Команда поддержки Искателей профессий. + """.trimIndent() + }.also { + mailer.send(it) + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/org/careerseekers/csmailservice/services/PasswordResetEmailService.kt b/src/main/kotlin/org/careerseekers/csmailservice/services/PasswordResetEmailService.kt index a725c09..7537ecd 100644 --- a/src/main/kotlin/org/careerseekers/csmailservice/services/PasswordResetEmailService.kt +++ b/src/main/kotlin/org/careerseekers/csmailservice/services/PasswordResetEmailService.kt @@ -4,6 +4,7 @@ import org.careerseekers.csmailservice.cache.VerificationCodesCache import org.careerseekers.csmailservice.dto.EmailSendingTaskDto import org.careerseekers.csmailservice.dto.VerificationCodeDto import org.careerseekers.csmailservice.enums.MailEventTypes +import org.careerseekers.csmailservice.exceptions.BadRequestException import org.careerseekers.csmailservice.exceptions.NotFoundException import org.careerseekers.csmailservice.utils.CodeGenerator.generateVerificationCode import org.careerseekers.csmailservice.utils.JwtUtil @@ -15,8 +16,8 @@ import org.springframework.stereotype.Service @Service class PasswordResetEmailService( + override val mailer: JavaMailSender, private val jwtUtil: JwtUtil, - private val mailer: JavaMailSender, private val passwordEncoder: PasswordEncoder, private val verificationCodesCache: VerificationCodesCache ) : EmailProcessingService { @@ -26,34 +27,33 @@ class PasswordResetEmailService( override val eventType = MailEventTypes.PASSWORD_RESET override fun processEmail(message: EmailSendingTaskDto) { - jwtUtil.getUserFromToken(message.token)?.let { user -> - val code = generateVerificationCode() - verificationCodesCache.loadItemToCache(VerificationCodeDto( - userId = user.id, + val user = message.token?.let { + jwtUtil.getUserFromToken(it) ?: throw NotFoundException("User not found") + } ?: message.user ?: throw BadRequestException("This method requires user") + + val code = generateVerificationCode() + verificationCodesCache.loadItemToCache( + VerificationCodeDto( + userEmail = user.email, code = passwordEncoder.encode(code), retries = 0 - )) - - val message = SimpleMailMessage() - - message.from = senderEmail - message.setTo(user.email) - message.subject = "Изменение пароля" - message.text = """ - Уважаемый(-ая) ${user.lastName} ${user.firstName} ${user.patronymic}! - - Для подтверждения изменения пароля введите следующий верификационный код: - - $code - - Если вы не запрашивали этот код, просто проигнорируйте это письмо. - - Спасибо, - Команда поддержки Искателей профессий. - """.trimIndent() - - mailer.send(message) - } ?: throw NotFoundException("User not found") - + ) + ) + + SimpleMailMessage().apply { + from = senderEmail + setTo(user.email) + subject = "Изменение пароля" + text = """ + Уважаемый(-ая) ${user.lastName} ${user.firstName} ${user.patronymic}! + Для подтверждения изменения пароля введите следующий верификационный код: $code + Если вы не запрашивали этот код, просто проигнорируйте это письмо. + + Спасибо, + Команда поддержки Искателей профессий. + """.trimIndent() + }.also { + mailer.send(it) + } } } \ No newline at end of file diff --git a/src/main/kotlin/org/careerseekers/csmailservice/services/PreRegistrationEmailService.kt b/src/main/kotlin/org/careerseekers/csmailservice/services/PreRegistrationEmailService.kt new file mode 100644 index 0000000..ffd11ca --- /dev/null +++ b/src/main/kotlin/org/careerseekers/csmailservice/services/PreRegistrationEmailService.kt @@ -0,0 +1,53 @@ +package org.careerseekers.csmailservice.services + +import org.careerseekers.csmailservice.cache.VerificationCodesCache +import org.careerseekers.csmailservice.dto.EmailSendingTaskDto +import org.careerseekers.csmailservice.dto.VerificationCodeDto +import org.careerseekers.csmailservice.enums.MailEventTypes +import org.careerseekers.csmailservice.utils.CodeGenerator.generateVerificationCode +import org.springframework.beans.factory.annotation.Value +import org.springframework.mail.SimpleMailMessage +import org.springframework.mail.javamail.JavaMailSender +import org.springframework.security.crypto.password.PasswordEncoder +import org.springframework.stereotype.Service + +@Service +class PreRegistrationEmailService( + override val mailer: JavaMailSender, + private val verificationCodesCache: VerificationCodesCache, + private val passwordEncoder: PasswordEncoder, +) : EmailProcessingService { + + @Value("\${spring.mail.username}") + private val senderEmail: String? = null + + override val eventType = MailEventTypes.PRE_REGISTRATION + + override fun processEmail(message: EmailSendingTaskDto) { + message.email?.let { email -> + val code = generateVerificationCode() + verificationCodesCache.loadItemToCache( + VerificationCodeDto( + userEmail = email, + code = passwordEncoder.encode(code), + retries = 0 + ) + ) + + SimpleMailMessage().apply { + from = senderEmail + setTo(email) + subject = "Регистрация в системе Искатели профессий" + text = """ + Для завершения регистрации введите следующий верификационный код, он действителен в течение 5 минут: $code + Если вы не запрашивали этот код, просто проигнорируйте это письмо. + + Спасибо, + Команда поддержки Искателей профессий. + """.trimIndent() + }.also { + mailer.send(it) + } + } + } +} \ No newline at end of file diff --git a/src/main/proto/UsersService.proto b/src/main/proto/UsersService.proto index b3d71be..4860883 100644 --- a/src/main/proto/UsersService.proto +++ b/src/main/proto/UsersService.proto @@ -28,4 +28,5 @@ message User { string role = 9; int64 avatarId = 10; bool verified = 11; + bool isMentor = 12; } \ No newline at end of file