Skip to content

Commit

Permalink
remove database abstraction and mocks
Browse files Browse the repository at this point in the history
also other fixes found along the way:
- wait for database when starting test
- run migrations before starting test container
- don't allow duplicate user email addresses
  • Loading branch information
briskt committed Nov 26, 2023
1 parent 87fb4ee commit 291e98d
Show file tree
Hide file tree
Showing 25 changed files with 213 additions and 655 deletions.
9 changes: 6 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ app: migrate
ui-app:
docker-compose up -d ui-app

test:
test: migratetestdb
docker-compose run --rm test

testcli:
testcli: migratetestdb
docker-compose run --rm test bash

bounce:
Expand All @@ -16,6 +16,9 @@ bounce:
migrate: db
docker-compose run --rm app bash -c "goose -dir migrations postgres postgres://keygo:keygo@db:5432/keygo?sslmode=disable up"

migratetestdb:
docker-compose run --rm test bash -c "goose -dir migrations postgres postgres://keygo:keygo@testdb:5432/keygo?sslmode=disable up"

migratedown: db
docker-compose run --rm app bash -c "goose -dir migrations postgres postgres://keygo:keygo@db:5432/keygo?sslmode=disable down"

Expand All @@ -39,4 +42,4 @@ install-js-deps:
proxy:
docker-compose up -d proxy

.PHONY: app ui-app test bounce migrate migratedown new-migration db fresh adminer install-js-deps proxy
.PHONY: app ui-app test bounce migrate migratetestdb migratedown new-migration db fresh adminer install-js-deps proxy
7 changes: 0 additions & 7 deletions app/app.go

This file was deleted.

23 changes: 0 additions & 23 deletions app/tenant.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,10 @@ package app

import (
"time"

"github.com/labstack/echo/v4"
)

const minTenantNameLength = 3

// TenantService is a service for managing tenants
type TenantService interface {
// FindTenantByID retrieves a tenant by ID
FindTenantByID(ctx echo.Context, id string) (Tenant, error)

// FindTenants retrieves a list of tenants by filter
FindTenants(ctx echo.Context, filter TenantFilter) ([]Tenant, int, error)

// CreateTenant creates a new tenant
CreateTenant(ctx echo.Context, input TenantCreateInput) (Tenant, error)

// UpdateTenant updates a tenant object
UpdateTenant(ctx echo.Context, id string, input TenantUpdateInput) (Tenant, error)

// DeleteTenant permanently deletes a tenant and all child objects
DeleteTenant(ctx echo.Context, id string) error

// CreateTenantUser creates a new tenant user
CreateTenantUser(ctx echo.Context, input TenantUserCreateInput) error
}

// Tenant is the full model that identifies an app Tenant
type Tenant struct {
ID string
Expand Down
22 changes: 0 additions & 22 deletions app/token.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,8 @@ package app

import (
"time"

"github.com/labstack/echo/v4"
)

// TokenService is a service for managing tokens
type TokenService interface {
// FindToken looks up a token object by raw, unhashed token, and returns the Token object
// with associated User
// Returns ERR_NOTFOUND if token does not exist
FindToken(ctx echo.Context, raw string) (Token, error)

// CreateToken creates a new token object
//
// On success, the token.ID is set to the new token ID
CreateToken(ctx echo.Context, input TokenCreateInput) (Token, error)

// DeleteToken permanently deletes a token object from the system by ID.
// The parent user object is not removed.
DeleteToken(ctx echo.Context, id string) error

// UpdateToken extends a token's ExpiresAt
UpdateToken(ctx echo.Context, id string, input TokenUpdateInput) error
}

type Token struct {
// TODO: remove private fields not appropriate for the API. (May require architecture changes.)
ID string
Expand Down
23 changes: 0 additions & 23 deletions app/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,10 @@ package app

import (
"time"

"github.com/labstack/echo/v4"
)

const UserRoleAdmin = "Admin"

// UserService is a service for managing users
type UserService interface {
// FindUserByID retrieves a user by ID
FindUserByID(ctx echo.Context, id string) (User, error)

// FindUsers retrieves a list of users by filter
FindUsers(ctx echo.Context, filter UserFilter) ([]User, int, error)

// CreateUser creates a new user
CreateUser(ctx echo.Context, input UserCreateInput) (User, error)

// UpdateUser updates a user object
UpdateUser(ctx echo.Context, id string, input UserUpdateInput) (User, error)

// DeleteUser permanently deletes a user and all child objects
DeleteUser(ctx echo.Context, id string) error

// TouchLastLoginAt sets the LastLoginAt field to the current time
TouchLastLoginAt(ctx echo.Context, id string) error
}

// UserFilter is a filter passed to FindUsers()
type UserFilter struct {
// Filtering fields.
Expand Down
8 changes: 1 addition & 7 deletions cmd/api/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (

"github.com/labstack/gommon/log"

"github.com/briskt/keygo/app"
"github.com/briskt/keygo/db"
"github.com/briskt/keygo/server"
)
Expand All @@ -14,12 +13,7 @@ func main() {
fmt.Println("starting API")

dbConnection := db.OpenDB()
services := app.DataServices{
TenantService: db.NewTenantService(),
TokenService: db.NewTokenService(),
UserService: db.NewUserService(),
}
e := server.New(server.WithDataBase(dbConnection), server.WithDataServices(services))
e := server.New(server.WithDataBase(dbConnection))

if l, ok := e.Logger.(*log.Logger); ok {
l.SetHeader("${time_rfc3339} ${level}")
Expand Down
3 changes: 0 additions & 3 deletions db/db_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ type TestSuite struct {
*require.Assertions
ctx echo.Context
DB *gorm.DB
app.DataServices
}

// SetupTest runs before every test function
Expand All @@ -33,8 +32,6 @@ func (ts *TestSuite) SetupTest() {
migrations.Fresh(sqlDB)
}
ts.Assertions = require.New(ts.T())
ts.TokenService = db.NewTokenService()
ts.UserService = db.NewUserService()
}

func Test_RunSuite(t *testing.T) {
Expand Down
69 changes: 21 additions & 48 deletions db/tenant.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,71 +22,44 @@ func (u *Tenant) BeforeCreate(_ *gorm.DB) error {
return nil
}

// Ensure service implements interface.
var _ app.TenantService = (*TenantService)(nil)

// TenantService is a service for managing tenants.
type TenantService struct{}

// NewTenantService returns a new instance of TenantService.
func NewTenantService() *TenantService {
return &TenantService{}
}

// FindTenantByID retrieves a tenant by ID along with their associated auth objects.
func (s *TenantService) FindTenantByID(ctx echo.Context, id string) (app.Tenant, error) {
tenant, err := findTenantByID(ctx, id)
if err != nil {
return app.Tenant{}, err
}
return convertTenant(ctx, tenant)
}

// FindTenants retrieves a list of tenants by filter. Also returns total count of
// matching tenants which may differ from returned results if filter.Limit is specified.
func (s *TenantService) FindTenants(ctx echo.Context, _ app.TenantFilter) ([]app.Tenant, int, error) {
func FindTenants(ctx echo.Context, _ app.TenantFilter) ([]Tenant, error) {
var tenants []Tenant
result := Tx(ctx).Find(&tenants)
if result.Error != nil {
return []app.Tenant{}, 0, result.Error
}
appTenants := make([]app.Tenant, len(tenants))
for i := range tenants {
tenant, err := convertTenant(ctx, tenants[i])
if err != nil {
return nil, 0, err
}
appTenants[i] = tenant
return []Tenant{}, result.Error
}
return appTenants, len(tenants), nil

return tenants, nil
}

// CreateTenant creates a new tenant.
func (s *TenantService) CreateTenant(ctx echo.Context, input app.TenantCreateInput) (app.Tenant, error) {
func CreateTenant(ctx echo.Context, input app.TenantCreateInput) (Tenant, error) {
if err := input.Validate(); err != nil {
return app.Tenant{}, err
return Tenant{}, err
}

newTenant := Tenant{
Name: input.Name,
}
err := Tx(ctx).Create(&newTenant).Error
if err != nil {
return app.Tenant{}, err
return Tenant{}, err
}

return convertTenant(ctx, newTenant)
return newTenant, nil
}

// UpdateTenant updates a tenant object.
func (s *TenantService) UpdateTenant(ctx echo.Context, id string, input app.TenantUpdateInput) (app.Tenant, error) {
func UpdateTenant(ctx echo.Context, id string, input app.TenantUpdateInput) (Tenant, error) {
if err := input.Validate(); err != nil {
return app.Tenant{}, err
return Tenant{}, err
}

tenant, err := findTenantByID(ctx, id)
tenant, err := FindTenantByID(ctx, id)
if err != nil {
return app.Tenant{}, err
return Tenant{}, err
}

if input.Name != nil {
Expand All @@ -95,14 +68,14 @@ func (s *TenantService) UpdateTenant(ctx echo.Context, id string, input app.Tena

result := Tx(ctx).Save(&tenant)
if err != nil {
return app.Tenant{}, result.Error
return Tenant{}, result.Error
}

return convertTenant(ctx, tenant)
return tenant, nil
}

// DeleteTenant permanently deletes a tenant and all child objects
func (s *TenantService) DeleteTenant(ctx echo.Context, id string) error {
func DeleteTenant(ctx echo.Context, id string) error {
result := Tx(ctx).Where("id = ?", id).Delete(&Tenant{})
if err := result.Error; err != nil {
return err
Expand All @@ -111,29 +84,29 @@ func (s *TenantService) DeleteTenant(ctx echo.Context, id string) error {
}

// CreateTenantUser permanently deletes a tenant and all child objects
func (s *TenantService) CreateTenantUser(ctx echo.Context, input app.TenantUserCreateInput) error {
func CreateTenantUser(ctx echo.Context, input app.TenantUserCreateInput) error {
return nil
}

// findTenantByID is a helper function to fetch a tenant by ID.
func findTenantByID(ctx echo.Context, id string) (Tenant, error) {
// FindTenantByID is a function to fetch a tenant by ID.
func FindTenantByID(ctx echo.Context, id string) (Tenant, error) {
var tenant Tenant
result := Tx(ctx).First(&tenant, "id = ?", id)
return tenant, result.Error
}

func convertTenant(c echo.Context, t Tenant) (app.Tenant, error) {
func ConvertTenant(c echo.Context, t Tenant) (app.Tenant, error) {
tenant := app.Tenant{
ID: t.ID,
Name: t.Name,
CreatedAt: t.CreatedAt,
UpdatedAt: t.UpdatedAt,
}
users, n, err := findUsers(c, app.UserFilter{TenantID: &t.ID})
users, err := FindUsers(c, app.UserFilter{TenantID: &t.ID})
if err != nil {
return app.Tenant{}, err
}
tenant.UserIDs = make([]string, n)
tenant.UserIDs = make([]string, len(users))
for i, user := range users {
tenant.UserIDs[i] = user.ID
}
Expand Down
37 changes: 14 additions & 23 deletions db/token.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,9 @@ func (t *Token) BeforeCreate(_ *gorm.DB) error {
// create a new token object in the database. On success, the ID is set to the new database
// ID & timestamp fields are set to the current time
func (t *Token) create(ctx echo.Context) error {
t.PlainText = randomString()
if t.PlainText == "" {
t.PlainText = randomString()
}
t.Hash = hashToken(t.PlainText)

err := Tx(ctx).Omit("User").Create(t).Error
Expand Down Expand Up @@ -120,29 +122,18 @@ func (t *Token) loadUser(ctx echo.Context) (err error) {
return nil
}

// Ensure service implements interface.
var _ app.TokenService = (*TokenService)(nil)

// TokenService is a service for managing API auth tokens
type TokenService struct{}

// NewTokenService returns a new instance of TokenService
func NewTokenService() *TokenService {
return &TokenService{}
}

func (t TokenService) FindToken(ctx echo.Context, raw string) (app.Token, error) {
func FindToken(ctx echo.Context, raw string) (Token, error) {
token, err := findToken(ctx, raw)
if err != nil {
return app.Token{}, err
return Token{}, err
}
return convertToken(ctx, token)
return token, nil
}

// CreateToken creates a new token object.
func (t TokenService) CreateToken(ctx echo.Context, input app.TokenCreateInput) (app.Token, error) {
func CreateToken(ctx echo.Context, input app.TokenCreateInput) (Token, error) {
if err := input.Validate(); err != nil {
return app.Token{}, err
return Token{}, err
}

token := Token{
Expand All @@ -153,17 +144,17 @@ func (t TokenService) CreateToken(ctx echo.Context, input app.TokenCreateInput)

err := token.create(ctx)
if err != nil {
return app.Token{}, err
return Token{}, err
}

return convertToken(ctx, token)
return token, nil
}

func (t TokenService) DeleteToken(ctx echo.Context, id string) error {
func DeleteToken(ctx echo.Context, id string) error {
return deleteToken(ctx, id)
}

func (t TokenService) UpdateToken(ctx echo.Context, id string, input app.TokenUpdateInput) error {
func UpdateToken(ctx echo.Context, id string, input app.TokenUpdateInput) error {
if err := input.Validate(); err != nil {
return err
}
Expand All @@ -176,12 +167,12 @@ func (t TokenService) UpdateToken(ctx echo.Context, id string, input app.TokenUp
return err
}

func convertToken(ctx echo.Context, token Token) (app.Token, error) {
func ConvertToken(ctx echo.Context, token Token) (app.Token, error) {
if err := token.loadUser(ctx); err != nil {
return app.Token{}, err
}

user, err := convertUser(ctx, token.User)
user, err := ConvertUser(ctx, token.User)
if err != nil {
return app.Token{}, err
}
Expand Down
Loading

0 comments on commit 291e98d

Please sign in to comment.