V3-664 Extend User repository. Reimplemented with dbx (#591)

* V3-664 Extend User repository. Reimplemented with dbx

* structure updated

* redundant packages removed, structure simplified

* fixing goimports

* removed constuctor from user struct

* separated types declarations from database file

* test file renamed

* fixes according to review

* fixing goimports

* fixing goimports
This commit is contained in:
Yehor Butko 2018-11-08 16:19:42 +02:00 committed by GitHub
parent 07ed38c930
commit 54cd9491c6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 1563 additions and 1 deletions

2
go.mod
View File

@ -69,7 +69,7 @@ require (
github.com/rcrowley/go-metrics v0.0.0-20180503174638-e2704e165165 // indirect
github.com/rs/cors v1.5.0 // indirect
github.com/shirou/gopsutil v2.17.12+incompatible
github.com/skyrings/skyring-common v0.0.0-20160929130248-d1c0bb1cbd5e // indirect
github.com/skyrings/skyring-common v0.0.0-20160929130248-d1c0bb1cbd5e
github.com/spacemonkeygo/errors v0.0.0-20171212215202-9064522e9fd1 // indirect
github.com/spf13/cobra v0.0.3
github.com/spf13/pflag v1.0.3

15
pkg/satellite/database.go Normal file
View File

@ -0,0 +1,15 @@
// Copyright (C) 2018 Storj Labs, Inc.
// See LICENSE for copying information.
package satellite
// DB contains access to different satellite databases
type DB interface {
// Users is getter for Users repository
Users() Users
// CreateTables is a method for creating all tables for satellitedb
CreateTables() error
// Close is used to close db connection
Close() error
}

View File

@ -0,0 +1,47 @@
// Copyright (C) 2018 Storj Labs, Inc.
// See LICENSE for copying information.
package satellitedb
import (
"storj.io/storj/pkg/satellite"
"storj.io/storj/pkg/satellite/satellitedb/dbx"
)
// Database contains access to different satellite databases
type Database struct {
db *dbx.DB
}
// New - constructor for DB
func New(driver, source string) (satellite.DB, error) {
db, err := dbx.Open(driver, source)
if err != nil {
return nil, err
}
database := &Database{
db: db,
}
return database, nil
}
// Users is getter for Users repository
func (db *Database) Users() satellite.Users {
return &users{db.db}
}
// CreateTables is a method for creating all tables for satellitedb
func (db *Database) CreateTables() error {
_, err := db.db.Exec(db.db.Schema())
return err
}
// Close is used to close db connection
func (db *Database) Close() error {
return db.db.Close()
}

View File

View File

@ -0,0 +1,35 @@
// dbx.v1 golang satellitedb.dbx .
model user (
key id
unique email
field id text
field first_name text ( updatable )
field last_name text ( updatable )
field email text ( updatable )
field password_hash blob ( updatable )
field created_at timestamp ( autoinsert )
)
read one (
select user
where user.email = ?
where user.password_hash = ?
)
read one (
select user
where user.id = ?
)
create user ( )
update user ( where user.id = ? )
delete user ( where user.id = ? )
//TODO: this entity will be used and updated in the next commit
model company (
key id
field id blob
field userId user.id cascade ( updatable )
)

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,17 @@
-- AUTOGENERATED BY gopkg.in/spacemonkeygo/dbx.v1
-- DO NOT EDIT
CREATE TABLE users (
id TEXT NOT NULL,
first_name TEXT NOT NULL,
last_name TEXT NOT NULL,
email TEXT NOT NULL,
password_hash BLOB NOT NULL,
created_at TIMESTAMP NOT NULL,
PRIMARY KEY ( id ),
UNIQUE ( email )
);
CREATE TABLE companies (
id BLOB NOT NULL,
userId TEXT NOT NULL REFERENCES users( id ) ON DELETE CASCADE,
PRIMARY KEY ( id )
);

View File

@ -0,0 +1,7 @@
// Copyright (C) 2018 Storj Labs, Inc.
// See LICENSE for copying information.
package dbx
//go:generate dbx.v1 golang -d sqlite3 satellitedb.dbx .
//go:generate dbx.v1 schema -d sqlite3 satellitedb.dbx .

View File

@ -0,0 +1,107 @@
// Copyright (C) 2018 Storj Labs, Inc.
// See LICENSE for copying information.
package satellitedb
import (
"context"
"github.com/zeebo/errs"
"storj.io/storj/pkg/satellite"
"github.com/skyrings/skyring-common/tools/uuid"
"storj.io/storj/pkg/satellite/satellitedb/dbx"
)
// implementation of User interface repository using spacemonkeygo/dbx orm
type users struct {
db *dbx.DB
}
// Get is a method for querying user from the database by id
func (users *users) Get(ctx context.Context, id uuid.UUID) (*satellite.User, error) {
userID := dbx.User_Id(id.String())
user, err := users.db.Get_User_By_Id(ctx, userID)
if err != nil {
return nil, err
}
return userFromDBX(user)
}
// GetByCredentials is a method for querying user by credentials from the database.
func (users *users) GetByCredentials(ctx context.Context, password []byte, email string) (*satellite.User, error) {
userEmail := dbx.User_Email(email)
userPassword := dbx.User_PasswordHash(password)
user, err := users.db.Get_User_By_Email_And_PasswordHash(ctx, userEmail, userPassword)
if err != nil {
return nil, err
}
return userFromDBX(user)
}
// Insert is a method for inserting user into the database
func (users *users) Insert(ctx context.Context, user *satellite.User) error {
_, err := users.db.Create_User(ctx,
dbx.User_Id(user.ID.String()),
dbx.User_FirstName(user.FirstName),
dbx.User_LastName(user.LastName),
dbx.User_Email(user.Email),
dbx.User_PasswordHash(user.PasswordHash))
return err
}
// Delete is a method for deleting user by Id from the database.
func (users *users) Delete(ctx context.Context, id uuid.UUID) error {
_, err := users.db.Delete_User_By_Id(ctx, dbx.User_Id(id.String()))
return err
}
// Update is a method for updating user entity
func (users *users) Update(ctx context.Context, user *satellite.User) error {
_, err := users.db.Update_User_By_Id(ctx,
dbx.User_Id(user.ID.String()),
dbx.User_Update_Fields{
FirstName: dbx.User_FirstName(user.FirstName),
LastName: dbx.User_LastName(user.LastName),
Email: dbx.User_Email(user.Email),
PasswordHash: dbx.User_PasswordHash(user.PasswordHash),
})
return err
}
// userFromDBX is used for creating User entity from autogenerated dbx.User struct
// TODO: move error strings to better place
func userFromDBX(user *dbx.User) (*satellite.User, error) {
if user == nil {
return nil, errs.New("user parameter is nil")
}
id, err := uuid.Parse(user.Id)
if err != nil {
return nil, errs.New("Id in not valid UUID string")
}
u := &satellite.User{}
u.ID = *id
u.FirstName = user.FirstName
u.LastName = user.LastName
u.Email = user.Email
u.PasswordHash = user.PasswordHash
u.CreatedAt = user.CreatedAt
return u, nil
}

View File

@ -0,0 +1,199 @@
// Copyright (C) 2018 Storj Labs, Inc.
// See LICENSE for copying information.
package satellitedb
import (
"testing"
"time"
"storj.io/storj/internal/testcontext"
"storj.io/storj/pkg/satellite/satellitedb/dbx"
"storj.io/storj/pkg/satellite"
"github.com/skyrings/skyring-common/tools/uuid"
"github.com/stretchr/testify/assert"
)
func TestRepository(t *testing.T) {
//testing constants
const (
lastName = "lastName"
email = "email@ukr.net"
passValid = "123456"
name = "name"
newName = "newName"
newLastName = "newLastName"
newEmail = "newEmail@ukr.net"
newPass = "newPass"
)
ctx := testcontext.New(t)
defer ctx.Cleanup()
// to test with real db3 file use this connection string - "../db/accountdb.db3"
db, err := New("sqlite3", "file::memory:?mode=memory&cache=shared")
if err != nil {
assert.NoError(t, err)
}
defer ctx.Check(db.Close)
err = db.CreateTables()
if err != nil {
assert.NoError(t, err)
}
repository := db.Users()
t.Run("User insertion success", func(t *testing.T) {
id, err := uuid.New()
if err != nil {
assert.NoError(t, err)
}
user := &satellite.User{
ID: *id,
FirstName: name,
LastName: lastName,
Email: email,
PasswordHash: []byte(passValid),
CreatedAt: time.Now(),
}
err = repository.Insert(ctx, user)
assert.Nil(t, err)
assert.NoError(t, err)
})
t.Run("Can't insert user with same email twice", func(t *testing.T) {
id, err := uuid.New()
if err != nil {
assert.NoError(t, err)
}
user := &satellite.User{
ID: *id,
FirstName: name,
LastName: lastName,
Email: email,
PasswordHash: []byte(passValid),
CreatedAt: time.Now(),
}
err = repository.Insert(ctx, user)
assert.NotNil(t, err)
assert.Error(t, err)
})
t.Run("Get user success", func(t *testing.T) {
userByCreds, err := repository.GetByCredentials(ctx, []byte(passValid), email)
assert.Equal(t, userByCreds.FirstName, name)
assert.Equal(t, userByCreds.LastName, lastName)
assert.Nil(t, err)
assert.NoError(t, err)
userByID, err := repository.GetByCredentials(ctx, []byte(passValid), email)
assert.Equal(t, userByID.FirstName, name)
assert.Equal(t, userByID.LastName, lastName)
assert.Nil(t, err)
assert.NoError(t, err)
assert.Equal(t, userByID.ID, userByCreds.ID)
assert.Equal(t, userByID.FirstName, userByCreds.FirstName)
assert.Equal(t, userByID.LastName, userByCreds.LastName)
assert.Equal(t, userByID.Email, userByCreds.Email)
assert.Equal(t, userByID.PasswordHash, userByCreds.PasswordHash)
assert.Equal(t, userByID.CreatedAt, userByCreds.CreatedAt)
})
t.Run("Update user success", func(t *testing.T) {
oldUser, err := repository.GetByCredentials(ctx, []byte(passValid), email)
if err != nil {
assert.NoError(t, err)
}
newUser := &satellite.User{
ID: oldUser.ID,
FirstName: newName,
LastName: newLastName,
Email: newEmail,
PasswordHash: []byte(newPass),
CreatedAt: oldUser.CreatedAt,
}
err = repository.Update(ctx, newUser)
assert.Nil(t, err)
assert.NoError(t, err)
newUser, err = repository.Get(ctx, oldUser.ID)
if err != nil {
assert.NoError(t, err)
}
assert.Equal(t, newUser.ID, oldUser.ID)
assert.Equal(t, newUser.FirstName, newName)
assert.Equal(t, newUser.LastName, newLastName)
assert.Equal(t, newUser.Email, newEmail)
assert.Equal(t, newUser.PasswordHash, []byte(newPass))
assert.Equal(t, newUser.CreatedAt, oldUser.CreatedAt)
})
t.Run("Delete user success", func(t *testing.T) {
oldUser, err := repository.GetByCredentials(ctx, []byte(newPass), newEmail)
if err != nil {
assert.NoError(t, err)
}
err = repository.Delete(ctx, oldUser.ID)
assert.Nil(t, err)
assert.NoError(t, err)
_, err = repository.Get(ctx, oldUser.ID)
assert.NotNil(t, err)
assert.Error(t, err)
})
}
func TestUserDboFromDbx(t *testing.T) {
t.Run("can't create dbo from nil dbx model", func(t *testing.T) {
user, err := userFromDBX(nil)
assert.Nil(t, user)
assert.NotNil(t, err)
assert.Error(t, err)
})
t.Run("can't create dbo from dbx model with invalid Id", func(t *testing.T) {
dbxUser := dbx.User{
Id: "qweqwe",
FirstName: "FirstName",
LastName: "LastName",
Email: "email@ukr.net",
PasswordHash: []byte("ihqerfgnu238723huagsd"),
CreatedAt: time.Now(),
}
user, err := userFromDBX(&dbxUser)
assert.Nil(t, user)
assert.NotNil(t, err)
assert.Error(t, err)
})
}

37
pkg/satellite/users.go Normal file
View File

@ -0,0 +1,37 @@
// Copyright (C) 2018 Storj Labs, Inc.
// See LICENSE for copying information.
package satellite
import (
"context"
"time"
"github.com/skyrings/skyring-common/tools/uuid"
)
// Users exposes methods to manage User table in database.
type Users interface {
// GetByCredentials is a method for querying user by credentials from the database.
GetByCredentials(ctx context.Context, password []byte, email string) (*User, error)
// Get is a method for querying user from the database by id
Get(ctx context.Context, id uuid.UUID) (*User, error)
// Insert is a method for inserting user into the database
Insert(ctx context.Context, user *User) error
// Delete is a method for deleting user by Id from the database.
Delete(ctx context.Context, id uuid.UUID) error
// Update is a method for updating user entity
Update(ctx context.Context, user *User) error
}
// User is a database object that describes User entity
type User struct {
ID uuid.UUID
FirstName string
LastName string
Email string
PasswordHash []byte
CreatedAt time.Time
}