todo/internal/user/service/service.go
Michael Thomson e55d419d44
All checks were successful
ci/woodpecker/pr/build Pipeline was successful
ci/woodpecker/pr/lint Pipeline was successful
ci/woodpecker/pr/test Pipeline was successful
auth services, middleware, and other stuff
2025-05-22 13:55:43 -04:00

186 lines
3.8 KiB
Go

package service
import (
"context"
"errors"
"log/slog"
"gitea.michaelthomson.dev/mthomson/habits/internal/auth"
"gitea.michaelthomson.dev/mthomson/habits/internal/user/repository"
"github.com/gofrs/uuid/v5"
)
var (
ErrNotFound error = errors.New("user cannot be found")
ErrUserExists error = errors.New("user already exists")
)
type User struct {
Id uuid.UUID
Email string
}
func UserFromUserRow(userRow repository.UserRow) User {
return User{Id: userRow.Id, Email: userRow.Email}
}
func (t User) Equal(user User) bool {
return t.Id == user.Id && t.Email == user.Email
}
type UserRepository interface {
Create(ctx context.Context, user repository.UserRow) (repository.UserRow, error)
GetById(ctx context.Context, id uuid.UUID) (repository.UserRow, error)
GetByEmail(ctx context.Context, email string) (repository.UserRow, error)
Update(ctx context.Context, user repository.UserRow) error
Delete(ctx context.Context, id uuid.UUID) error
}
type UserService struct {
logger *slog.Logger
repo UserRepository
argon2IdHash *auth.Argon2IdHash
}
func NewUserService(logger *slog.Logger, userRepo UserRepository, argon2IdHash *auth.Argon2IdHash) *UserService {
return &UserService{
logger: logger,
repo: userRepo,
argon2IdHash: argon2IdHash,
}
}
func (s *UserService) GetUser(ctx context.Context, id uuid.UUID) (User, error) {
user, err := s.repo.GetById(ctx, id)
if err != nil {
if err == repository.ErrNotFound {
return User{}, ErrNotFound
}
s.logger.ErrorContext(ctx, err.Error())
return User{}, err
}
return UserFromUserRow(user), err
}
func (s *UserService) Register(ctx context.Context, email string, password string) (uuid.UUID, error) {
uuid, err := uuid.NewV4()
if err != nil {
s.logger.ErrorContext(ctx, err.Error())
return uuid, err
}
_, err = s.repo.GetByEmail(ctx, email)
if err != repository.ErrNotFound {
return uuid, ErrUserExists
}
hashSalt, err := s.argon2IdHash.GenerateHash([]byte(password), nil)
if err != nil {
s.logger.ErrorContext(ctx, err.Error())
return uuid, err
}
userRow := repository.UserRow{Id: uuid, Email: email, HashedPassword: hashSalt.Hash, Salt: hashSalt.Salt}
_, err = s.repo.Create(ctx, userRow)
if err != nil {
s.logger.ErrorContext(ctx, err.Error())
return uuid, err
}
return uuid, err
}
func (s *UserService) DeleteUser(ctx context.Context, id uuid.UUID) error {
err := s.repo.Delete(ctx, id)
if err == repository.ErrNotFound {
return ErrNotFound
}
if err != nil {
s.logger.ErrorContext(ctx, err.Error())
}
return err
}
func (s *UserService) UpdateUserEmail(ctx context.Context, id uuid.UUID, email string) error {
user, err := s.repo.GetById(ctx, id)
if err == repository.ErrNotFound {
return ErrNotFound
}
if err != nil {
s.logger.ErrorContext(ctx, err.Error())
return err
}
_, err = s.repo.GetByEmail(ctx, email)
switch err {
case repository.ErrNotFound:
user.Email = email
err = s.repo.Update(ctx, user)
if err == repository.ErrNotFound {
return ErrNotFound
}
if err != nil {
s.logger.ErrorContext(ctx, err.Error())
}
return err
case nil:
return ErrUserExists
default:
s.logger.ErrorContext(ctx, err.Error())
return err
}
}
func (s *UserService) UpdateUserPassword(ctx context.Context, id uuid.UUID, password string) error {
user, err := s.repo.GetById(ctx, id)
if err == repository.ErrNotFound {
return ErrNotFound
}
if err != nil {
s.logger.ErrorContext(ctx, err.Error())
return err
}
hashSalt, err := s.argon2IdHash.GenerateHash([]byte(password), nil)
if err != nil {
s.logger.ErrorContext(ctx, err.Error())
return err
}
user.HashedPassword = hashSalt.Hash
user.Salt = hashSalt.Salt
err = s.repo.Update(ctx, user)
if err == repository.ErrNotFound {
return ErrNotFound
}
if err != nil {
s.logger.ErrorContext(ctx, err.Error())
}
return err
}