Rename the module, proto service, and all identifiers from simple_bank/ SimpleBank to api_tmpl/ApiTmpl, and regenerate the gRPC/gateway/swagger/ mock code accordingly. - Scrub real secrets (SMTP password, auth token secret) from api.env/ api_test.env, untrack them, and add api.env.example/api_test.env.example - Remove committed build artifacts (tmp/main, tmp/build-errors.log, dbml-error.log) and gitignore them going forward - Fix Dockerfile paths (build target, migrations copy) that referenced a stale project layout - Rewrite README to match the actual cmd/ + internal/ structure and add a "using this as a template" section
139 lines
3.5 KiB
Go
139 lines
3.5 KiB
Go
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
|
|
}
|