ikurotime / gitgud

public
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}