some ref after review

This commit is contained in:
Pavel 2024-01-15 11:43:55 +03:00
parent 881add8f6d
commit e5c3b3786d
9 changed files with 68 additions and 45 deletions

2
go.mod

@ -8,6 +8,7 @@ require (
github.com/gofiber/fiber/v2 v2.51.0 github.com/gofiber/fiber/v2 v2.51.0
github.com/joho/godotenv v1.5.1 github.com/joho/godotenv v1.5.1
github.com/pioz/faker v1.7.3 github.com/pioz/faker v1.7.3
github.com/rs/xid v1.5.0
github.com/stretchr/testify v1.8.1 github.com/stretchr/testify v1.8.1
go.mongodb.org/mongo-driver v1.13.1 go.mongodb.org/mongo-driver v1.13.1
go.uber.org/zap v1.26.0 go.uber.org/zap v1.26.0
@ -27,7 +28,6 @@ require (
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rivo/uniseg v0.2.0 // indirect github.com/rivo/uniseg v0.2.0 // indirect
github.com/rs/xid v1.5.0 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasthttp v1.50.0 // indirect github.com/valyala/fasthttp v1.50.0 // indirect
github.com/valyala/tcplisten v1.0.0 // indirect github.com/valyala/tcplisten v1.0.0 // indirect

@ -17,10 +17,13 @@ import (
func Run(ctx context.Context, cfg initialize.Config, logger *zap.Logger) error { func Run(ctx context.Context, cfg initialize.Config, logger *zap.Logger) error {
logger.Info("Запуск приложения", zap.String("AppName", cfg.AppName)) logger.Info("Запуск приложения", zap.String("AppName", cfg.AppName))
ctx, cancel := context.WithCancel(ctx)
defer cancel()
mdb, err := initialize.MongoDB(ctx, cfg) mdb, err := initialize.MongoDB(ctx, cfg)
if err != nil { if err != nil {
logger.Error("Failed to initialize MongoDB", zap.Error(err)) logger.Error("Failed to initialize MongoDB", zap.Error(err))
return err cancel()
} }
rdb, err := initialize.Redis(ctx, cfg) rdb, err := initialize.Redis(ctx, cfg)

@ -2,8 +2,11 @@ package recovery
import ( import (
"codeword/internal/models" "codeword/internal/models"
"codeword/internal/repository"
"codeword/internal/services" "codeword/internal/services"
"encoding/base64" "encoding/base64"
"errors"
"fmt"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"go.uber.org/zap" "go.uber.org/zap"
"time" "time"
@ -24,25 +27,44 @@ func NewRecoveryController(logger *zap.Logger, service *services.RecoveryService
} }
func (r *RecoveryController) HandlePingDB(c *fiber.Ctx) error { func (r *RecoveryController) HandlePingDB(c *fiber.Ctx) error {
return r.service.Ping(c.Context()) startTime := time.Now()
if err := r.service.Ping(c.Context()); err != nil {
r.logger.Error("Failed to ping the database", zap.Error(err))
return c.Status(fiber.StatusServiceUnavailable).SendString("DB ping failed")
}
duration := time.Since(startTime)
durationMillis := duration.Milliseconds()
responseMessage := fmt.Sprintf("DB ping success - Time taken: %d ms", durationMillis)
return c.Status(fiber.StatusOK).SendString(responseMessage)
} }
// HandleRecoveryRequest обрабатывает запрос на восстановление пароля // todo update docs
func (r *RecoveryController) HandleRecoveryRequest(c *fiber.Ctx) error { func (r *RecoveryController) HandleRecoveryRequest(c *fiber.Ctx) error {
email := c.FormValue("email") var req models.RecoveryRequest
referralURL := c.Get("Referrer") if err := c.BodyParser(&req); err != nil {
redirectionURL := c.FormValue("RedirectionURL") r.logger.Error("Failed to parse recovery request", zap.Error(err))
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Bad Request"})
if redirectionURL == "" && referralURL != "" {
redirectionURL = referralURL
} else if redirectionURL == "" {
redirectionURL = r.defaultURL
} }
user, err := r.service.FindUserByEmail(c.Context(), email) referralURL := c.Get("Referrer")
if err != nil || user == nil {
if req.RedirectionURL == "" && referralURL != "" {
req.RedirectionURL = referralURL
} else if req.RedirectionURL == "" {
req.RedirectionURL = r.defaultURL
}
user, err := r.service.FindUserByEmail(c.Context(), req.Email)
if err != nil {
r.logger.Error("Failed to find user by email", zap.Error(err)) r.logger.Error("Failed to find user by email", zap.Error(err))
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "User not found"})
if errors.Is(err, repository.ErrPromoUserNotFound) {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "User not found"})
}
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Internal Server Error"})
} }
key, err := r.service.GenerateKey() key, err := r.service.GenerateKey()
@ -51,7 +73,7 @@ func (r *RecoveryController) HandleRecoveryRequest(c *fiber.Ctx) error {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Internal Server Error"}) return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Internal Server Error"})
} }
signUrl := redirectionURL + base64.URLEncoding.EncodeToString(key) signUrl := req.RedirectionURL + base64.URLEncoding.EncodeToString(key)
sign := base64.URLEncoding.EncodeToString(key) sign := base64.URLEncoding.EncodeToString(key)
id, err := r.service.StoreRecoveryRecord(c.Context(), models.StoreRecDeps{UserID: user.ID.Hex(), Email: user.Email, Key: sign, Url: signUrl}) id, err := r.service.StoreRecoveryRecord(c.Context(), models.StoreRecDeps{UserID: user.ID.Hex(), Email: user.Email, Key: sign, Url: signUrl})
@ -62,7 +84,7 @@ func (r *RecoveryController) HandleRecoveryRequest(c *fiber.Ctx) error {
signWithID := sign + id // подпись с id записи signWithID := sign + id // подпись с id записи
err = r.service.RecoveryEmailTask(c.Context(), models.RecEmailDeps{UserID: user.ID.Hex(), Email: email, SignWithID: signWithID, ID: id}) err = r.service.RecoveryEmailTask(c.Context(), models.RecEmailDeps{UserID: user.ID.Hex(), Email: req.Email, SignWithID: signWithID, ID: 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"})
@ -73,20 +95,21 @@ func (r *RecoveryController) HandleRecoveryRequest(c *fiber.Ctx) error {
}) })
} }
// todo тут скорее всего помимо подписи будет передаваться еще что-то, например email пользователя от фронта для поиска в бд
// HandleRecoveryLink обрабатывает ссылку восстановления и обменивает ее на токены
func (r *RecoveryController) HandleRecoveryLink(c *fiber.Ctx) error { func (r *RecoveryController) HandleRecoveryLink(c *fiber.Ctx) error {
key := c.Params("sign") var req models.RecoveryLinkRequest
if err := c.BodyParser(&req); err != nil {
r.logger.Error("Failed to parse recovery link request", zap.Error(err))
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Bad Request"})
}
record, err := r.service.GetRecoveryRecord(c.Context(), key) record, err := r.service.GetRecoveryRecord(c.Context(), req.Sign)
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"})
} }
if time.Since(record.CreatedAt) > 15*time.Minute { if time.Since(record.CreatedAt) > 15*time.Minute {
r.logger.Error("Recovery link expired", zap.String("signature", key)) r.logger.Error("Recovery link expired", zap.String("signature", req.Sign))
return c.Status(fiber.StatusNotAcceptable).JSON(fiber.Map{"error": "Recovery link expired"}) return c.Status(fiber.StatusNotAcceptable).JSON(fiber.Map{"error": "Recovery link expired"})
} }

@ -17,6 +17,9 @@ func MongoDB(ctx context.Context, cfg Config) (*mongo.Database, error) {
MongoAuth: cfg.MongoAuth, MongoAuth: cfg.MongoAuth,
} }
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()
mongoDeps := &mdb.ConnectDeps{ mongoDeps := &mdb.ConnectDeps{
Configuration: dbConfig, Configuration: dbConfig,
Timeout: 10 * time.Second, Timeout: 10 * time.Second,

@ -35,3 +35,12 @@ type RecoveryRecord struct {
Email string Email string
Key string Key string
} }
type RecoveryRequest struct {
Email string `json:"email"`
RedirectionURL string `json:"redirectionURL"`
}
type RecoveryLinkRequest struct {
Sign string `json:"sign"`
}

@ -81,6 +81,8 @@ func (r *CodewordRepository) GetRecoveryRecord(ctx context.Context, key string)
// пингует в монгу чтобы проверить подключение // пингует в монгу чтобы проверить подключение
func (r *CodewordRepository) Ping(ctx context.Context) error { func (r *CodewordRepository) Ping(ctx context.Context) error {
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
if err := r.mdb.Database().Client().Ping(ctx, readpref.Primary()); err != nil { if err := r.mdb.Database().Client().Ping(ctx, readpref.Primary()); err != nil {
return err return err
} }

@ -3,6 +3,7 @@ package repository
import ( import (
"codeword/internal/models" "codeword/internal/models"
"context" "context"
"errors"
"github.com/go-redis/redis/v8" "github.com/go-redis/redis/v8"
"go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo"
@ -17,6 +18,8 @@ type UserRepository struct {
mdb *mongo.Collection mdb *mongo.Collection
} }
var ErrPromoUserNotFound = errors.New("user not found")
func NewUserRepository(deps Deps) *UserRepository { func NewUserRepository(deps Deps) *UserRepository {
return &UserRepository{mdb: deps.Mdb} return &UserRepository{mdb: deps.Mdb}
@ -31,7 +34,7 @@ func (r *UserRepository) FindByEmail(ctx context.Context, email string) (*models
if err == mongo.ErrNoDocuments { if err == mongo.ErrNoDocuments {
return nil, nil return nil, nil
} }
return nil, err return nil, ErrPromoUserNotFound
} }
return &user, nil return &user, nil
} }

@ -4,10 +4,8 @@ import (
"codeword/internal/controller/promocode" "codeword/internal/controller/promocode"
"codeword/internal/controller/recovery" "codeword/internal/controller/recovery"
"context" "context"
"fmt"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"go.uber.org/zap" "go.uber.org/zap"
"time"
) )
type ServerConfig struct { type ServerConfig struct {
@ -52,7 +50,7 @@ func (s *Server) Shutdown(ctx context.Context) error {
func (s *Server) registerRoutes() { func (s *Server) registerRoutes() {
s.app.Get("/liveness", s.handleLiveness) s.app.Get("/liveness", s.handleLiveness)
s.app.Get("/readiness", s.handleReadiness) s.app.Get("/readiness", s.RecoveryController.HandlePingDB)
s.app.Post("/recover", s.RecoveryController.HandleRecoveryRequest) s.app.Post("/recover", s.RecoveryController.HandleRecoveryRequest)
s.app.Get("/recover/:sign", s.RecoveryController.HandleRecoveryLink) s.app.Get("/recover/:sign", s.RecoveryController.HandleRecoveryLink)
@ -69,17 +67,3 @@ func (s *Server) registerRoutes() {
func (s *Server) handleLiveness(c *fiber.Ctx) error { func (s *Server) handleLiveness(c *fiber.Ctx) error {
return c.SendStatus(fiber.StatusOK) return c.SendStatus(fiber.StatusOK)
} }
func (s *Server) handleReadiness(c *fiber.Ctx) error {
startTime := time.Now()
if err := s.RecoveryController.HandlePingDB(c); err != nil {
s.Logger.Error("Failed to ping the database", zap.Error(err))
return c.Status(fiber.StatusServiceUnavailable).SendString("DB ping failed")
}
duration := time.Since(startTime)
durationMillis := duration.Milliseconds()
responseMessage := fmt.Sprintf("DB ping success - Time taken: %d ms", durationMillis)
return c.Status(fiber.StatusOK).SendString(responseMessage)
}

@ -73,10 +73,6 @@ func (s *RecoveryService) FindUserByEmail(ctx context.Context, email string) (*m
s.logger.Error("Failed to find user by email", zap.String("email", email), zap.Error(err)) s.logger.Error("Failed to find user by email", zap.String("email", email), zap.Error(err))
return nil, err return nil, err
} }
if user == nil {
s.logger.Info("No user found with email", zap.String("email", email))
return nil, nil
}
return user, nil return user, nil
} }