🚧 Implement basic share handling

This commit is contained in:
Andreas Schneider 2020-10-12 19:51:44 +02:00
parent e6a62bfeb1
commit 66c525aa7b
2 changed files with 251 additions and 8 deletions

162
store.go
View File

@ -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
@ -72,30 +85,47 @@ 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")
}

View File

@ -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)
}
})
}