Files
api_tmpl/internal/service/user.go
T
2026-01-08 01:15:07 +00:00

139 lines
3.5 KiB
Go

package service
import (
"context"
"fmt"
"time"
db "github.com/FernandoJVideira/simple_bank/internal/db/sqlc"
"github.com/FernandoJVideira/simple_bank/internal/security"
"github.com/FernandoJVideira/simple_bank/internal/worker"
"github.com/hibiken/asynq"
"github.com/jackc/pgx/v5/pgtype"
)
type userService struct {
store db.Store
taskDistributor worker.TaskDistributor
}
// NewUserService creates a new user service
func NewUserService(store db.Store, taskDistributor worker.TaskDistributor) UserService {
return &userService{
store: store,
taskDistributor: taskDistributor,
}
}
func (s *userService) CreateUser(ctx context.Context, params RegisterUserRequest) (*RegisterUserResponse, error) {
// Hash the password
hashedPassword, err := security.HashPassword(params.Password)
if err != nil {
return nil, fmt.Errorf("failed to hash password: %w", err)
}
// Prepare transaction parameters
arg := db.CreateUserTxParams{
CreateUserParams: db.CreateUserParams{
Username: params.Username,
FullName: params.FullName,
Email: params.Email,
HashedPassword: hashedPassword,
},
AfterCreate: func(user db.User) error {
opts := []asynq.Option{
asynq.MaxRetry(10),
asynq.ProcessIn(10 * time.Second),
asynq.Queue(worker.CriticalQueue),
}
taskPayload := &worker.SendVerifyEmailPayload{
Username: user.Username,
}
return s.taskDistributor.DestributeTaskSendVerifyEmail(ctx, taskPayload, opts...)
},
}
// Execute transaction
txResult, err := s.store.CreateUserTx(ctx, arg)
if err != nil {
return nil, fmt.Errorf("failed to create user: %w", err)
}
return &RegisterUserResponse{User: txResult.User}, nil
}
func (s *userService) GetUser(ctx context.Context, username string) (*db.User, error) {
user, err := s.store.GetUser(ctx, username)
if err != nil {
return nil, err
}
return &user, nil
}
func (s *userService) UpdateUser(ctx context.Context, params UserUpdateRequest) (*db.User, error) {
arg := db.UpdateUserParams{
Username: params.Username,
FullName: pgtype.Text{
String: *params.FullName,
Valid: params.FullName != nil,
},
Email: pgtype.Text{
String: *params.Email,
Valid: params.Email != nil,
},
}
user, err := s.store.UpdateUser(ctx, arg)
if err != nil {
return nil, err
}
return &user, nil
}
func (s *userService) UpdatePassword(ctx context.Context, params PasswordUpdateRequest) (*db.User, error) {
user, err := s.store.GetUser(ctx, params.Username)
if err != nil {
return nil, err
}
err = security.CheckPassword(params.OldPassword, user.HashedPassword)
if err != nil {
return nil, fmt.Errorf("old password does not match: %w", err)
}
newHashedPassword, err := security.HashPassword(params.NewPassword)
if err != nil {
return nil, fmt.Errorf("failed to hash new password: %w", err)
}
updUser, err := s.store.UpdateUser(ctx, db.UpdateUserParams{
Username: params.Username,
HashedPassword: pgtype.Text{
String: newHashedPassword,
Valid: true,
},
PasswordChangedAt: pgtype.Timestamptz{
Time: time.Now(),
Valid: true,
},
})
if err != nil {
return nil, err
}
return &updUser, nil
}
func (s *userService) VerifyEmail(ctx context.Context, params EmailVerificationRequest) (*EmailVerificationResponse, error) {
txRes, err := s.store.VerifyEmailTx(ctx, db.VerifyEmailTxParams{
EmailID: params.EmailID,
SecretCode: params.SecretCode,
})
if err != nil {
return nil, err
}
return &EmailVerificationResponse{User: txRes.User}, nil
}