add task struct
This commit is contained in:
parent
5e1a04a02c
commit
922222d2c8
3
.env
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)
|
||||
|
Loading…
Reference in New Issue
Block a user