rename long name to short name
This commit is contained in:
@@ -0,0 +1,297 @@
|
||||
// service/email_service.go
|
||||
package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"api_bb/internal/models"
|
||||
"api_bb/internal/repository"
|
||||
"api_bb/pkg/email"
|
||||
"api_bb/pkg/logger"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type EmailService struct {
|
||||
emailRepo repository.EmailRepository
|
||||
userRepo repository.UserRepository
|
||||
emailSender email.Service
|
||||
logger *zap.Logger
|
||||
tokenExpiry time.Duration
|
||||
passwordExpiry time.Duration
|
||||
}
|
||||
|
||||
func NewEmailService(
|
||||
emailRepo repository.EmailRepository,
|
||||
userRepo repository.UserRepository,
|
||||
emailSender email.Service,
|
||||
) EmailService {
|
||||
// Создаем логгер с контекстом для сервиса
|
||||
serviceLogger := logger.Get().With(zap.String("service", "email"))
|
||||
|
||||
return EmailService{
|
||||
emailRepo: emailRepo,
|
||||
userRepo: userRepo,
|
||||
emailSender: emailSender,
|
||||
logger: serviceLogger,
|
||||
tokenExpiry: 24 * time.Hour, // 24 часа для верификации
|
||||
passwordExpiry: 1 * time.Hour, // 1 час для сброса пароля
|
||||
}
|
||||
}
|
||||
|
||||
func (s *EmailService) SendVerificationEmail(userID uint, email, userName string) error {
|
||||
s.logger.Info("Sending verification email",
|
||||
zap.Uint("user_id", userID),
|
||||
zap.String("email", email),
|
||||
)
|
||||
|
||||
token := uuid.New().String()
|
||||
|
||||
verification := &models.EmailVerification{
|
||||
UserID: userID,
|
||||
Token: token,
|
||||
Email: email,
|
||||
Type: "verification",
|
||||
ExpiresAt: time.Now().Add(s.tokenExpiry),
|
||||
}
|
||||
|
||||
if err := s.emailRepo.CreateVerificationToken(verification); err != nil {
|
||||
s.logger.Error("Failed to create verification token",
|
||||
zap.Uint("user_id", userID),
|
||||
zap.String("email", email),
|
||||
zap.Error(err),
|
||||
)
|
||||
return fmt.Errorf("failed to create verification token: %w", err)
|
||||
}
|
||||
|
||||
if err := s.emailSender.SendVerificationEmail(email, userName, token); err != nil {
|
||||
s.logger.Error("Failed to send verification email",
|
||||
zap.Uint("user_id", userID),
|
||||
zap.String("email", email),
|
||||
zap.Error(err),
|
||||
)
|
||||
return fmt.Errorf("failed to send verification email: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("Verification email sent successfully",
|
||||
zap.Uint("user_id", userID),
|
||||
zap.String("email", email))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *EmailService) VerifyEmail(token string) error {
|
||||
s.logger.Info("Verifying email token",
|
||||
zap.String("token", token),
|
||||
)
|
||||
|
||||
verification, err := s.emailRepo.GetVerificationToken(token)
|
||||
if err != nil {
|
||||
s.logger.Error("Invalid or expired verification token",
|
||||
zap.String("token", token),
|
||||
zap.Error(err),
|
||||
)
|
||||
return fmt.Errorf("invalid or expired token: %w", err)
|
||||
}
|
||||
|
||||
if verification.Type != "verification" {
|
||||
s.logger.Error("Invalid token type for email verification",
|
||||
zap.String("token", token),
|
||||
zap.String("type", verification.Type),
|
||||
)
|
||||
return fmt.Errorf("invalid token type")
|
||||
}
|
||||
|
||||
// Обновляем пользователя
|
||||
if err := s.userRepo.MarkEmailAsVerified(verification.UserID); err != nil {
|
||||
s.logger.Error("Failed to verify email in user repository",
|
||||
zap.Uint("user_id", verification.UserID),
|
||||
zap.String("email", verification.Email),
|
||||
zap.Error(err),
|
||||
)
|
||||
return fmt.Errorf("failed to verify email: %w", err)
|
||||
}
|
||||
|
||||
// Помечаем токен как использованный
|
||||
if err := s.emailRepo.MarkTokenAsUsed(token); err != nil {
|
||||
s.logger.Error("Failed to mark token as used",
|
||||
zap.Error(err),
|
||||
zap.String("token", token))
|
||||
}
|
||||
|
||||
s.logger.Info("Email verified successfully",
|
||||
zap.Uint("user_id", verification.UserID),
|
||||
zap.String("email", verification.Email))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *EmailService) SendPasswordResetEmail(email string) error {
|
||||
s.logger.Info("Sending password reset email",
|
||||
zap.String("email", email),
|
||||
)
|
||||
|
||||
user, err := s.userRepo.FindByEmail(email)
|
||||
if err != nil {
|
||||
// Для безопасности не сообщаем, существует ли email
|
||||
s.logger.Info("Password reset requested for non-existent email",
|
||||
zap.String("email", email))
|
||||
return nil
|
||||
}
|
||||
|
||||
token := uuid.New().String()
|
||||
|
||||
resetRequest := &models.EmailVerification{
|
||||
UserID: user.ID,
|
||||
Token: token,
|
||||
Email: email,
|
||||
Type: "password_reset",
|
||||
ExpiresAt: time.Now().Add(s.passwordExpiry),
|
||||
}
|
||||
|
||||
if err := s.emailRepo.CreateVerificationToken(resetRequest); err != nil {
|
||||
s.logger.Error("Failed to create password reset token",
|
||||
zap.Uint("user_id", user.ID),
|
||||
zap.String("email", email),
|
||||
zap.Error(err),
|
||||
)
|
||||
return fmt.Errorf("failed to create password reset token: %w", err)
|
||||
}
|
||||
|
||||
if err := s.emailSender.SendPasswordResetEmail(email, user.FirstName, token); err != nil {
|
||||
s.logger.Error("Failed to send password reset email",
|
||||
zap.Uint("user_id", user.ID),
|
||||
zap.String("email", email),
|
||||
zap.Error(err),
|
||||
)
|
||||
return fmt.Errorf("failed to send password reset email: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("Password reset email sent successfully",
|
||||
zap.Uint("user_id", user.ID),
|
||||
zap.String("email", email))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *EmailService) ResetPassword(token, newPassword string) error {
|
||||
s.logger.Info("Resetting password with token",
|
||||
zap.String("token", token),
|
||||
)
|
||||
|
||||
verification, err := s.emailRepo.GetVerificationToken(token)
|
||||
if err != nil {
|
||||
s.logger.Error("Invalid or expired password reset token",
|
||||
zap.String("token", token),
|
||||
zap.Error(err),
|
||||
)
|
||||
return fmt.Errorf("invalid or expired token: %w", err)
|
||||
}
|
||||
|
||||
if verification.Type != "password_reset" {
|
||||
s.logger.Error("Invalid token type for password reset",
|
||||
zap.String("token", token),
|
||||
zap.String("type", verification.Type),
|
||||
)
|
||||
return fmt.Errorf("invalid token type")
|
||||
}
|
||||
|
||||
// Обновляем пароль пользователя
|
||||
if err := s.userRepo.UpdatePassword(verification.UserID, newPassword); err != nil {
|
||||
s.logger.Error("Failed to update password",
|
||||
zap.Uint("user_id", verification.UserID),
|
||||
zap.Error(err),
|
||||
)
|
||||
return fmt.Errorf("failed to update password: %w", err)
|
||||
}
|
||||
|
||||
// Помечаем токен как использованный
|
||||
if err := s.emailRepo.MarkTokenAsUsed(token); err != nil {
|
||||
s.logger.Error("Failed to mark token as used",
|
||||
zap.Error(err),
|
||||
zap.String("token", token))
|
||||
}
|
||||
|
||||
s.logger.Info("Password reset successfully",
|
||||
zap.Uint("user_id", verification.UserID))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *EmailService) SendNewsletterToSubscribers(subject, content string) error {
|
||||
s.logger.Info("Sending newsletter to subscribers",
|
||||
zap.String("subject", subject),
|
||||
)
|
||||
|
||||
subscribers, err := s.emailRepo.GetUsersWithNewsletter()
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to get subscribers",
|
||||
zap.Error(err),
|
||||
)
|
||||
return fmt.Errorf("failed to get subscribers: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Debug("Found subscribers for newsletter",
|
||||
zap.Int("count", len(subscribers)),
|
||||
)
|
||||
|
||||
var errors []error
|
||||
for _, user := range subscribers {
|
||||
if err := s.emailSender.SendNewsletterEmail(user.Email, user.FirstName, subject, content); err != nil {
|
||||
s.logger.Error("Failed to send newsletter to user",
|
||||
zap.Uint("user_id", user.ID),
|
||||
zap.String("email", user.Email),
|
||||
zap.Error(err))
|
||||
errors = append(errors, err)
|
||||
continue
|
||||
}
|
||||
s.logger.Debug("Newsletter sent to user",
|
||||
zap.Uint("user_id", user.ID),
|
||||
zap.String("email", user.Email))
|
||||
}
|
||||
|
||||
if len(errors) > 0 {
|
||||
s.logger.Error("Failed to send newsletter to some users",
|
||||
zap.Int("failed_count", len(errors)),
|
||||
zap.Int("total_subscribers", len(subscribers)),
|
||||
)
|
||||
return fmt.Errorf("failed to send newsletter to %d users", len(errors))
|
||||
}
|
||||
|
||||
s.logger.Info("Newsletter sent to all subscribers",
|
||||
zap.Int("total_subscribers", len(subscribers)))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *EmailService) CleanupExpiredTokens() error {
|
||||
s.logger.Info("Cleaning up expired tokens")
|
||||
|
||||
if err := s.emailRepo.DeleteExpiredTokens(); err != nil {
|
||||
s.logger.Error("Failed to cleanup expired tokens",
|
||||
zap.Error(err),
|
||||
)
|
||||
return fmt.Errorf("failed to cleanup expired tokens: %w", err)
|
||||
}
|
||||
s.logger.Info("Expired tokens cleaned up successfully")
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetUserByID возвращает пользователя по ID
|
||||
func (s *EmailService) GetUserByID(userID uint) (*models.User, error) {
|
||||
s.logger.Info("Getting user by ID",
|
||||
zap.Uint("user_id", userID),
|
||||
)
|
||||
|
||||
user, err := s.userRepo.GetUserByID(userID)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to get user by ID",
|
||||
zap.Uint("user_id", userID),
|
||||
zap.Error(err),
|
||||
)
|
||||
return nil, fmt.Errorf("failed to get user: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Debug("User retrieved successfully",
|
||||
zap.Uint("user_id", userID),
|
||||
zap.String("email", user.Email),
|
||||
)
|
||||
return user, nil
|
||||
}
|
||||
Reference in New Issue
Block a user