package service import ( "context" "fmt" "time" db "github.com/FernandoJVideira/api_tmpl/internal/db/sqlc" "github.com/FernandoJVideira/api_tmpl/internal/security" "github.com/FernandoJVideira/api_tmpl/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 }