Dmitri af256b66b5
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 3m23s
added transaction in registration
2026-04-21 10:21:16 +02:00

124 lines
3.0 KiB
Go

package users
import (
"context"
"errors"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgxpool"
"go.uber.org/zap"
"git.kanopo.dev/rhythm/rhythm-backend/internal/config"
usersdb "git.kanopo.dev/rhythm/rhythm-backend/internal/db/users"
"git.kanopo.dev/rhythm/rhythm-backend/internal/jwt"
"git.kanopo.dev/rhythm/rhythm-backend/internal/password"
)
var ErrInvalidCredentials = errors.New("invalid credentials")
type AuthResult struct {
AccessToken string
RefreshToken string
}
type Service interface {
Login(ctx context.Context, email, password string) (*AuthResult, error)
Register(ctx context.Context, email, password string) (*AuthResult, error)
GetUserByEmail(ctx context.Context, email string) (usersdb.User, error)
}
type service struct {
pool *pgxpool.Pool
repo usersdb.Querier
cfg *config.Config
log *zap.SugaredLogger
}
func NewService(repo usersdb.Querier, cfg *config.Config, log *zap.SugaredLogger, pool *pgxpool.Pool) Service {
return &service{
repo: repo,
cfg: cfg,
log: log,
pool: pool,
}
}
func (s *service) Login(ctx context.Context, email, passwordPlain string) (*AuthResult, error) {
user, err := s.repo.GetUserByEmail(ctx, email)
if err != nil {
s.log.Infof("login failed: user not found with email %v", email)
return nil, ErrInvalidCredentials
}
if !password.CheckPassword(passwordPlain, user.Password) {
s.log.Infof("login failed: invalid password for email %v", email)
return nil, ErrInvalidCredentials
}
tokenPair, err := jwt.GenerateTokenPair(user.ID.String(), s.cfg)
if err != nil {
s.log.Errorf("failed to generate token pair %v", err)
return nil, err
}
s.log.Infof("user logged in successfully with email %v", email)
return &AuthResult{
AccessToken: tokenPair.AccessToken,
RefreshToken: tokenPair.RefreshToken,
}, nil
}
func (s *service) Register(ctx context.Context, email, passwordPlain string) (*AuthResult, error) {
tx, err := s.pool.BeginTx(ctx, pgx.TxOptions{})
if err != nil {
return nil, err
}
defer tx.Rollback(ctx)
txRepo := usersdb.New(tx)
_, err = txRepo.GetUserByEmail(ctx, email)
if err == nil {
s.log.Infof("registration failed: email already exists %v", email)
return nil, ErrInvalidCredentials
}
if !errors.Is(err, pgx.ErrNoRows) {
return nil, err
}
hash, err := password.HashPassword(passwordPlain)
if err != nil {
return nil, err
}
user, err := txRepo.CreateUser(ctx, usersdb.CreateUserParams{
Email: email,
Password: hash,
})
if err != nil {
return nil, err
}
if err := tx.Commit(ctx); err != nil {
return nil, err
}
tokenPair, err := jwt.GenerateTokenPair(user.ID.String(), s.cfg)
if err != nil {
s.log.Error("failed to generate token pair", "error", err)
return nil, err
}
s.log.Info("user registered successfully", "email", email)
return &AuthResult{
AccessToken: tokenPair.AccessToken,
RefreshToken: tokenPair.RefreshToken,
}, nil
}
func (s *service) GetUserByEmail(ctx context.Context, email string) (usersdb.User, error) {
return s.repo.GetUserByEmail(ctx, email)
}