user service
This commit is contained in:
parent
1ffbbcec15
commit
70bb4e66b4
113
internal/user/service/service.go
Normal file
113
internal/user/service/service.go
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"log/slog"
|
||||||
|
|
||||||
|
"gitea.michaelthomson.dev/mthomson/habits/internal/user/repository"
|
||||||
|
"github.com/gofrs/uuid/v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrNotFound error = errors.New("user cannot be found")
|
||||||
|
)
|
||||||
|
|
||||||
|
type User struct {
|
||||||
|
Id uuid.UUID
|
||||||
|
Email string
|
||||||
|
HashedPassword string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUser(id uuid.UUID, email string, hashedPassword string) User {
|
||||||
|
return User{Id: id, Email: email, HashedPassword: hashedPassword}
|
||||||
|
}
|
||||||
|
|
||||||
|
func UserFromUserRow(userRow repository.UserRow) User {
|
||||||
|
return User{Id: userRow.Id, Email: userRow.Email, HashedPassword: userRow.HashedPassword}
|
||||||
|
}
|
||||||
|
|
||||||
|
func UserRowFromUser(user User) repository.UserRow {
|
||||||
|
return repository.UserRow{Id: user.Id, Email: user.Email, HashedPassword: user.HashedPassword}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t User) Equal(user User) bool {
|
||||||
|
return t.Id == user.Id && t.Email == user.Email && t.HashedPassword == user.HashedPassword
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserRepository interface {
|
||||||
|
Create(ctx context.Context, user repository.UserRow) (repository.UserRow, error)
|
||||||
|
GetById(ctx context.Context, id uuid.UUID) (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
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUserService(logger *slog.Logger, userRepo UserRepository) *UserService {
|
||||||
|
return &UserService{
|
||||||
|
logger: logger,
|
||||||
|
repo: userRepo,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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) CreateUser(ctx context.Context, user User) (User, error) {
|
||||||
|
userRow := UserRowFromUser(user)
|
||||||
|
|
||||||
|
newUserRow, err := s.repo.Create(ctx, userRow)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
s.logger.ErrorContext(ctx, err.Error())
|
||||||
|
return User{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return UserFromUserRow(newUserRow), 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) UpdateUser(ctx context.Context, user User) error {
|
||||||
|
userRow := UserRowFromUser(user)
|
||||||
|
|
||||||
|
err := s.repo.Update(ctx, userRow)
|
||||||
|
|
||||||
|
if err == repository.ErrNotFound {
|
||||||
|
return ErrNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
s.logger.ErrorContext(ctx, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
155
internal/user/service/service_test.go
Normal file
155
internal/user/service/service_test.go
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
package service_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log/slog"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"gitea.michaelthomson.dev/mthomson/habits/internal/test"
|
||||||
|
"gitea.michaelthomson.dev/mthomson/habits/internal/user/repository"
|
||||||
|
"gitea.michaelthomson.dev/mthomson/habits/internal/user/service"
|
||||||
|
"github.com/gofrs/uuid/v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCreateUser(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
ctx := context.Background()
|
||||||
|
logger := slog.Default()
|
||||||
|
tdb := test.NewTestDatabase(t)
|
||||||
|
defer tdb.TearDown()
|
||||||
|
r := repository.NewUserRepository(logger, tdb.Db)
|
||||||
|
|
||||||
|
userService := service.NewUserService(logger, r)
|
||||||
|
|
||||||
|
t.Run("Create user", func(t *testing.T) {
|
||||||
|
uuid := NewUUID(t)
|
||||||
|
user := service.NewUser(uuid, "test@test.com", "supersecurehash")
|
||||||
|
|
||||||
|
_, err := userService.CreateUser(ctx, user)
|
||||||
|
|
||||||
|
AssertNoError(t, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetUser(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
ctx := context.Background()
|
||||||
|
logger := slog.Default()
|
||||||
|
tdb := test.NewTestDatabase(t)
|
||||||
|
defer tdb.TearDown()
|
||||||
|
r := repository.NewUserRepository(logger, tdb.Db)
|
||||||
|
uuid := NewUUID(t)
|
||||||
|
|
||||||
|
row := repository.UserRow{Id: uuid, Email: "test@test.com", HashedPassword: "supersecurehash"}
|
||||||
|
_, err := r.Create(ctx, row)
|
||||||
|
AssertNoError(t, err);
|
||||||
|
|
||||||
|
userService := service.NewUserService(logger, r)
|
||||||
|
|
||||||
|
t.Run("Get exisiting user", func(t *testing.T) {
|
||||||
|
_, err := userService.GetUser(ctx, uuid)
|
||||||
|
|
||||||
|
AssertNoError(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Get non-existant user", func(t *testing.T) {
|
||||||
|
_, err := userService.GetUser(ctx, NewUUID(t))
|
||||||
|
|
||||||
|
AssertErrors(t, err, service.ErrNotFound)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDeleteUser(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
ctx := context.Background()
|
||||||
|
logger := slog.Default()
|
||||||
|
tdb := test.NewTestDatabase(t)
|
||||||
|
defer tdb.TearDown()
|
||||||
|
r := repository.NewUserRepository(logger, tdb.Db)
|
||||||
|
uuid := NewUUID(t)
|
||||||
|
|
||||||
|
row := repository.UserRow{Id: uuid, Email: "test@test.com", HashedPassword: "supersecurehash"}
|
||||||
|
_, err := r.Create(ctx, row)
|
||||||
|
AssertNoError(t, err);
|
||||||
|
|
||||||
|
userService := service.NewUserService(logger, r)
|
||||||
|
|
||||||
|
t.Run("Delete exisiting user", func(t *testing.T) {
|
||||||
|
err := userService.DeleteUser(ctx, uuid)
|
||||||
|
|
||||||
|
AssertNoError(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Delete non-existant user", func(t *testing.T) {
|
||||||
|
err := userService.DeleteUser(ctx, uuid)
|
||||||
|
|
||||||
|
AssertErrors(t, err, service.ErrNotFound)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUpdateUser(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
ctx := context.Background()
|
||||||
|
logger := slog.Default()
|
||||||
|
tdb := test.NewTestDatabase(t)
|
||||||
|
defer tdb.TearDown()
|
||||||
|
r := repository.NewUserRepository(logger, tdb.Db)
|
||||||
|
uuid := NewUUID(t)
|
||||||
|
|
||||||
|
row := repository.UserRow{Id: uuid, Email: "test@test.com", HashedPassword: "supersecurehash"}
|
||||||
|
_, err := r.Create(ctx, row)
|
||||||
|
AssertNoError(t, err);
|
||||||
|
|
||||||
|
userService := service.NewUserService(logger, r)
|
||||||
|
|
||||||
|
t.Run("Update exisiting user", func(t *testing.T) {
|
||||||
|
user := service.User{uuid, "new@email.com", "supersecurehash"}
|
||||||
|
|
||||||
|
err := userService.UpdateUser(ctx, user)
|
||||||
|
|
||||||
|
AssertNoError(t, err)
|
||||||
|
|
||||||
|
newUser, err := userService.GetUser(ctx, uuid)
|
||||||
|
|
||||||
|
AssertNoError(t, err)
|
||||||
|
|
||||||
|
AssertUsers(t, newUser, user)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Update non-existant user", func(t *testing.T) {
|
||||||
|
user := service.User{NewUUID(t), "new@email.com", "supersecurehash"}
|
||||||
|
|
||||||
|
err := userService.UpdateUser(ctx, user)
|
||||||
|
|
||||||
|
AssertErrors(t, err, service.ErrNotFound)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func AssertErrors(t testing.TB, got, want error) {
|
||||||
|
t.Helper()
|
||||||
|
if got != want {
|
||||||
|
t.Errorf("got error: %v, want error: %v", want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func AssertNoError(t testing.TB, err error) {
|
||||||
|
t.Helper()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error, got %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func AssertUsers(t testing.TB, got, want service.User) {
|
||||||
|
if !got.Equal(want) {
|
||||||
|
t.Errorf("got %+v want %+v", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUUID(t testing.TB) uuid.UUID {
|
||||||
|
t.Helper()
|
||||||
|
uuid, err := uuid.NewV4()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("error generation uuid: %v", err)
|
||||||
|
}
|
||||||
|
return uuid
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user