add task struct

This commit is contained in:
Pavel 2024-01-03 18:45:41 +03:00
parent 5e1a04a02c
commit 922222d2c8
9 changed files with 130 additions and 68 deletions

3
.env

@ -31,3 +31,6 @@ SMTP_UNAME="kotilion.95@gmail.com"
SMTP_PASS="vWwbCSg4bf0p"
SMTP_API_KEY="P0YsjUB137upXrr1NiJefHmXVKW1hmBWlpev"
SMTP_SENDER="noreply@mailing.pena.digital"
# URL settings
DEFAULT_REDIRECTION_URL = "def.url"

@ -55,12 +55,13 @@ func Run(ctx context.Context, cfg initialize.Config, logger *zap.Logger) error {
EncryptService: encryptService,
})
recoveryController := controller.NewRecoveryController(logger, recoveryService)
recoveryController := controller.NewRecoveryController(logger, recoveryService, cfg.DefaultRedirectionURL)
recoveryWC := recovery_worker.NewRecoveryWC(recovery_worker.Deps{
Logger: logger,
Redis: rdb,
EmailSender: recoveryEmailSender,
Mongo: mdb.Collection("codeword"),
})
go recoveryWC.Start(ctx)

@ -8,14 +8,16 @@ import (
)
type RecoveryController struct {
logger *zap.Logger
service *services.RecoveryService
logger *zap.Logger
service *services.RecoveryService
defaultURL string
}
func NewRecoveryController(logger *zap.Logger, service *services.RecoveryService) *RecoveryController {
func NewRecoveryController(logger *zap.Logger, service *services.RecoveryService, defaultRedirectionURL string) *RecoveryController {
return &RecoveryController{
logger: logger,
service: service,
logger: logger,
service: service,
defaultURL: defaultRedirectionURL,
}
}
@ -23,9 +25,18 @@ func (r *RecoveryController) HandlePingDB(c *fiber.Ctx) error {
return r.service.Ping(c.Context())
}
// TODO add deps struct, counnt params >3
// HandleRecoveryRequest обрабатывает запрос на восстановление пароля
func (r *RecoveryController) HandleRecoveryRequest(c *fiber.Ctx) error {
email := c.FormValue("email")
referralURL := c.Get("Referrer")
redirectionURL := c.FormValue("RedirectionURL")
if redirectionURL == "" && referralURL != "" {
redirectionURL = referralURL
} else if redirectionURL == "" {
redirectionURL = r.defaultURL
}
key, err := r.service.GenerateKey()
if err != nil {
@ -39,26 +50,30 @@ func (r *RecoveryController) HandleRecoveryRequest(c *fiber.Ctx) error {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "User not found"})
}
err = r.service.StoreRecoveryRecord(c.Context(), user.ID.Hex(), user.Email, key)
//sign := referralURL + string(key)
id, err := r.service.StoreRecoveryRecord(c.Context(), user.ID.Hex(), user.Email, key)
if err != nil {
r.logger.Error("Failed to store recovery record", zap.Error(err))
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Internal Server Error"})
}
err = r.service.RecoveryEmailTask(c.Context(), user.ID.Hex(), email, key)
err = r.service.RecoveryEmailTask(c.Context(), user.ID.Hex(), email, key, id)
if err != nil {
r.logger.Error("Failed to send recovery email", zap.Error(err))
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Internal Server Error"})
}
return c.Status(fiber.StatusOK).JSON(fiber.Map{"message": "Recovery email sent successfully"})
return c.Status(fiber.StatusOK).JSON(fiber.Map{
"id": id,
})
}
// HandleRecoveryLink обрабатывает ссылку восстановления и обменивает ее на токены
func (r *RecoveryController) HandleRecoveryLink(c *fiber.Ctx) error {
key := c.Params("sign")
// тут получается
record, err := r.service.GetRecoveryRecord(c.Context(), key)
record, err := r.service.GetRecoveryRecord(c.Context(), []byte(key))
if err != nil {
r.logger.Error("Failed to get recovery record", zap.Error(err))
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Internal Server Error"})

@ -7,28 +7,29 @@ import (
)
type Config struct {
AppName string `env:"APP_NAME" envDefault:"codeword"`
HTTPHost string `env:"HTTP_HOST" envDefault:"localhost"`
HTTPPort string `env:"HTTP_PORT" envDefault:"3000"`
MongoHost string `env:"MONGO_HOST" envDefault:"127.0.0.1"`
MongoPort string `env:"MONGO_PORT" envDefault:"27020"`
MongoUser string `env:"MONGO_USER" envDefault:"test"`
MongoPassword string `env:"MONGO_PASSWORD" envDefault:"test"`
MongoDatabase string `env:"MONGO_DB" envDefault:"admin"`
MongoAuth string `env:"MONGO_AUTH" envDefault:"admin"`
PublicCurveKey string `env:"PUBLIC_CURVE_KEY"`
PrivateCurveKey string `env:"PRIVATE_CURVE_KEY"`
SignSecret string `env:"SIGN_SECRET"`
RedisAddr string `env:"REDIS_ADDR" envDefault:"localhost:6379"`
RedisPassword string `env:"REDIS_PASS" envDefault:"admin"`
RedisDB int `env:"REDIS_DB" envDefault:"2"`
SmtpApiUrl string `env:"SMTP_API_URL"`
SmtpHost string `env:"SMTP_HOST"`
SmtpPort string `env:"SMTP_PORT"`
SmtpUsername string `env:"SMTP_UNAME"`
SmtpPassword string `env:"SMTP_PASS"`
SmtpApiKey string `env:"SMTP_API_KEY"`
SmtpSender string `env:"SMTP_SENDER"`
AppName string `env:"APP_NAME" envDefault:"codeword"`
HTTPHost string `env:"HTTP_HOST" envDefault:"localhost"`
HTTPPort string `env:"HTTP_PORT" envDefault:"3000"`
MongoHost string `env:"MONGO_HOST" envDefault:"127.0.0.1"`
MongoPort string `env:"MONGO_PORT" envDefault:"27020"`
MongoUser string `env:"MONGO_USER" envDefault:"test"`
MongoPassword string `env:"MONGO_PASSWORD" envDefault:"test"`
MongoDatabase string `env:"MONGO_DB" envDefault:"admin"`
MongoAuth string `env:"MONGO_AUTH" envDefault:"admin"`
PublicCurveKey string `env:"PUBLIC_CURVE_KEY"`
PrivateCurveKey string `env:"PRIVATE_CURVE_KEY"`
SignSecret string `env:"SIGN_SECRET"`
RedisAddr string `env:"REDIS_ADDR" envDefault:"localhost:6379"`
RedisPassword string `env:"REDIS_PASS" envDefault:"admin"`
RedisDB int `env:"REDIS_DB" envDefault:"2"`
SmtpApiUrl string `env:"SMTP_API_URL"`
SmtpHost string `env:"SMTP_HOST"`
SmtpPort string `env:"SMTP_PORT"`
SmtpUsername string `env:"SMTP_UNAME"`
SmtpPassword string `env:"SMTP_PASS"`
SmtpApiKey string `env:"SMTP_API_KEY"`
SmtpSender string `env:"SMTP_SENDER"`
DefaultRedirectionURL string `env:"DEFAULT_REDIRECTION_URL"`
}
func LoadConfig() (*Config, error) {

@ -18,18 +18,18 @@ type User struct {
}
type RestoreRequest struct {
ID string // xid или ObjectID
CreatedAt time.Time
Sign string // подпись
Email string // email из запроса
UserID string // айдишник юзера, которого нашли по email
Sent bool
SentAt time.Time
ID primitive.ObjectID `bson:"_id,omitempty"`
CreatedAt time.Time `bson:"created_at,omitempty"`
Sign []byte `bson:"sign,omitempty"`
Email string `bson:"email,omitempty"`
UserID string `bson:"user_id,omitempty"`
Sent bool `bson:"sent"`
SentAt time.Time `bson:"sent_at"`
}
type RecoveryRecord struct {
UserID string `bson:"user_id"`
Email string `bson:"email"`
Key []byte `bson:"key"`
CreatedAt time.Time `bson:"created_at"`
ID string
UserID string
Email string
Key []byte
}

@ -7,6 +7,7 @@ import (
"github.com/go-redis/redis/v8"
"github.com/pioz/faker"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/readpref"
"time"
@ -49,31 +50,33 @@ func (r *userRepository) FindByEmail(ctx context.Context, email string) (*models
return &user, nil
}
func (r *codewordRepository) StoreRecoveryRecord(ctx context.Context, userID string, email string, key []byte) error {
record := models.RecoveryRecord{
func (r *codewordRepository) StoreRecoveryRecord(ctx context.Context, userID string, email string, key []byte) (string, error) {
newID := primitive.NewObjectID()
record := models.RestoreRequest{
ID: newID,
UserID: userID,
Email: email,
Key: key,
Sign: key,
CreatedAt: time.Now(),
}
_, err := r.mdb.InsertOne(ctx, record)
if err != nil {
return err
return "", err
}
return nil
return newID.Hex(), nil
}
func (r *codewordRepository) InsertToQueue(ctx context.Context, userID string, email string, key []byte) error {
func (r *codewordRepository) InsertToQueue(ctx context.Context, userID string, email string, key []byte, id string) error {
// todo не забыть убрать потом этот цикл
for i := 0; i < 10; i++ {
task := models.RecoveryRecord{
UserID: userID + faker.String(),
Email: email,
Key: key,
CreatedAt: time.Now(),
ID: id,
UserID: userID + faker.String(),
Email: email,
Key: key,
}
taskBytes, err := json.Marshal(task)
@ -89,7 +92,7 @@ func (r *codewordRepository) InsertToQueue(ctx context.Context, userID string, e
return nil
}
func (r *codewordRepository) GetRecoveryRecord(ctx context.Context, key string) (*models.RestoreRequest, error) {
func (r *codewordRepository) GetRecoveryRecord(ctx context.Context, key []byte) (*models.RestoreRequest, error) {
return &models.RestoreRequest{UserID: "123", Sign: key, CreatedAt: time.Now()}, nil
}

@ -8,10 +8,10 @@ import (
)
type CodewordRepository interface {
StoreRecoveryRecord(ctx context.Context, userID string, email string, key []byte) error
InsertToQueue(ctx context.Context, userID string, email string, key []byte) error
StoreRecoveryRecord(ctx context.Context, userID string, email string, key []byte) (string, error)
InsertToQueue(ctx context.Context, userID string, email string, key []byte, id string) error
Ping(ctx context.Context) error
GetRecoveryRecord(ctx context.Context, key string) (*models.RestoreRequest, error)
GetRecoveryRecord(ctx context.Context, key []byte) (*models.RestoreRequest, error)
}
type UserRepository interface {
@ -60,17 +60,21 @@ func (s *RecoveryService) FindUserByEmail(ctx context.Context, email string) (*m
}
// StoreRecoveryRecord сохраняет запись восстановления в базе данных
func (s *RecoveryService) StoreRecoveryRecord(ctx context.Context, userID string, email string, key []byte) error {
return s.repositoryCodeword.StoreRecoveryRecord(ctx, userID, email, key)
func (s *RecoveryService) StoreRecoveryRecord(ctx context.Context, userID string, email string, key []byte) (string, error) {
id, err := s.repositoryCodeword.StoreRecoveryRecord(ctx, userID, email, key)
if err != nil {
return "", err
}
return id, nil
}
// SendRecoveryEmail посылает письмо для восстановления доступа пользователю
func (s *RecoveryService) RecoveryEmailTask(ctx context.Context, userID string, email string, key []byte) error {
return s.repositoryCodeword.InsertToQueue(ctx, userID, email, key)
func (s *RecoveryService) RecoveryEmailTask(ctx context.Context, userID string, email string, key []byte, id string) error {
return s.repositoryCodeword.InsertToQueue(ctx, userID, email, key, id)
}
// GetRecoveryRecord получает запись восстановления из базы данных
func (s *RecoveryService) GetRecoveryRecord(ctx context.Context, key string) (*models.RestoreRequest, error) {
func (s *RecoveryService) GetRecoveryRecord(ctx context.Context, key []byte) (*models.RestoreRequest, error) {
return s.repositoryCodeword.GetRecoveryRecord(ctx, key)
}

@ -6,6 +6,9 @@ import (
"context"
"encoding/json"
"github.com/go-redis/redis/v8"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo"
"go.uber.org/zap"
"time"
)
@ -14,12 +17,14 @@ type Deps struct {
Logger *zap.Logger
Redis *redis.Client
EmailSender *client.RecoveryEmailSender
Mongo *mongo.Collection
}
type recoveryWorker struct {
logger *zap.Logger
redis *redis.Client
emailSender *client.RecoveryEmailSender
mongo *mongo.Collection
}
func NewRecoveryWC(deps Deps) *recoveryWorker {
@ -27,6 +32,7 @@ func NewRecoveryWC(deps Deps) *recoveryWorker {
logger: deps.Logger,
redis: deps.Redis,
emailSender: deps.EmailSender,
mongo: deps.Mongo,
}
}
@ -65,19 +71,46 @@ func (wc *recoveryWorker) processTasks(ctx context.Context) {
return
}
err = wc.sendRecoveryTask(task)
err = wc.sendRecoveryTask(ctx, task)
if err != nil {
wc.logger.Error("Failed to send recovery task", zap.String("key", result[0]), zap.Error(err))
return
}
}
func (wc *recoveryWorker) sendRecoveryTask(task models.RecoveryRecord) error {
func (wc *recoveryWorker) sendRecoveryTask(ctx context.Context, task models.RecoveryRecord) error {
err := wc.emailSender.SendRecoveryEmail(task.Email, task.Key)
if err != nil {
wc.logger.Error("Failed to send recovery email", zap.Error(err))
return err
}
wc.logger.Info("Recovery email sent successfully", zap.String("email", task.Email))
update := bson.M{
"$set": bson.M{
"sent": true,
"sent_at": time.Now(),
},
}
objectID, err := primitive.ObjectIDFromHex(task.ID)
if err != nil {
wc.logger.Error("Invalid ObjectID", zap.String("ID", task.ID), zap.Error(err))
return err
}
filter := bson.M{"_id": objectID}
result, err := wc.mongo.UpdateOne(ctx, filter, update)
if err != nil {
wc.logger.Error("Failed to update restore request", zap.Error(err))
return err
}
if result.ModifiedCount == 0 {
wc.logger.Warn("No documents were updated - this may indicate the document was not found",
zap.String("ID", task.ID))
}
wc.logger.Info("Recovery email sent and restore request updated successfully", zap.String("email", task.Email))
return nil
}

@ -4,6 +4,7 @@ import (
"codeword/internal/models"
"codeword/internal/repository"
"context"
"fmt"
"github.com/stretchr/testify/require"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
@ -77,14 +78,15 @@ func TestStoreRecoveryRecord(t *testing.T) {
email := faker.Email()
key := []byte("test_recovery_key")
err = userRepo.StoreRecoveryRecord(ctx, userID, email, key)
id, err := userRepo.StoreRecoveryRecord(ctx, userID, email, key)
assert.NoError(t, err)
fmt.Println(id)
var storedRecord models.RecoveryRecord
var storedRecord models.RestoreRequest
err = codeword.FindOne(ctx, bson.M{"user_id": userID}).Decode(&storedRecord)
assert.NoError(t, err)
assert.Equal(t, email, storedRecord.Email)
assert.Equal(t, string(key), storedRecord.Key)
assert.Equal(t, key, storedRecord.Sign)
}
_ = database.Drop(ctx)