multinode/database: members repository created
Change-Id: I429791636f667a19c383a2a0c524a2068cf2812f
This commit is contained in:
parent
8dc10e32ad
commit
e6dd3ecaa7
40
multinode/console/members.go
Normal file
40
multinode/console/members.go
Normal file
@ -0,0 +1,40 @@
|
||||
// Copyright (C) 2020 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
package console
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/zeebo/errs"
|
||||
|
||||
"storj.io/common/uuid"
|
||||
)
|
||||
|
||||
// Members exposes needed by MND MembersDB functionality.
|
||||
//
|
||||
// architecture: Database
|
||||
type Members interface {
|
||||
// Invite will create empty row in membersDB.
|
||||
Invite(ctx context.Context, member Member) error
|
||||
// Update updates all updatable fields of member.
|
||||
Update(ctx context.Context, member Member) error
|
||||
// Remove deletes member from membersDB.
|
||||
Remove(ctx context.Context, id uuid.UUID) error
|
||||
// GetByEmail will return member with specified email.
|
||||
GetByEmail(ctx context.Context, email string) (Member, error)
|
||||
// GetByID will return member with specified id.
|
||||
GetByID(ctx context.Context, id uuid.UUID) (Member, error)
|
||||
}
|
||||
|
||||
// ErrNoMember is a special error type that indicates about absence of member in MembersDB.
|
||||
var ErrNoMember = errs.Class("no such member")
|
||||
|
||||
// Member represents some person that is invited to the MND by node owner.
|
||||
// Member will have configurable access privileges that will define which functions and which nodes are available for him.
|
||||
type Member struct {
|
||||
ID uuid.UUID
|
||||
Email string
|
||||
Name string
|
||||
PasswordHash []byte
|
||||
}
|
64
multinode/console/members_test.go
Normal file
64
multinode/console/members_test.go
Normal file
@ -0,0 +1,64 @@
|
||||
// Copyright (C) 2020 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
package console_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/zeebo/assert"
|
||||
|
||||
"storj.io/common/testcontext"
|
||||
"storj.io/common/uuid"
|
||||
"storj.io/storj/multinode"
|
||||
"storj.io/storj/multinode/console"
|
||||
"storj.io/storj/multinode/multinodedb/multinodedbtest"
|
||||
)
|
||||
|
||||
func TestMembersDB(t *testing.T) {
|
||||
multinodedbtest.Run(t, func(ctx *testcontext.Context, t *testing.T, db multinode.DB) {
|
||||
members := db.Members()
|
||||
|
||||
memberID, err := uuid.New()
|
||||
require.NoError(t, err)
|
||||
|
||||
memberBob := console.Member{
|
||||
ID: memberID,
|
||||
Email: "mail@example.com",
|
||||
Name: "Bob",
|
||||
PasswordHash: []byte{0},
|
||||
}
|
||||
|
||||
err = members.Invite(ctx, memberBob)
|
||||
assert.NoError(t, err)
|
||||
|
||||
memberToCheck, err := members.GetByEmail(ctx, memberBob.Email)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, memberToCheck.Email, memberBob.Email)
|
||||
assert.Equal(t, memberToCheck.Name, memberBob.Name)
|
||||
assert.Equal(t, memberToCheck.Email, memberBob.Email)
|
||||
|
||||
memberBob.Name = "Alice"
|
||||
err = members.Update(ctx, memberBob)
|
||||
assert.NoError(t, err)
|
||||
|
||||
memberAlice, err := members.GetByID(ctx, memberToCheck.ID)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, memberToCheck.Email, memberAlice.Email)
|
||||
assert.Equal(t, memberToCheck.Name, memberAlice.Name)
|
||||
assert.Equal(t, memberToCheck.Email, memberAlice.Email)
|
||||
assert.Equal(t, memberToCheck.ID, memberAlice.ID)
|
||||
|
||||
err = members.Remove(ctx, memberAlice.ID)
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = members.GetByID(ctx, memberToCheck.ID)
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, true, console.ErrNoMember.Has(err))
|
||||
|
||||
_, err = members.GetByEmail(ctx, memberToCheck.Email)
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, true, console.ErrNoMember.Has(err))
|
||||
})
|
||||
}
|
@ -1,16 +1,18 @@
|
||||
// Copyright (C) 2020 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
package mutlinodedb
|
||||
package multinodedb
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/spacemonkeygo/monkit/v3"
|
||||
"github.com/zeebo/errs"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"storj.io/storj/multinode"
|
||||
"storj.io/storj/multinode/console"
|
||||
"storj.io/storj/multinode/mutlinodedb/dbx"
|
||||
"storj.io/storj/multinode/multinodedb/dbx"
|
||||
"storj.io/storj/private/dbutil"
|
||||
"storj.io/storj/private/dbutil/pgutil"
|
||||
)
|
||||
@ -39,8 +41,8 @@ type multinodeDB struct {
|
||||
source string
|
||||
}
|
||||
|
||||
// New creates instance of database supports postgres.
|
||||
func New(log *zap.Logger, databaseURL string) (multinode.DB, error) {
|
||||
// Open creates instance of database supports postgres.
|
||||
func Open(ctx context.Context, log *zap.Logger, databaseURL string) (multinode.DB, error) {
|
||||
driver, source, implementation, err := dbutil.SplitConnStr(databaseURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -52,17 +54,17 @@ func New(log *zap.Logger, databaseURL string) (multinode.DB, error) {
|
||||
|
||||
source = pgutil.CheckApplicationName(source)
|
||||
|
||||
// dbxDB, err := dbx.Open(driver, source)
|
||||
// if err != nil {
|
||||
// return nil, Error.New("failed opening database via DBX at %q: %v",
|
||||
// source, err)
|
||||
// }
|
||||
// log.Debug("Connected to:", zap.String("db source", source))
|
||||
dbxDB, err := dbx.Open(driver, source)
|
||||
if err != nil {
|
||||
return nil, Error.New("failed opening database via DBX at %q: %v",
|
||||
source, err)
|
||||
}
|
||||
log.Debug("Connected to:", zap.String("db source", source))
|
||||
|
||||
// dbutil.Configure(dbxDB.DB, "multinodedb", mon)
|
||||
dbutil.Configure(ctx, dbxDB.DB, "multinodedb", mon)
|
||||
|
||||
core := &multinodeDB{
|
||||
// DB: dbxDB,
|
||||
DB: dbxDB,
|
||||
|
||||
log: log,
|
||||
driver: driver,
|
||||
@ -77,6 +79,18 @@ func New(log *zap.Logger, databaseURL string) (multinode.DB, error) {
|
||||
func (db *multinodeDB) Nodes() console.Nodes {
|
||||
return &nodes{
|
||||
methods: db,
|
||||
db: db,
|
||||
}
|
||||
}
|
||||
|
||||
// Members returns members database.
|
||||
func (db *multinodeDB) Members() console.Members {
|
||||
return &members{
|
||||
methods: db,
|
||||
}
|
||||
}
|
||||
|
||||
// CreateSchema creates schema.
|
||||
func (db *multinodeDB) CreateSchema(ctx context.Context) error {
|
||||
_, err := db.ExecContext(ctx, db.DB.Schema())
|
||||
return err
|
||||
}
|
24
multinode/multinodedb/dbx/gen.go
Normal file
24
multinode/multinodedb/dbx/gen.go
Normal file
@ -0,0 +1,24 @@
|
||||
// Copyright (C) 2020 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
package dbx
|
||||
|
||||
import (
|
||||
"github.com/spacemonkeygo/monkit/v3"
|
||||
"github.com/zeebo/errs"
|
||||
)
|
||||
|
||||
//go:generate sh gen.sh
|
||||
|
||||
var mon = monkit.Package()
|
||||
|
||||
func init() {
|
||||
// catch dbx errors
|
||||
class := errs.Class("multinodedb dbx error")
|
||||
WrapErr = func(e *Error) error {
|
||||
if e.Code == ErrorCode_NoRows {
|
||||
return e.Err
|
||||
}
|
||||
return class.Wrap(e)
|
||||
}
|
||||
}
|
45
multinode/multinodedb/dbx/multinodedb.dbx
Normal file
45
multinode/multinodedb/dbx/multinodedb.dbx
Normal file
@ -0,0 +1,45 @@
|
||||
// dbx.v1 golang multinodedb.dbx .
|
||||
|
||||
model node (
|
||||
key id
|
||||
|
||||
field id blob
|
||||
field name text ( updatable )
|
||||
field tag text ( updatable )
|
||||
field public_address text
|
||||
field api_secret blob
|
||||
field logo blob ( updatable )
|
||||
)
|
||||
|
||||
create node ( )
|
||||
delete node ( where node.id = ? )
|
||||
|
||||
read one (
|
||||
select node
|
||||
where node.id = ?
|
||||
)
|
||||
|
||||
model member (
|
||||
key id
|
||||
|
||||
field id blob
|
||||
field email text ( updatable )
|
||||
field name text ( updatable )
|
||||
field password_hash blob ( updatable )
|
||||
|
||||
field created_at timestamp ( autoinsert )
|
||||
)
|
||||
|
||||
create member ( )
|
||||
delete member ( where member.id = ? )
|
||||
|
||||
update member ( where member.id = ? )
|
||||
|
||||
read one (
|
||||
select member
|
||||
where member.email = ?
|
||||
)
|
||||
read one (
|
||||
select member
|
||||
where member.id = ?
|
||||
)
|
@ -269,7 +269,15 @@ func newpgx(db *DB) *pgxDB {
|
||||
}
|
||||
|
||||
func (obj *pgxDB) Schema() string {
|
||||
return `CREATE TABLE nodes (
|
||||
return `CREATE TABLE members (
|
||||
id bytea NOT NULL,
|
||||
email text NOT NULL,
|
||||
name text NOT NULL,
|
||||
password_hash bytea NOT NULL,
|
||||
created_at timestamp with time zone NOT NULL,
|
||||
PRIMARY KEY ( id )
|
||||
);
|
||||
CREATE TABLE nodes (
|
||||
id bytea NOT NULL,
|
||||
name text NOT NULL,
|
||||
tag text NOT NULL,
|
||||
@ -340,6 +348,117 @@ nextval:
|
||||
fmt.Fprint(f, "]")
|
||||
}
|
||||
|
||||
type Member struct {
|
||||
Id []byte
|
||||
Email string
|
||||
Name string
|
||||
PasswordHash []byte
|
||||
CreatedAt time.Time
|
||||
}
|
||||
|
||||
func (Member) _Table() string { return "members" }
|
||||
|
||||
type Member_Update_Fields struct {
|
||||
Email Member_Email_Field
|
||||
Name Member_Name_Field
|
||||
PasswordHash Member_PasswordHash_Field
|
||||
}
|
||||
|
||||
type Member_Id_Field struct {
|
||||
_set bool
|
||||
_null bool
|
||||
_value []byte
|
||||
}
|
||||
|
||||
func Member_Id(v []byte) Member_Id_Field {
|
||||
return Member_Id_Field{_set: true, _value: v}
|
||||
}
|
||||
|
||||
func (f Member_Id_Field) value() interface{} {
|
||||
if !f._set || f._null {
|
||||
return nil
|
||||
}
|
||||
return f._value
|
||||
}
|
||||
|
||||
func (Member_Id_Field) _Column() string { return "id" }
|
||||
|
||||
type Member_Email_Field struct {
|
||||
_set bool
|
||||
_null bool
|
||||
_value string
|
||||
}
|
||||
|
||||
func Member_Email(v string) Member_Email_Field {
|
||||
return Member_Email_Field{_set: true, _value: v}
|
||||
}
|
||||
|
||||
func (f Member_Email_Field) value() interface{} {
|
||||
if !f._set || f._null {
|
||||
return nil
|
||||
}
|
||||
return f._value
|
||||
}
|
||||
|
||||
func (Member_Email_Field) _Column() string { return "email" }
|
||||
|
||||
type Member_Name_Field struct {
|
||||
_set bool
|
||||
_null bool
|
||||
_value string
|
||||
}
|
||||
|
||||
func Member_Name(v string) Member_Name_Field {
|
||||
return Member_Name_Field{_set: true, _value: v}
|
||||
}
|
||||
|
||||
func (f Member_Name_Field) value() interface{} {
|
||||
if !f._set || f._null {
|
||||
return nil
|
||||
}
|
||||
return f._value
|
||||
}
|
||||
|
||||
func (Member_Name_Field) _Column() string { return "name" }
|
||||
|
||||
type Member_PasswordHash_Field struct {
|
||||
_set bool
|
||||
_null bool
|
||||
_value []byte
|
||||
}
|
||||
|
||||
func Member_PasswordHash(v []byte) Member_PasswordHash_Field {
|
||||
return Member_PasswordHash_Field{_set: true, _value: v}
|
||||
}
|
||||
|
||||
func (f Member_PasswordHash_Field) value() interface{} {
|
||||
if !f._set || f._null {
|
||||
return nil
|
||||
}
|
||||
return f._value
|
||||
}
|
||||
|
||||
func (Member_PasswordHash_Field) _Column() string { return "password_hash" }
|
||||
|
||||
type Member_CreatedAt_Field struct {
|
||||
_set bool
|
||||
_null bool
|
||||
_value time.Time
|
||||
}
|
||||
|
||||
func Member_CreatedAt(v time.Time) Member_CreatedAt_Field {
|
||||
return Member_CreatedAt_Field{_set: true, _value: v}
|
||||
}
|
||||
|
||||
func (f Member_CreatedAt_Field) value() interface{} {
|
||||
if !f._set || f._null {
|
||||
return nil
|
||||
}
|
||||
return f._value
|
||||
}
|
||||
|
||||
func (Member_CreatedAt_Field) _Column() string { return "created_at" }
|
||||
|
||||
type Node struct {
|
||||
Id []byte
|
||||
Name string
|
||||
@ -924,6 +1043,38 @@ func (obj *pgxImpl) Create_Node(ctx context.Context,
|
||||
|
||||
}
|
||||
|
||||
func (obj *pgxImpl) Create_Member(ctx context.Context,
|
||||
member_id Member_Id_Field,
|
||||
member_email Member_Email_Field,
|
||||
member_name Member_Name_Field,
|
||||
member_password_hash Member_PasswordHash_Field) (
|
||||
member *Member, err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
||||
__now := obj.db.Hooks.Now().UTC()
|
||||
__id_val := member_id.value()
|
||||
__email_val := member_email.value()
|
||||
__name_val := member_name.value()
|
||||
__password_hash_val := member_password_hash.value()
|
||||
__created_at_val := __now
|
||||
|
||||
var __embed_stmt = __sqlbundle_Literal("INSERT INTO members ( id, email, name, password_hash, created_at ) VALUES ( ?, ?, ?, ?, ? ) RETURNING members.id, members.email, members.name, members.password_hash, members.created_at")
|
||||
|
||||
var __values []interface{}
|
||||
__values = append(__values, __id_val, __email_val, __name_val, __password_hash_val, __created_at_val)
|
||||
|
||||
var __stmt = __sqlbundle_Render(obj.dialect, __embed_stmt)
|
||||
obj.logStmt(__stmt, __values...)
|
||||
|
||||
member = &Member{}
|
||||
err = obj.driver.QueryRowContext(ctx, __stmt, __values...).Scan(&member.Id, &member.Email, &member.Name, &member.PasswordHash, &member.CreatedAt)
|
||||
if err != nil {
|
||||
return nil, obj.makeErr(err)
|
||||
}
|
||||
return member, nil
|
||||
|
||||
}
|
||||
|
||||
func (obj *pgxImpl) Get_Node_By_Id(ctx context.Context,
|
||||
node_id Node_Id_Field) (
|
||||
node *Node, err error) {
|
||||
@ -946,6 +1097,123 @@ func (obj *pgxImpl) Get_Node_By_Id(ctx context.Context,
|
||||
|
||||
}
|
||||
|
||||
func (obj *pgxImpl) Get_Member_By_Email(ctx context.Context,
|
||||
member_email Member_Email_Field) (
|
||||
member *Member, err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
||||
var __embed_stmt = __sqlbundle_Literal("SELECT members.id, members.email, members.name, members.password_hash, members.created_at FROM members WHERE members.email = ? LIMIT 2")
|
||||
|
||||
var __values []interface{}
|
||||
__values = append(__values, member_email.value())
|
||||
|
||||
var __stmt = __sqlbundle_Render(obj.dialect, __embed_stmt)
|
||||
obj.logStmt(__stmt, __values...)
|
||||
|
||||
__rows, err := obj.driver.QueryContext(ctx, __stmt, __values...)
|
||||
if err != nil {
|
||||
return nil, obj.makeErr(err)
|
||||
}
|
||||
defer __rows.Close()
|
||||
|
||||
if !__rows.Next() {
|
||||
if err := __rows.Err(); err != nil {
|
||||
return nil, obj.makeErr(err)
|
||||
}
|
||||
return nil, makeErr(sql.ErrNoRows)
|
||||
}
|
||||
|
||||
member = &Member{}
|
||||
err = __rows.Scan(&member.Id, &member.Email, &member.Name, &member.PasswordHash, &member.CreatedAt)
|
||||
if err != nil {
|
||||
return nil, obj.makeErr(err)
|
||||
}
|
||||
|
||||
if __rows.Next() {
|
||||
return nil, tooManyRows("Member_By_Email")
|
||||
}
|
||||
|
||||
if err := __rows.Err(); err != nil {
|
||||
return nil, obj.makeErr(err)
|
||||
}
|
||||
|
||||
return member, nil
|
||||
|
||||
}
|
||||
|
||||
func (obj *pgxImpl) Get_Member_By_Id(ctx context.Context,
|
||||
member_id Member_Id_Field) (
|
||||
member *Member, err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
||||
var __embed_stmt = __sqlbundle_Literal("SELECT members.id, members.email, members.name, members.password_hash, members.created_at FROM members WHERE members.id = ?")
|
||||
|
||||
var __values []interface{}
|
||||
__values = append(__values, member_id.value())
|
||||
|
||||
var __stmt = __sqlbundle_Render(obj.dialect, __embed_stmt)
|
||||
obj.logStmt(__stmt, __values...)
|
||||
|
||||
member = &Member{}
|
||||
err = obj.driver.QueryRowContext(ctx, __stmt, __values...).Scan(&member.Id, &member.Email, &member.Name, &member.PasswordHash, &member.CreatedAt)
|
||||
if err != nil {
|
||||
return (*Member)(nil), obj.makeErr(err)
|
||||
}
|
||||
return member, nil
|
||||
|
||||
}
|
||||
|
||||
func (obj *pgxImpl) Update_Member_By_Id(ctx context.Context,
|
||||
member_id Member_Id_Field,
|
||||
update Member_Update_Fields) (
|
||||
member *Member, err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
var __sets = &__sqlbundle_Hole{}
|
||||
|
||||
var __embed_stmt = __sqlbundle_Literals{Join: "", SQLs: []__sqlbundle_SQL{__sqlbundle_Literal("UPDATE members SET "), __sets, __sqlbundle_Literal(" WHERE members.id = ? RETURNING members.id, members.email, members.name, members.password_hash, members.created_at")}}
|
||||
|
||||
__sets_sql := __sqlbundle_Literals{Join: ", "}
|
||||
var __values []interface{}
|
||||
var __args []interface{}
|
||||
|
||||
if update.Email._set {
|
||||
__values = append(__values, update.Email.value())
|
||||
__sets_sql.SQLs = append(__sets_sql.SQLs, __sqlbundle_Literal("email = ?"))
|
||||
}
|
||||
|
||||
if update.Name._set {
|
||||
__values = append(__values, update.Name.value())
|
||||
__sets_sql.SQLs = append(__sets_sql.SQLs, __sqlbundle_Literal("name = ?"))
|
||||
}
|
||||
|
||||
if update.PasswordHash._set {
|
||||
__values = append(__values, update.PasswordHash.value())
|
||||
__sets_sql.SQLs = append(__sets_sql.SQLs, __sqlbundle_Literal("password_hash = ?"))
|
||||
}
|
||||
|
||||
if len(__sets_sql.SQLs) == 0 {
|
||||
return nil, emptyUpdate()
|
||||
}
|
||||
|
||||
__args = append(__args, member_id.value())
|
||||
|
||||
__values = append(__values, __args...)
|
||||
__sets.SQL = __sets_sql
|
||||
|
||||
var __stmt = __sqlbundle_Render(obj.dialect, __embed_stmt)
|
||||
obj.logStmt(__stmt, __values...)
|
||||
|
||||
member = &Member{}
|
||||
err = obj.driver.QueryRowContext(ctx, __stmt, __values...).Scan(&member.Id, &member.Email, &member.Name, &member.PasswordHash, &member.CreatedAt)
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, obj.makeErr(err)
|
||||
}
|
||||
return member, nil
|
||||
}
|
||||
|
||||
func (obj *pgxImpl) Delete_Node_By_Id(ctx context.Context,
|
||||
node_id Node_Id_Field) (
|
||||
deleted bool, err error) {
|
||||
@ -973,6 +1241,33 @@ func (obj *pgxImpl) Delete_Node_By_Id(ctx context.Context,
|
||||
|
||||
}
|
||||
|
||||
func (obj *pgxImpl) Delete_Member_By_Id(ctx context.Context,
|
||||
member_id Member_Id_Field) (
|
||||
deleted bool, err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
||||
var __embed_stmt = __sqlbundle_Literal("DELETE FROM members WHERE members.id = ?")
|
||||
|
||||
var __values []interface{}
|
||||
__values = append(__values, member_id.value())
|
||||
|
||||
var __stmt = __sqlbundle_Render(obj.dialect, __embed_stmt)
|
||||
obj.logStmt(__stmt, __values...)
|
||||
|
||||
__res, err := obj.driver.ExecContext(ctx, __stmt, __values...)
|
||||
if err != nil {
|
||||
return false, obj.makeErr(err)
|
||||
}
|
||||
|
||||
__count, err := __res.RowsAffected()
|
||||
if err != nil {
|
||||
return false, obj.makeErr(err)
|
||||
}
|
||||
|
||||
return __count > 0, nil
|
||||
|
||||
}
|
||||
|
||||
func (impl pgxImpl) isConstraintError(err error) (
|
||||
constraint string, ok bool) {
|
||||
if e, ok := err.(*pgconn.PgError); ok {
|
||||
@ -992,6 +1287,16 @@ func (obj *pgxImpl) deleteAll(ctx context.Context) (count int64, err error) {
|
||||
return 0, obj.makeErr(err)
|
||||
}
|
||||
|
||||
__count, err = __res.RowsAffected()
|
||||
if err != nil {
|
||||
return 0, obj.makeErr(err)
|
||||
}
|
||||
count += __count
|
||||
__res, err = obj.driver.ExecContext(ctx, "DELETE FROM members;")
|
||||
if err != nil {
|
||||
return 0, obj.makeErr(err)
|
||||
}
|
||||
|
||||
__count, err = __res.RowsAffected()
|
||||
if err != nil {
|
||||
return 0, obj.makeErr(err)
|
||||
@ -1044,6 +1349,20 @@ func (rx *Rx) Rollback() (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
func (rx *Rx) Create_Member(ctx context.Context,
|
||||
member_id Member_Id_Field,
|
||||
member_email Member_Email_Field,
|
||||
member_name Member_Name_Field,
|
||||
member_password_hash Member_PasswordHash_Field) (
|
||||
member *Member, err error) {
|
||||
var tx *Tx
|
||||
if tx, err = rx.getTx(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
return tx.Create_Member(ctx, member_id, member_email, member_name, member_password_hash)
|
||||
|
||||
}
|
||||
|
||||
func (rx *Rx) Create_Node(ctx context.Context,
|
||||
node_id Node_Id_Field,
|
||||
node_name Node_Name_Field,
|
||||
@ -1060,6 +1379,16 @@ func (rx *Rx) Create_Node(ctx context.Context,
|
||||
|
||||
}
|
||||
|
||||
func (rx *Rx) Delete_Member_By_Id(ctx context.Context,
|
||||
member_id Member_Id_Field) (
|
||||
deleted bool, err error) {
|
||||
var tx *Tx
|
||||
if tx, err = rx.getTx(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
return tx.Delete_Member_By_Id(ctx, member_id)
|
||||
}
|
||||
|
||||
func (rx *Rx) Delete_Node_By_Id(ctx context.Context,
|
||||
node_id Node_Id_Field) (
|
||||
deleted bool, err error) {
|
||||
@ -1070,6 +1399,26 @@ func (rx *Rx) Delete_Node_By_Id(ctx context.Context,
|
||||
return tx.Delete_Node_By_Id(ctx, node_id)
|
||||
}
|
||||
|
||||
func (rx *Rx) Get_Member_By_Email(ctx context.Context,
|
||||
member_email Member_Email_Field) (
|
||||
member *Member, err error) {
|
||||
var tx *Tx
|
||||
if tx, err = rx.getTx(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
return tx.Get_Member_By_Email(ctx, member_email)
|
||||
}
|
||||
|
||||
func (rx *Rx) Get_Member_By_Id(ctx context.Context,
|
||||
member_id Member_Id_Field) (
|
||||
member *Member, err error) {
|
||||
var tx *Tx
|
||||
if tx, err = rx.getTx(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
return tx.Get_Member_By_Id(ctx, member_id)
|
||||
}
|
||||
|
||||
func (rx *Rx) Get_Node_By_Id(ctx context.Context,
|
||||
node_id Node_Id_Field) (
|
||||
node *Node, err error) {
|
||||
@ -1080,7 +1429,25 @@ func (rx *Rx) Get_Node_By_Id(ctx context.Context,
|
||||
return tx.Get_Node_By_Id(ctx, node_id)
|
||||
}
|
||||
|
||||
func (rx *Rx) Update_Member_By_Id(ctx context.Context,
|
||||
member_id Member_Id_Field,
|
||||
update Member_Update_Fields) (
|
||||
member *Member, err error) {
|
||||
var tx *Tx
|
||||
if tx, err = rx.getTx(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
return tx.Update_Member_By_Id(ctx, member_id, update)
|
||||
}
|
||||
|
||||
type Methods interface {
|
||||
Create_Member(ctx context.Context,
|
||||
member_id Member_Id_Field,
|
||||
member_email Member_Email_Field,
|
||||
member_name Member_Name_Field,
|
||||
member_password_hash Member_PasswordHash_Field) (
|
||||
member *Member, err error)
|
||||
|
||||
Create_Node(ctx context.Context,
|
||||
node_id Node_Id_Field,
|
||||
node_name Node_Name_Field,
|
||||
@ -1090,13 +1457,30 @@ type Methods interface {
|
||||
node_logo Node_Logo_Field) (
|
||||
node *Node, err error)
|
||||
|
||||
Delete_Member_By_Id(ctx context.Context,
|
||||
member_id Member_Id_Field) (
|
||||
deleted bool, err error)
|
||||
|
||||
Delete_Node_By_Id(ctx context.Context,
|
||||
node_id Node_Id_Field) (
|
||||
deleted bool, err error)
|
||||
|
||||
Get_Member_By_Email(ctx context.Context,
|
||||
member_email Member_Email_Field) (
|
||||
member *Member, err error)
|
||||
|
||||
Get_Member_By_Id(ctx context.Context,
|
||||
member_id Member_Id_Field) (
|
||||
member *Member, err error)
|
||||
|
||||
Get_Node_By_Id(ctx context.Context,
|
||||
node_id Node_Id_Field) (
|
||||
node *Node, err error)
|
||||
|
||||
Update_Member_By_Id(ctx context.Context,
|
||||
member_id Member_Id_Field,
|
||||
update Member_Update_Fields) (
|
||||
member *Member, err error)
|
||||
}
|
||||
|
||||
type TxMethods interface {
|
@ -1,5 +1,13 @@
|
||||
-- AUTOGENERATED BY storj.io/dbx
|
||||
-- DO NOT EDIT
|
||||
CREATE TABLE members (
|
||||
id bytea NOT NULL,
|
||||
email text NOT NULL,
|
||||
name text NOT NULL,
|
||||
password_hash bytea NOT NULL,
|
||||
created_at timestamp with time zone NOT NULL,
|
||||
PRIMARY KEY ( id )
|
||||
);
|
||||
CREATE TABLE nodes (
|
||||
id bytea NOT NULL,
|
||||
name text NOT NULL,
|
117
multinode/multinodedb/members.go
Normal file
117
multinode/multinodedb/members.go
Normal file
@ -0,0 +1,117 @@
|
||||
// Copyright (C) 2020 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
package multinodedb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
|
||||
"github.com/zeebo/errs"
|
||||
|
||||
"storj.io/common/uuid"
|
||||
"storj.io/storj/multinode/console"
|
||||
"storj.io/storj/multinode/multinodedb/dbx"
|
||||
)
|
||||
|
||||
// MembersDBError indicates about internal MembersDB error.
|
||||
var MembersDBError = errs.Class("MembersDB error")
|
||||
|
||||
// ensures that members implements console.Members.
|
||||
var _ console.Members = (*members)(nil)
|
||||
|
||||
// members exposes needed by MND MembersDB functionality.
|
||||
// dbx implementation of console.Members.
|
||||
//
|
||||
// architecture: Database
|
||||
type members struct {
|
||||
methods dbx.Methods
|
||||
}
|
||||
|
||||
// Invite will create empty row in membersDB.
|
||||
func (m *members) Invite(ctx context.Context, member console.Member) (err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
||||
id, err := uuid.New()
|
||||
if err != nil {
|
||||
return MembersDBError.Wrap(err)
|
||||
}
|
||||
|
||||
_, err = m.methods.Create_Member(ctx, dbx.Member_Id(id[:]), dbx.Member_Email(member.Email), dbx.Member_Name(member.Name), dbx.Member_PasswordHash(member.PasswordHash))
|
||||
|
||||
return MembersDBError.Wrap(err)
|
||||
}
|
||||
|
||||
// Update updates all updatable fields of member.
|
||||
func (m *members) Update(ctx context.Context, member console.Member) (err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
||||
_, err = m.methods.Update_Member_By_Id(ctx, dbx.Member_Id(member.ID[:]), dbx.Member_Update_Fields{
|
||||
Email: dbx.Member_Email(member.Email),
|
||||
Name: dbx.Member_Name(member.Name),
|
||||
PasswordHash: dbx.Member_PasswordHash(member.PasswordHash),
|
||||
})
|
||||
|
||||
return MembersDBError.Wrap(err)
|
||||
}
|
||||
|
||||
// Remove deletes member from membersDB.
|
||||
func (m *members) Remove(ctx context.Context, id uuid.UUID) (err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
||||
_, err = m.methods.Delete_Member_By_Id(ctx, dbx.Member_Id(id[:]))
|
||||
|
||||
return MembersDBError.Wrap(err)
|
||||
}
|
||||
|
||||
// GetByEmail will return member with specified email.
|
||||
func (m *members) GetByEmail(ctx context.Context, email string) (_ console.Member, err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
||||
memberDbx, err := m.methods.Get_Member_By_Email(ctx, dbx.Member_Email(email))
|
||||
if err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return console.Member{}, console.ErrNoMember.Wrap(err)
|
||||
}
|
||||
return console.Member{}, MembersDBError.Wrap(err)
|
||||
}
|
||||
|
||||
member, err := fromDBXMember(memberDbx)
|
||||
|
||||
return member, MembersDBError.Wrap(err)
|
||||
}
|
||||
|
||||
// GetByID will return member with specified id.
|
||||
func (m *members) GetByID(ctx context.Context, id uuid.UUID) (_ console.Member, err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
||||
memberDbx, err := m.methods.Get_Member_By_Id(ctx, dbx.Member_Id(id[:]))
|
||||
if err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return console.Member{}, console.ErrNoMember.Wrap(err)
|
||||
}
|
||||
return console.Member{}, MembersDBError.Wrap(err)
|
||||
}
|
||||
|
||||
member, err := fromDBXMember(memberDbx)
|
||||
|
||||
return member, MembersDBError.Wrap(err)
|
||||
}
|
||||
|
||||
// fromDBXMember converts dbx.Member to console.Member.
|
||||
func fromDBXMember(member *dbx.Member) (_ console.Member, err error) {
|
||||
id, err := uuid.FromBytes(member.Id)
|
||||
if err != nil {
|
||||
return console.Member{}, err
|
||||
}
|
||||
|
||||
result := console.Member{
|
||||
ID: id,
|
||||
Email: member.Email,
|
||||
Name: member.Name,
|
||||
PasswordHash: member.PasswordHash,
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
141
multinode/multinodedb/multinodedbtest/run.go
Normal file
141
multinode/multinodedb/multinodedbtest/run.go
Normal file
@ -0,0 +1,141 @@
|
||||
// Copyright (C) 2020 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
package multinodedbtest
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/zeebo/errs"
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zaptest"
|
||||
|
||||
"storj.io/common/testcontext"
|
||||
"storj.io/storj/multinode"
|
||||
"storj.io/storj/multinode/multinodedb"
|
||||
"storj.io/storj/multinode/multinodedb/dbx"
|
||||
"storj.io/storj/private/dbutil"
|
||||
"storj.io/storj/private/dbutil/pgtest"
|
||||
"storj.io/storj/private/dbutil/pgutil"
|
||||
"storj.io/storj/private/dbutil/tempdb"
|
||||
)
|
||||
|
||||
// Database describes a test database.
|
||||
type Database struct {
|
||||
Name string
|
||||
URL string
|
||||
Message string
|
||||
}
|
||||
|
||||
// tempMasterDB is a multinode.DB-implementing type that cleans up after itself when closed.
|
||||
type tempMasterDB struct {
|
||||
multinode.DB
|
||||
tempDB *dbutil.TempDatabase
|
||||
}
|
||||
|
||||
// Close closes a tempMasterDB and cleans it up afterward.
|
||||
func (db *tempMasterDB) Close() error {
|
||||
return errs.Combine(db.DB.Close(), db.tempDB.Close())
|
||||
}
|
||||
|
||||
// TestDBAccess provides a somewhat regularized access to the underlying DB.
|
||||
func (db *tempMasterDB) TestDBAccess() *dbx.DB {
|
||||
return db.DB.(interface{ TestDBAccess() *dbx.DB }).TestDBAccess()
|
||||
}
|
||||
|
||||
type ignoreSkip struct{}
|
||||
|
||||
func (ignoreSkip) Skip(...interface{}) {}
|
||||
|
||||
// SchemaSuffix returns a suffix for schemas.
|
||||
func SchemaSuffix() string {
|
||||
return pgutil.CreateRandomTestingSchemaName(6)
|
||||
}
|
||||
|
||||
// SchemaName returns a properly formatted schema string.
|
||||
func SchemaName(testname, category string, index int, schemaSuffix string) string {
|
||||
// postgres has a maximum schema length of 64
|
||||
// we need additional 6 bytes for the random suffix
|
||||
// and 4 bytes for the index "/S0/""
|
||||
|
||||
indexStr := strconv.Itoa(index)
|
||||
|
||||
var maxTestNameLen = 64 - len(category) - len(indexStr) - len(schemaSuffix) - 2
|
||||
if len(testname) > maxTestNameLen {
|
||||
testname = testname[:maxTestNameLen]
|
||||
}
|
||||
|
||||
if schemaSuffix == "" {
|
||||
return strings.ToLower(testname + "/" + category + indexStr)
|
||||
}
|
||||
|
||||
return strings.ToLower(testname + "/" + schemaSuffix + "/" + category + indexStr)
|
||||
}
|
||||
|
||||
// CreateMasterDB creates a new satellite database for testing.
|
||||
func CreateMasterDB(ctx context.Context, log *zap.Logger, name string, category string, index int, dbInfo Database) (db multinode.DB, err error) {
|
||||
if dbInfo.URL == "" {
|
||||
return nil, fmt.Errorf("database %s connection string not provided. %s", dbInfo.Name, dbInfo.Message)
|
||||
}
|
||||
|
||||
schemaSuffix := SchemaSuffix()
|
||||
log.Debug("creating", zap.String("suffix", schemaSuffix))
|
||||
schema := SchemaName(name, category, index, schemaSuffix)
|
||||
|
||||
tempDB, err := tempdb.OpenUnique(ctx, dbInfo.URL, schema)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return CreateMasterDBOnTopOf(ctx, log, tempDB)
|
||||
}
|
||||
|
||||
// CreateMasterDBOnTopOf creates a new satellite database on top of an already existing
|
||||
// temporary database.
|
||||
func CreateMasterDBOnTopOf(ctx context.Context, log *zap.Logger, tempDB *dbutil.TempDatabase) (db multinode.DB, err error) {
|
||||
masterDB, err := multinodedb.Open(ctx, log, tempDB.ConnStr)
|
||||
return &tempMasterDB{DB: masterDB, tempDB: tempDB}, err
|
||||
}
|
||||
|
||||
// Run method will iterate over all supported databases. Will establish
|
||||
// connection and will create tables for each DB.
|
||||
func Run(t *testing.T, test func(ctx *testcontext.Context, t *testing.T, db multinode.DB)) {
|
||||
masterDB := Database{
|
||||
Name: "Postgres",
|
||||
URL: pgtest.PickPostgres(ignoreSkip{}),
|
||||
Message: "Postgres flag missing, example: -postgres-test-db=" + pgtest.DefaultPostgres + " or use STORJ_TEST_POSTGRES environment variable.",
|
||||
}
|
||||
|
||||
t.Run(masterDB.Name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx := testcontext.New(t)
|
||||
defer ctx.Cleanup()
|
||||
|
||||
if masterDB.URL == "" {
|
||||
t.Skipf("Database %s connection string not provided. %s", masterDB.Name, masterDB.Message)
|
||||
}
|
||||
|
||||
db, err := CreateMasterDB(ctx, zaptest.NewLogger(t), t.Name(), "T", 0, masterDB)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() {
|
||||
err := db.Close()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}()
|
||||
|
||||
err = db.CreateSchema(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
test(ctx, t, db)
|
||||
})
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
// Copyright (C) 2020 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
package mutlinodedb
|
||||
package multinodedb
|
||||
|
||||
import (
|
||||
"context"
|
||||
@ -10,7 +10,7 @@ import (
|
||||
|
||||
"storj.io/common/storj"
|
||||
"storj.io/storj/multinode/console"
|
||||
"storj.io/storj/multinode/mutlinodedb/dbx"
|
||||
"storj.io/storj/multinode/multinodedb/dbx"
|
||||
)
|
||||
|
||||
// NodesDBError indicates about internal NodesDB error.
|
||||
@ -25,7 +25,6 @@ var _ console.Nodes = (*nodes)(nil)
|
||||
// architecture: Database
|
||||
type nodes struct {
|
||||
methods dbx.Methods
|
||||
db *multinodeDB
|
||||
}
|
||||
|
||||
// Add creates new node in NodesDB.
|
@ -1,12 +0,0 @@
|
||||
// Copyright (C) 2020 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
package dbx
|
||||
|
||||
import (
|
||||
"github.com/spacemonkeygo/monkit/v3"
|
||||
)
|
||||
|
||||
//go:generate sh gen.sh
|
||||
|
||||
var mon = monkit.Package()
|
@ -1,20 +0,0 @@
|
||||
// dbx.v1 golang multinodedb.dbx .
|
||||
|
||||
model node (
|
||||
key id
|
||||
|
||||
field id blob
|
||||
field name text ( updatable )
|
||||
field tag text ( updatable )
|
||||
field public_address text
|
||||
field api_secret blob
|
||||
field logo blob ( updatable )
|
||||
)
|
||||
|
||||
create node ( )
|
||||
delete node ( where node.id = ? )
|
||||
|
||||
read one (
|
||||
select node
|
||||
where node.id = ?
|
||||
)
|
@ -27,9 +27,13 @@ var (
|
||||
type DB interface {
|
||||
// Nodes returns nodes database.
|
||||
Nodes() console.Nodes
|
||||
// Members returns members database.
|
||||
Members() console.Members
|
||||
|
||||
// Close closes the database.
|
||||
Close() error
|
||||
// CreateSchema creates schema.
|
||||
CreateSchema(ctx context.Context) error
|
||||
}
|
||||
|
||||
// Config is all the configuration parameters for a Multinode Dashboard.
|
||||
|
Loading…
Reference in New Issue
Block a user