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_PASS="vWwbCSg4bf0p"
SMTP_API_KEY="P0YsjUB137upXrr1NiJefHmXVKW1hmBWlpev" SMTP_API_KEY="P0YsjUB137upXrr1NiJefHmXVKW1hmBWlpev"
SMTP_SENDER="noreply@mailing.pena.digital" 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, EncryptService: encryptService,
}) })
recoveryController := controller.NewRecoveryController(logger, recoveryService) recoveryController := controller.NewRecoveryController(logger, recoveryService, cfg.DefaultRedirectionURL)
recoveryWC := recovery_worker.NewRecoveryWC(recovery_worker.Deps{ recoveryWC := recovery_worker.NewRecoveryWC(recovery_worker.Deps{
Logger: logger, Logger: logger,
Redis: rdb, Redis: rdb,
EmailSender: recoveryEmailSender, EmailSender: recoveryEmailSender,
Mongo: mdb.Collection("codeword"),
}) })
go recoveryWC.Start(ctx) go recoveryWC.Start(ctx)

@ -8,14 +8,16 @@ import (
) )
type RecoveryController struct { type RecoveryController struct {
logger *zap.Logger logger *zap.Logger
service *services.RecoveryService 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{ return &RecoveryController{
logger: logger, logger: logger,
service: service, service: service,
defaultURL: defaultRedirectionURL,
} }
} }
@ -23,9 +25,18 @@ func (r *RecoveryController) HandlePingDB(c *fiber.Ctx) error {
return r.service.Ping(c.Context()) return r.service.Ping(c.Context())
} }
// TODO add deps struct, counnt params >3
// HandleRecoveryRequest обрабатывает запрос на восстановление пароля // HandleRecoveryRequest обрабатывает запрос на восстановление пароля
func (r *RecoveryController) HandleRecoveryRequest(c *fiber.Ctx) error { func (r *RecoveryController) HandleRecoveryRequest(c *fiber.Ctx) error {
email := c.FormValue("email") 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() key, err := r.service.GenerateKey()
if err != nil { 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"}) 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 { if err != nil {
r.logger.Error("Failed to store recovery record", zap.Error(err)) r.logger.Error("Failed to store recovery record", zap.Error(err))
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Internal Server Error"}) 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 { if err != nil {
r.logger.Error("Failed to send recovery email", zap.Error(err)) 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.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 обрабатывает ссылку восстановления и обменивает ее на токены // HandleRecoveryLink обрабатывает ссылку восстановления и обменивает ее на токены
func (r *RecoveryController) HandleRecoveryLink(c *fiber.Ctx) error { func (r *RecoveryController) HandleRecoveryLink(c *fiber.Ctx) error {
key := c.Params("sign") key := c.Params("sign")
// тут получается // тут получается
record, err := r.service.GetRecoveryRecord(c.Context(), key) record, err := r.service.GetRecoveryRecord(c.Context(), []byte(key))
if err != nil { if err != nil {
r.logger.Error("Failed to get recovery record", zap.Error(err)) r.logger.Error("Failed to get recovery record", zap.Error(err))
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Internal Server Error"}) return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Internal Server Error"})

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

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

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

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

@ -6,6 +6,9 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"github.com/go-redis/redis/v8" "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" "go.uber.org/zap"
"time" "time"
) )
@ -14,12 +17,14 @@ type Deps struct {
Logger *zap.Logger Logger *zap.Logger
Redis *redis.Client Redis *redis.Client
EmailSender *client.RecoveryEmailSender EmailSender *client.RecoveryEmailSender
Mongo *mongo.Collection
} }
type recoveryWorker struct { type recoveryWorker struct {
logger *zap.Logger logger *zap.Logger
redis *redis.Client redis *redis.Client
emailSender *client.RecoveryEmailSender emailSender *client.RecoveryEmailSender
mongo *mongo.Collection
} }
func NewRecoveryWC(deps Deps) *recoveryWorker { func NewRecoveryWC(deps Deps) *recoveryWorker {
@ -27,6 +32,7 @@ func NewRecoveryWC(deps Deps) *recoveryWorker {
logger: deps.Logger, logger: deps.Logger,
redis: deps.Redis, redis: deps.Redis,
emailSender: deps.EmailSender, emailSender: deps.EmailSender,
mongo: deps.Mongo,
} }
} }
@ -65,19 +71,46 @@ func (wc *recoveryWorker) processTasks(ctx context.Context) {
return return
} }
err = wc.sendRecoveryTask(task) err = wc.sendRecoveryTask(ctx, task)
if err != nil { if err != nil {
wc.logger.Error("Failed to send recovery task", zap.String("key", result[0]), zap.Error(err)) wc.logger.Error("Failed to send recovery task", zap.String("key", result[0]), zap.Error(err))
return 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) err := wc.emailSender.SendRecoveryEmail(task.Email, task.Key)
if err != nil { if err != nil {
wc.logger.Error("Failed to send recovery email", zap.Error(err)) wc.logger.Error("Failed to send recovery email", zap.Error(err))
return 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 return nil
} }

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