user repository
This commit is contained in:
parent
b4b634fd3f
commit
1ffbbcec15
@ -5,7 +5,7 @@ CREATE TABLE todo(
|
|||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE users(
|
CREATE TABLE users(
|
||||||
id uuid PRIMARY KEY,
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
email VARCHAR NOT NULL,
|
email VARCHAR NOT NULL,
|
||||||
hashed_password VARCHAR NOT NULL
|
hashed_password VARCHAR NOT NULL
|
||||||
);
|
);
|
||||||
|
115
internal/user/repository/repository.go
Normal file
115
internal/user/repository/repository.go
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"log/slog"
|
||||||
|
|
||||||
|
"github.com/gofrs/uuid/v5"
|
||||||
|
"github.com/jackc/pgx/v5"
|
||||||
|
"github.com/jackc/pgx/v5/pgxpool"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrNotFound error = errors.New("user cannot be found")
|
||||||
|
)
|
||||||
|
|
||||||
|
type UserRow struct {
|
||||||
|
Id uuid.UUID
|
||||||
|
Email string
|
||||||
|
HashedPassword string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUserRow(id uuid.UUID, email string, hashedPassword string) UserRow {
|
||||||
|
return UserRow{Id: id, Email: email, HashedPassword: hashedPassword}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u UserRow) Equal(user UserRow) bool {
|
||||||
|
return u.Id == user.Id && u.Email == user.Email && u.HashedPassword == user.HashedPassword
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserRepository struct {
|
||||||
|
logger *slog.Logger
|
||||||
|
db *pgxpool.Pool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUserRepository(logger *slog.Logger, db *pgxpool.Pool) *UserRepository {
|
||||||
|
return &UserRepository{
|
||||||
|
logger: logger,
|
||||||
|
db: db,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *UserRepository) GetById(ctx context.Context, id uuid.UUID) (UserRow, error) {
|
||||||
|
user := UserRow{}
|
||||||
|
|
||||||
|
err := r.db.QueryRow(ctx, "SELECT * FROM users WHERE id = $1;", id).Scan(&user.Id, &user.Email, &user.HashedPassword)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if err == pgx.ErrNoRows {
|
||||||
|
return user, ErrNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
r.logger.ErrorContext(ctx, err.Error())
|
||||||
|
return user, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return user, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *UserRepository) Create(ctx context.Context, user UserRow) (UserRow, error) {
|
||||||
|
var result pgx.Row
|
||||||
|
if user.Id.IsNil() {
|
||||||
|
result = r.db.QueryRow(ctx, "INSERT INTO users (email, hashed_password) VALUES ($1, $2) RETURNING id;", user.Email, user.HashedPassword)
|
||||||
|
|
||||||
|
err := result.Scan(&user.Id)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
r.logger.ErrorContext(ctx, err.Error())
|
||||||
|
return UserRow{}, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_, err := r.db.Exec(ctx, "INSERT INTO users (id, email, hashed_password) VALUES ($1, $2, $3);", user.Id, user.Email, user.HashedPassword)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
r.logger.ErrorContext(ctx, err.Error())
|
||||||
|
return UserRow{}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return user, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *UserRepository) Update(ctx context.Context, user UserRow) error {
|
||||||
|
result, err := r.db.Exec(ctx, "UPDATE users SET email = $1, hashed_password = $2 WHERE id = $3;", user.Email, user.HashedPassword, user.Id)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
r.logger.ErrorContext(ctx, err.Error())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
rowsAffected := result.RowsAffected()
|
||||||
|
|
||||||
|
if rowsAffected == 0 {
|
||||||
|
return ErrNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *UserRepository) Delete(ctx context.Context, id uuid.UUID) error {
|
||||||
|
result, err := r.db.Exec(ctx, "DELETE FROM users WHERE id = $1;", id)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
r.logger.ErrorContext(ctx, err.Error())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
rowsAffected := result.RowsAffected()
|
||||||
|
|
||||||
|
if rowsAffected == 0 {
|
||||||
|
return ErrNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
84
internal/user/repository/repository_test.go
Normal file
84
internal/user/repository/repository_test.go
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
package repository_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"log/slog"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"gitea.michaelthomson.dev/mthomson/habits/internal/test"
|
||||||
|
"gitea.michaelthomson.dev/mthomson/habits/internal/user/repository"
|
||||||
|
"github.com/gofrs/uuid/v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCRUD(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
logger := slog.Default()
|
||||||
|
tdb := test.NewTestDatabase(t)
|
||||||
|
defer tdb.TearDown()
|
||||||
|
r := repository.NewUserRepository(logger, tdb.Db)
|
||||||
|
uuid := NewUUID(t)
|
||||||
|
|
||||||
|
t.Run("creates new user", func(t *testing.T) {
|
||||||
|
newUser := repository.UserRow{Id: uuid, Email: "test@test.com", HashedPassword: "supersecurehash"}
|
||||||
|
_, err := r.Create(ctx, newUser)
|
||||||
|
AssertNoError(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("gets user", func(t *testing.T) {
|
||||||
|
want := repository.UserRow{Id: uuid, Email: "test@test.com", HashedPassword: "supersecurehash"}
|
||||||
|
got, err := r.GetById(ctx, uuid)
|
||||||
|
AssertNoError(t, err)
|
||||||
|
AssertUserRows(t, got, want)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("updates user", func(t *testing.T) {
|
||||||
|
want := repository.UserRow{Id: uuid, Email: "new@test.com", HashedPassword: "supersecurehash"}
|
||||||
|
err := r.Update(ctx, want)
|
||||||
|
AssertNoError(t, err)
|
||||||
|
|
||||||
|
got, err := r.GetById(ctx, uuid)
|
||||||
|
AssertNoError(t, err)
|
||||||
|
AssertUserRows(t, got, want)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("deletes user", func(t *testing.T) {
|
||||||
|
err := r.Delete(ctx, uuid)
|
||||||
|
AssertNoError(t, err)
|
||||||
|
|
||||||
|
want := repository.ErrNotFound
|
||||||
|
_, got := r.GetById(ctx, uuid)
|
||||||
|
|
||||||
|
AssertErrors(t, got, want)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func AssertErrors(t testing.TB, got, want error) {
|
||||||
|
t.Helper()
|
||||||
|
if !errors.Is(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 AssertUserRows(t testing.TB, got, want repository.UserRow) {
|
||||||
|
t.Helper()
|
||||||
|
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