🚧 Implement basic share handling
This commit is contained in:
parent
e6a62bfeb1
commit
66c525aa7b
160
store.go
160
store.go
|
@ -45,9 +45,22 @@ type UserStore interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
type ShareStore interface {
|
type ShareStore interface {
|
||||||
|
CreateShare() (Share, error)
|
||||||
|
UpdateShareAttributes(share Share) error
|
||||||
|
RemoveShare(id uuid.UUID) error
|
||||||
|
AddUserToShare(share Share, username string, role ShareRole) error
|
||||||
|
RemoveUserFromShare(share Share, username string) error
|
||||||
|
AddLogin(share Share, username string, login Login) error
|
||||||
|
RemoveLogin(share Share, username string, loginName string) error
|
||||||
|
|
||||||
|
GetShares() ([]Share, error)
|
||||||
|
|
||||||
|
FindByLogin(username, loginName string) (LoginShare, error)
|
||||||
|
FindByUser(username string) ([]UserShare, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ UserStore = &DBStore{}
|
var _ UserStore = (*DBStore)(nil)
|
||||||
|
var _ ShareStore = (*DBStore)(nil)
|
||||||
|
|
||||||
type GlobalRole string
|
type GlobalRole string
|
||||||
|
|
||||||
|
@ -73,29 +86,46 @@ type User struct {
|
||||||
|
|
||||||
type Share struct {
|
type Share struct {
|
||||||
UUID uuid.UUID
|
UUID uuid.UUID
|
||||||
Directory string
|
Name string
|
||||||
Users []ShareUser
|
Description string
|
||||||
}
|
}
|
||||||
|
|
||||||
type ShareUser struct {
|
type ShareUser struct {
|
||||||
Username string
|
Username string
|
||||||
Role ShareRole
|
Role ShareRole
|
||||||
Logins []Login
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Login struct {
|
type Login struct {
|
||||||
Loginname string
|
LoginName string
|
||||||
Password string
|
Password string
|
||||||
}
|
}
|
||||||
|
|
||||||
const usersPrefix = "users:"
|
// View on a share from the perspective of a user of that share.
|
||||||
|
type UserShare struct {
|
||||||
|
Share
|
||||||
|
Role ShareRole
|
||||||
|
}
|
||||||
|
|
||||||
|
// View on a share from the perspective of a specific login (with associated user).
|
||||||
|
type LoginShare struct {
|
||||||
|
Share
|
||||||
|
ShareUser
|
||||||
|
Login
|
||||||
|
}
|
||||||
|
|
||||||
|
const userPrefix = "user:"
|
||||||
|
const sharePrefix = "share:"
|
||||||
|
|
||||||
type DBStore struct {
|
type DBStore struct {
|
||||||
db *buntdb.DB
|
db *buntdb.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u User) key() string {
|
func (u User) key() string {
|
||||||
return usersPrefix + u.Username
|
return userPrefix + u.Username
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Share) key() string {
|
||||||
|
return sharePrefix + s.UUID.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDBStore(filename string) (*DBStore, error) {
|
func NewDBStore(filename string) (*DBStore, error) {
|
||||||
|
@ -135,6 +165,8 @@ func (u User) merge(updates User) (User, error) {
|
||||||
return merged, nil
|
return merged, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var ErrShareNotFound = errors.New("share not found")
|
||||||
|
|
||||||
func (store *DBStore) AddUser(user User) (err error) {
|
func (store *DBStore) AddUser(user User) (err error) {
|
||||||
if strings.Contains(user.Username, ":") {
|
if strings.Contains(user.Username, ":") {
|
||||||
return ErrInvalidUsername
|
return ErrInvalidUsername
|
||||||
|
@ -260,3 +292,117 @@ func hashPassword(password string) (string, error) {
|
||||||
}
|
}
|
||||||
return string(hash), nil
|
return string(hash), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (store *DBStore) CreateShare() (Share, error) {
|
||||||
|
share := Share{
|
||||||
|
UUID: uuid.NewV4(),
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := store.db.Update(func(tx *buntdb.Tx) error {
|
||||||
|
b, err := json.Marshal(share)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot marshal share: %w", err)
|
||||||
|
}
|
||||||
|
if _, _, err := tx.Set(share.key(), string(b), nil); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
return Share{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return share, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (store *DBStore) UpdateShareAttributes(share Share) error {
|
||||||
|
return store.db.Update(func(tx *buntdb.Tx) error {
|
||||||
|
var existingShare Share
|
||||||
|
if val, err := tx.Get(share.key()); err == buntdb.ErrNotFound {
|
||||||
|
return ErrShareNotFound
|
||||||
|
} else if err != nil {
|
||||||
|
return err
|
||||||
|
} else if err := json.Unmarshal([]byte(val), &existingShare); err != nil {
|
||||||
|
return fmt.Errorf("cannot unmarshal share %s: %w", share.UUID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
existingShare.UUID = share.UUID
|
||||||
|
existingShare.Name = share.Name
|
||||||
|
existingShare.Description = share.Description
|
||||||
|
|
||||||
|
if b, err := json.Marshal(existingShare); err != nil {
|
||||||
|
return fmt.Errorf("cannot marshal share %s: %w", share.UUID, err)
|
||||||
|
} else if _, _, err := tx.Set(existingShare.key(), string(b), nil); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (store *DBStore) RemoveShare(id uuid.UUID) error {
|
||||||
|
return store.db.Update(func(tx *buntdb.Tx) error {
|
||||||
|
share := Share{UUID: id}
|
||||||
|
if _, err := tx.Delete(share.key()); err == buntdb.ErrNotFound {
|
||||||
|
return ErrShareNotFound
|
||||||
|
} else {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (store *DBStore) AddUserToShare(share Share, username string, role ShareRole) error {
|
||||||
|
panic("implement me")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (store *DBStore) RemoveUserFromShare(share Share, username string) error {
|
||||||
|
panic("implement me")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (store *DBStore) AddLogin(share Share, username string, login Login) error {
|
||||||
|
panic("implement me")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (store *DBStore) RemoveLogin(share Share, username string, loginName string) error {
|
||||||
|
panic("implement me")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (store *DBStore) GetShares() (shares []Share, err error) {
|
||||||
|
err = store.db.View(func(tx *buntdb.Tx) error {
|
||||||
|
var processingError error
|
||||||
|
|
||||||
|
if err := tx.AscendKeys(sharePrefix+"*", func(key, value string) bool {
|
||||||
|
var share Share
|
||||||
|
|
||||||
|
if err := json.Unmarshal([]byte(value), &share); err != nil {
|
||||||
|
processingError = err
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Just in case ...
|
||||||
|
idString := strings.TrimPrefix(key, sharePrefix)
|
||||||
|
if id, err := uuid.FromString(idString); err != nil {
|
||||||
|
processingError = fmt.Errorf("invalid uuid in db: %q: %w", idString, err)
|
||||||
|
return false
|
||||||
|
} else {
|
||||||
|
share.UUID = id
|
||||||
|
}
|
||||||
|
|
||||||
|
shares = append(shares, share)
|
||||||
|
return true
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return processingError
|
||||||
|
})
|
||||||
|
|
||||||
|
return shares, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (store *DBStore) FindByLogin(username, loginName string) (LoginShare, error) {
|
||||||
|
panic("implement me")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (store *DBStore) FindByUser(username string) ([]UserShare, error) {
|
||||||
|
panic("implement me")
|
||||||
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package main
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
uuid "github.com/satori/go.uuid"
|
||||||
"github.com/tidwall/buntdb"
|
"github.com/tidwall/buntdb"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -101,3 +102,99 @@ func TestStoreUserHandling(t *testing.T) {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestStoreShareHandling(t *testing.T) {
|
||||||
|
store, err := NewDBStore(":memory:")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("cannot create store: %v", err)
|
||||||
|
}
|
||||||
|
defer store.Close()
|
||||||
|
|
||||||
|
t.Run("store should be empty initially", func(t *testing.T) {
|
||||||
|
shares, err := store.GetShares()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("no error should have been returned: %v", err)
|
||||||
|
}
|
||||||
|
if len(shares) != 0 {
|
||||||
|
t.Errorf("there should be no shares")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("creating a share should work", func(t *testing.T) {
|
||||||
|
share, err := store.CreateShare()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("error creating share: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var emptyUUID uuid.UUID
|
||||||
|
if share.UUID == emptyUUID {
|
||||||
|
t.Errorf("UUID is empty")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if share.Name != "" || share.Description != "" {
|
||||||
|
t.Errorf("share should not have attributes set (yet)")
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("share attributes can be set", func(t *testing.T) {
|
||||||
|
share.Name = "a name"
|
||||||
|
share.Description = "some desc"
|
||||||
|
if err := store.UpdateShareAttributes(share); err != nil {
|
||||||
|
t.Errorf("cannot set attributes of share: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("cannot set attributes of unknown share", func(t *testing.T) {
|
||||||
|
otherShare := Share{UUID: uuid.NewV4()}
|
||||||
|
otherShare.Name = "foo"
|
||||||
|
otherShare.Description = "bar"
|
||||||
|
|
||||||
|
err := store.UpdateShareAttributes(otherShare)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("an error should have been returned")
|
||||||
|
} else if err != ErrShareNotFound {
|
||||||
|
t.Errorf("wrong error has been returned: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("share can be listed", func(t *testing.T) {
|
||||||
|
shares, err := store.GetShares()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("error getting shares: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(shares) != 1 {
|
||||||
|
t.Errorf("invalid number of shares: %d", len(shares))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if shares[0].UUID != share.UUID {
|
||||||
|
t.Errorf("unexpected uuid")
|
||||||
|
}
|
||||||
|
if shares[0].Name != "a name" {
|
||||||
|
t.Errorf("unexpected name")
|
||||||
|
}
|
||||||
|
if shares[0].Description != "some desc" {
|
||||||
|
t.Errorf("unexpected description")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("share can be removed", func(t *testing.T) {
|
||||||
|
if err := store.RemoveShare(share.UUID); err != nil {
|
||||||
|
t.Errorf("removing share failed: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("database should be empty now", func(t *testing.T) {
|
||||||
|
// checks that we properly deleted all keys
|
||||||
|
if err := store.db.View(func(tx *buntdb.Tx) error {
|
||||||
|
return tx.Ascend("", func(key, value string) bool {
|
||||||
|
t.Errorf("there should be no keys left")
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
}); err != nil {
|
||||||
|
t.Errorf("iterating keys failed: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue