feat: add a new REST endpoint - GET /api/v1/user

This commit is contained in:
Dawid Wysokiński 2022-11-20 09:57:21 +01:00
parent ffcb965590
commit 5d07fba5b7
Signed by: Kichiyaki
GPG Key ID: B5445E357FB8B892
8 changed files with 154 additions and 19 deletions

View File

@ -2,6 +2,7 @@ package bundb
import (
"context"
"database/sql"
"errors"
"fmt"
@ -36,6 +37,26 @@ func (a *APIKey) Create(ctx context.Context, params domain.CreateAPIKeyParams) (
return apiKey.ToDomain(), nil
}
func (a *APIKey) Get(ctx context.Context, key string) (domain.APIKey, error) {
var ak model.APIKey
if err := a.db.NewSelect().
Model(&ak).
Where("key = ?", key).
Scan(ctx); err != nil {
if errors.Is(err, sql.ErrNoRows) {
return domain.APIKey{}, domain.APIKeyNotFoundError{Key: key}
}
return domain.APIKey{}, fmt.Errorf(
"something went wrong while selecting api key (key=%s) from the db: %w",
key,
err,
)
}
return ak.ToDomain(), nil
}
func mapCreateAPIKeyError(err error, params domain.CreateAPIKeyParams) error {
var pgError pgdriver.Error
if !errors.As(err, &pgError) {

View File

@ -33,7 +33,6 @@ func TestAPIKey_Create(t *testing.T) {
assert.Equal(t, params.Key(), apiKey.Key)
assert.Equal(t, params.UserID(), apiKey.UserID)
assert.WithinDuration(t, time.Now(), apiKey.CreatedAt, time.Second)
assert.Zero(t, apiKey.LastUsedAt)
})
t.Run("ERR: player doesn't exist", func(t *testing.T) {
@ -66,3 +65,30 @@ func TestAPIKey_Create(t *testing.T) {
assert.Zero(t, apiKey)
})
}
func TestAPIKey_Get(t *testing.T) {
t.Parallel()
db := newDB(t)
fixture := loadFixtures(t, db)
repo := bundb.NewAPIKey(db)
apiKeyFromFixture := fixture.apiKey(t, "user-1-api-key-1")
t.Run("OK", func(t *testing.T) {
t.Parallel()
ak, err := repo.Get(context.Background(), apiKeyFromFixture.Key)
assert.NoError(t, err)
assert.Equal(t, apiKeyFromFixture, ak)
})
t.Run("ERR: API key not found", func(t *testing.T) {
t.Parallel()
ak, err := repo.Get(context.Background(), apiKeyFromFixture.Key+"1")
assert.ErrorIs(t, err, domain.APIKeyNotFoundError{
Key: apiKeyFromFixture.Key + "1",
})
assert.Zero(t, ak)
})
}

View File

@ -10,11 +10,10 @@ import (
type APIKey struct {
bun.BaseModel `bun:"base_model,table:api_keys,alias:ak"`
ID int64 `bun:"id,pk,autoincrement,identity"`
Key string `bun:"key,nullzero,type:varchar(255),notnull,unique"`
UserID int64 `bun:"user_id,nullzero,notnull"`
CreatedAt time.Time `bun:"created_at,nullzero,notnull,default:current_timestamp"`
LastUsedAt time.Time `bun:"last_used_at,nullzero"`
ID int64 `bun:"id,pk,autoincrement,identity"`
Key string `bun:"key,nullzero,type:varchar(255),notnull,unique"`
UserID int64 `bun:"user_id,nullzero,notnull"`
CreatedAt time.Time `bun:"created_at,nullzero,notnull,default:current_timestamp"`
}
func NewAPIKey(p domain.CreateAPIKeyParams) APIKey {
@ -27,10 +26,9 @@ func NewAPIKey(p domain.CreateAPIKeyParams) APIKey {
func (a *APIKey) ToDomain() domain.APIKey {
return domain.APIKey{
ID: a.ID,
Key: a.Key,
UserID: a.UserID,
CreatedAt: a.CreatedAt,
LastUsedAt: a.LastUsedAt,
ID: a.ID,
Key: a.Key,
UserID: a.UserID,
CreatedAt: a.CreatedAt,
}
}

View File

@ -14,18 +14,15 @@ func TestAPIKey(t *testing.T) {
t.Parallel()
var id int64 = 123
lastUsedAt := time.Now().Add(-5 * time.Minute)
params, err := domain.NewCreateAPIKeyParams(uuid.NewString(), 1)
assert.NoError(t, err)
result := model.NewAPIKey(params)
result.ID = id
result.LastUsedAt = lastUsedAt
apiKey := result.ToDomain()
assert.Equal(t, id, apiKey.ID)
assert.Equal(t, params.Key(), apiKey.Key)
assert.Equal(t, params.UserID(), apiKey.UserID)
assert.WithinDuration(t, time.Now(), apiKey.CreatedAt, 10*time.Millisecond)
assert.Equal(t, lastUsedAt, apiKey.LastUsedAt)
}

View File

@ -1,6 +1,7 @@
package domain
import (
"fmt"
"time"
)
@ -9,11 +10,10 @@ const (
)
type APIKey struct {
ID int64
Key string
UserID int64
CreatedAt time.Time
LastUsedAt time.Time
ID int64
Key string
UserID int64
CreatedAt time.Time
}
type CreateAPIKeyParams struct {
@ -48,3 +48,19 @@ func (c CreateAPIKeyParams) Key() string {
func (c CreateAPIKeyParams) UserID() int64 {
return c.userID
}
type APIKeyNotFoundError struct {
Key string
}
func (e APIKeyNotFoundError) Error() string {
return fmt.Sprintf("API key (key=%s) not found", e.Key)
}
func (e APIKeyNotFoundError) UserError() string {
return e.Error()
}
func (e APIKeyNotFoundError) Code() ErrorCode {
return ErrorCodeEntityNotFound
}

View File

@ -1,6 +1,7 @@
package domain_test
import (
"fmt"
"testing"
"gitea.dwysokinski.me/twhelp/sessions/internal/domain"
@ -62,3 +63,15 @@ func TestNewCreateAPIKeyParams(t *testing.T) {
})
}
}
func TestAPIKeyNotFoundError(t *testing.T) {
t.Parallel()
err := domain.APIKeyNotFoundError{
Key: uuid.NewString(),
}
var _ domain.Error = err
assert.Equal(t, fmt.Sprintf("API key (key=%s) not found", err.Key), err.Error())
assert.Equal(t, err.Error(), err.UserError())
assert.Equal(t, domain.ErrorCodeEntityNotFound, err.Code())
}

View File

@ -12,6 +12,7 @@ import (
//counterfeiter:generate -o internal/mock/api_key_repository.gen.go . APIKeyRepository
type APIKeyRepository interface {
Create(ctx context.Context, params domain.CreateAPIKeyParams) (domain.APIKey, error)
Get(ctx context.Context, key string) (domain.APIKey, error)
}
//counterfeiter:generate -o internal/mock/user_getter.gen.go . UserGetter
@ -54,3 +55,24 @@ func (a *APIKey) Create(ctx context.Context, userID int64) (domain.APIKey, error
return apiKey, nil
}
func (a *APIKey) Verify(ctx context.Context, keyStr string) (domain.User, error) {
key, err := uuid.Parse(keyStr)
if err != nil {
return domain.User{}, domain.APIKeyNotFoundError{
Key: keyStr,
}
}
ak, err := a.repo.Get(ctx, key.String())
if err != nil {
return domain.User{}, fmt.Errorf("APIKeyRepository.Get: %w", err)
}
user, err := a.userSvc.Get(ctx, ak.UserID)
if err != nil {
return domain.User{}, fmt.Errorf("UserService.Get: %w", err)
}
return user, nil
}

View File

@ -53,3 +53,45 @@ func TestAPIKey_Create(t *testing.T) {
assert.Zero(t, ak)
})
}
func TestAPIKey_Verify(t *testing.T) {
t.Parallel()
t.Run("OK", func(t *testing.T) {
t.Parallel()
apiKeyRepo := &mock.FakeAPIKeyRepository{}
apiKeyRepo.GetCalls(func(ctx context.Context, key string) (domain.APIKey, error) {
return domain.APIKey{
ID: 1,
Key: key,
UserID: 125,
CreatedAt: time.Now(),
}, nil
})
userSvc := &mock.FakeUserGetter{}
userSvc.GetCalls(func(ctx context.Context, id int64) (domain.User, error) {
return domain.User{
ID: id,
Name: uuid.NewString(),
CreatedAt: time.Now(),
}, nil
})
user, err := service.NewAPIKey(apiKeyRepo, userSvc).Verify(context.Background(), uuid.NewString())
assert.NoError(t, err)
assert.Greater(t, user.ID, int64(0))
})
t.Run("ERR: invalid uuid", func(t *testing.T) {
t.Parallel()
key := "abcd"
user, err := service.NewAPIKey(nil, nil).Verify(context.Background(), key)
assert.ErrorIs(t, err, domain.APIKeyNotFoundError{
Key: key,
})
assert.Zero(t, user)
})
}