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) }