Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 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
Original file line number Diff line number Diff line change
@@ -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<String, TemporaryPasswordDto>,
cacheManager: CacheManager,
) : CacheRetriever<TemporaryPasswordDto> {
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
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}


Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -51,4 +52,22 @@ class RedisTemplatesConfig(
template.afterPropertiesSet()
return template
}

@Bean
@Qualifier("temporaryPasswords")
fun temporaryPasswordsRedisTemplate(
connectionFactory: RedisConnectionFactory
): RedisTemplate<String, TemporaryPasswordDto> {
val template = RedisTemplate<String, TemporaryPasswordDto>()
template.connectionFactory = connectionFactory

template.keySerializer = StringRedisSerializer()
template.valueSerializer = serializer

template.hashKeySerializer = StringRedisSerializer()
template.hashValueSerializer = serializer

template.afterPropertiesSet()
return template
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,24 @@ 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())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package org.careerseekers.csmailservice.enums

enum class MailEventTypes {
REGISTRATION,
PRE_REGISTRATION,
MENTOR_AND_USER_REGISTRATION,
EXPERT_REGISTRATION,
PASSWORD_RESET
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@ fun User.toCache() : UsersCacheDto {
role = UsersRoles.valueOf(this.role),
avatarId = this.avatarId,
verified = this.verified,
isMentor = this.isMentor,
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
@@ -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)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -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)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 {
Expand All @@ -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)
}
}
}
Original file line number Diff line number Diff line change
@@ -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)
}
}
}
}
1 change: 1 addition & 0 deletions src/main/proto/UsersService.proto
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,5 @@ message User {
string role = 9;
int64 avatarId = 10;
bool verified = 11;
bool isMentor = 12;
}