Files
FernandoJVideira c4ee0ce4dc chore: rename simple_bank to api_tmpl and clean up for template use
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
2026-08-25 03:02:37 +01:00

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
}