migrate orion repo into monorepo structure
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
package human
|
||||
|
||||
import "time"
|
||||
|
||||
type Human struct {
|
||||
ID string
|
||||
Email string
|
||||
EmailPrefix string
|
||||
CreatedAt time.Time
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
package human
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
"go.jetify.com/typeid"
|
||||
)
|
||||
|
||||
var errNotFound = errors.New("not found")
|
||||
|
||||
type humanIDPrefix struct{}
|
||||
|
||||
func (humanIDPrefix) Prefix() string { return "human" }
|
||||
|
||||
type humanID struct {
|
||||
typeid.TypeID[humanIDPrefix]
|
||||
}
|
||||
|
||||
func newHumanID() (humanID, error) {
|
||||
return typeid.New[humanID]()
|
||||
}
|
||||
|
||||
func emailPrefix(email string) string {
|
||||
return strings.Split(email, "@")[0]
|
||||
}
|
||||
|
||||
type repository interface {
|
||||
getByEmail(ctx context.Context, email string) (*Human, error)
|
||||
getByID(ctx context.Context, id string) (*Human, error)
|
||||
create(ctx context.Context, email string) (*Human, error)
|
||||
exists(ctx context.Context, email string) (bool, error)
|
||||
}
|
||||
|
||||
type repositoryImpl struct {
|
||||
pool *pgxpool.Pool
|
||||
}
|
||||
|
||||
func newRepository(pool *pgxpool.Pool) repository {
|
||||
return &repositoryImpl{pool: pool}
|
||||
}
|
||||
|
||||
func (r *repositoryImpl) getByEmail(ctx context.Context, email string) (*Human, error) {
|
||||
var h Human
|
||||
err := r.pool.QueryRow(ctx,
|
||||
`SELECT id, email, created_at FROM humans WHERE email = $1`,
|
||||
email,
|
||||
).Scan(&h.ID, &h.Email, &h.CreatedAt)
|
||||
if err != nil {
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return nil, errNotFound
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
h.EmailPrefix = emailPrefix(h.Email)
|
||||
return &h, nil
|
||||
}
|
||||
|
||||
func (r *repositoryImpl) getByID(ctx context.Context, id string) (*Human, error) {
|
||||
var h Human
|
||||
err := r.pool.QueryRow(ctx,
|
||||
`SELECT id, email, created_at FROM humans WHERE id = $1`,
|
||||
id,
|
||||
).Scan(&h.ID, &h.Email, &h.CreatedAt)
|
||||
if err != nil {
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return nil, errNotFound
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
h.EmailPrefix = emailPrefix(h.Email)
|
||||
return &h, nil
|
||||
}
|
||||
|
||||
func (r *repositoryImpl) create(ctx context.Context, email string) (*Human, error) {
|
||||
id, err := newHumanID()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var h Human
|
||||
err = r.pool.QueryRow(ctx,
|
||||
`INSERT INTO humans (id, email) VALUES ($1, $2)
|
||||
RETURNING id, email, created_at`,
|
||||
id.String(), email,
|
||||
).Scan(&h.ID, &h.Email, &h.CreatedAt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
h.EmailPrefix = emailPrefix(h.Email)
|
||||
return &h, nil
|
||||
}
|
||||
|
||||
func (r *repositoryImpl) exists(ctx context.Context, email string) (bool, error) {
|
||||
var exists bool
|
||||
err := r.pool.QueryRow(ctx,
|
||||
`SELECT EXISTS(SELECT 1 FROM humans WHERE email = $1)`,
|
||||
email,
|
||||
).Scan(&exists)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return exists, nil
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package human
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/flowy-live/llink/internal/utils"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
)
|
||||
|
||||
var ErrNotFound = errors.New("human not found")
|
||||
|
||||
type Service interface {
|
||||
GetOrCreateByEmail(ctx context.Context, email string) (*Human, error)
|
||||
// GetByEmail returns ErrNotFound if no human found
|
||||
GetByEmail(ctx context.Context, email string) (*Human, error)
|
||||
}
|
||||
|
||||
type serviceImpl struct {
|
||||
repo repository
|
||||
}
|
||||
|
||||
func NewService(pool *pgxpool.Pool) Service {
|
||||
return &serviceImpl{repo: newRepository(pool)}
|
||||
}
|
||||
|
||||
func (s *serviceImpl) GetOrCreateByEmail(ctx context.Context, email string) (*Human, error) {
|
||||
email, err := utils.NormalizeEmail(email)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
h, err := s.repo.getByEmail(ctx, email)
|
||||
if err != nil {
|
||||
if errors.Is(err, errNotFound) {
|
||||
return s.repo.create(ctx, email)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return h, nil
|
||||
}
|
||||
|
||||
func (s *serviceImpl) GetByEmail(ctx context.Context, email string) (*Human, error) {
|
||||
email, err := utils.NormalizeEmail(email)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
h, err := s.repo.getByEmail(ctx, email)
|
||||
if errors.Is(err, errNotFound) {
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
return h, err
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package human_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/flowy-live/llink/internal/human"
|
||||
"github.com/flowy-live/llink/internal/testhelper"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var dbPool *pgxpool.Pool
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
dbPool = testhelper.SetupTestDB()
|
||||
defer testhelper.TeardownTestDB()
|
||||
|
||||
ret := m.Run()
|
||||
os.Exit(ret)
|
||||
}
|
||||
|
||||
func TestHumanService(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
svc := human.NewService(dbPool)
|
||||
|
||||
// Test GetByEmail with non-existent email
|
||||
_, err := svc.GetByEmail(ctx, "[email protected]")
|
||||
assert.Error(t, err)
|
||||
assert.ErrorIs(t, err, human.ErrNotFound)
|
||||
|
||||
// Test GetOrCreateByEmail creates new human
|
||||
createdHuman, err := svc.GetOrCreateByEmail(ctx, "[email protected]")
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, createdHuman.ID)
|
||||
assert.Equal(t, "[email protected]", createdHuman.Email)
|
||||
assert.Equal(t, "newuser", createdHuman.EmailPrefix)
|
||||
assert.NotZero(t, createdHuman.CreatedAt)
|
||||
|
||||
// Test GetOrCreateByEmail returns existing human
|
||||
existingHuman, err := svc.GetOrCreateByEmail(ctx, "[email protected]")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, createdHuman.ID, existingHuman.ID)
|
||||
assert.Equal(t, createdHuman.Email, existingHuman.Email)
|
||||
|
||||
// Test GetByEmail with existing email
|
||||
foundHuman, err := svc.GetByEmail(ctx, "[email protected]")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, createdHuman.ID, foundHuman.ID)
|
||||
assert.Equal(t, createdHuman.Email, foundHuman.Email)
|
||||
|
||||
// Test with another email
|
||||
anotherHuman, err := svc.GetOrCreateByEmail(ctx, "[email protected]")
|
||||
assert.NoError(t, err)
|
||||
assert.NotEqual(t, createdHuman.ID, anotherHuman.ID)
|
||||
assert.Equal(t, "[email protected]", anotherHuman.Email)
|
||||
assert.Equal(t, "another", anotherHuman.EmailPrefix)
|
||||
}
|
||||
Reference in New Issue
Block a user