main / internal/app/user_service.go
2.2 KB · Go Raw
1package app
2
3import (
4 "context"
5 "errors"
6 "fmt"
7 "regexp"
8 "strings"
9
10 "gitgud/internal/domain"
11)
12
13var usernameRe = regexp.MustCompile(`^[a-z0-9-]{1,39}$`)
14
15type UserService struct {
16 users domain.UserRepository
17 hasher domain.PasswordHasher
18}
19
20func NewUserService(u domain.UserRepository, h domain.PasswordHasher) *UserService {
21 return &UserService{users: u, hasher: h}
22}
23
24func (s *UserService) Register(ctx context.Context, username, email, password string) (*domain.User, error) {
25 username = strings.ToLower(strings.TrimSpace(username))
26 email = strings.TrimSpace(email)
27
28 if username == "" || email == "" || password == "" {
29 return nil, fmt.Errorf("username, email and password are required: %w", domain.ErrValidation)
30 }
31 if !usernameRe.MatchString(username) {
32 return nil, fmt.Errorf("username must be 1-39 chars of a-z, 0-9 or -: %w", domain.ErrValidation)
33 }
34 if len(password) < 8 {
35 return nil, fmt.Errorf("password must be at least 8 characters: %w", domain.ErrValidation)
36 }
37
38 if _, err := s.users.ByUsername(ctx, username); err == nil {
39 return nil, fmt.Errorf("username %q is taken: %w", username, domain.ErrConflict)
40 } else if !errors.Is(err, domain.ErrNotFound) {
41 return nil, err
42 }
43
44 hash, err := s.hasher.Hash(password)
45 if err != nil {
46 return nil, fmt.Errorf("hash password: %w", err)
47 }
48
49 u := &domain.User{
50 Username: username,
51 Email: email,
52 PasswordHash: hash,
53 }
54 if err := s.users.Create(ctx, u); err != nil {
55 return nil, err
56 }
57 return u, nil
58}
59
60func (s *UserService) Authenticate(ctx context.Context, username, password string) (*domain.User, error) {
61 u, err := s.users.ByUsername(ctx, strings.ToLower(strings.TrimSpace(username)))
62 if err != nil {
63 if errors.Is(err, domain.ErrNotFound) {
64 return nil, domain.ErrUnauthorized
65 }
66 return nil, err
67 }
68
69 if err := s.hasher.Compare(u.PasswordHash, password); err != nil {
70 return nil, domain.ErrUnauthorized
71 }
72 return u, nil
73}
74
75func (s *UserService) ByID(ctx context.Context, id string) (*domain.User, error) {
76 return s.users.ByID(ctx, id)
77}
78
79func (s *UserService) ByUsername(ctx context.Context, name string) (*domain.User, error) {
80 return s.users.ByUsername(ctx, strings.ToLower(strings.TrimSpace(name)))
81}