From 838eea8d5226d304115fd84cbc2ce199a3579e8c Mon Sep 17 00:00:00 2001 From: Ogurtsov-Pavel Date: Wed, 12 Jul 2023 14:19:24 +0300 Subject: [PATCH 1/3] UserStorage implementation for boltDB --- storage/boltdb/db.go | 10 + storage/boltdb/user_storage.go | 329 +++++++++++++++++++++++++++++++++ 2 files changed, 339 insertions(+) create mode 100644 storage/boltdb/user_storage.go diff --git a/storage/boltdb/db.go b/storage/boltdb/db.go index 1288d594..d2f8ea25 100644 --- a/storage/boltdb/db.go +++ b/storage/boltdb/db.go @@ -2,6 +2,7 @@ package boltdb import ( "errors" + "fmt" "sync" "time" @@ -70,3 +71,12 @@ func CloseDB(db *bolt.DB) error { return ErrorClosingNonExistentDatabase } } + +func Ready(db *bolt.DB) error { + _, ok := poolCounter.Load(db) + if !ok { + return fmt.Errorf("database not exists") + } + + return nil +} diff --git a/storage/boltdb/user_storage.go b/storage/boltdb/user_storage.go new file mode 100644 index 00000000..5fb491e6 --- /dev/null +++ b/storage/boltdb/user_storage.go @@ -0,0 +1,329 @@ +package boltdb + +import ( + "context" + "encoding/json" + "fmt" + + "github.com/madappgang/identifo/v2/l" + "github.com/madappgang/identifo/v2/model" + bolt "go.etcd.io/bbolt" +) + +const ( + UserBucket = "Users" + UserDataBucket = "UserData" + UserByFederatedIDBucket = "UserBySocialID" + UserByUsername = "UserByUsername" + UserByPhoneNumberBucket = "UserByPhoneNumber" + UserByEmailBucket = "UserByEmail" +) + +var _ model.UserStorage = &UserStorage{} + +func NewUserStorage(settings model.BoltDBDatabaseSettings) (*UserStorage, error) { + if len(settings.Path) == 0 { + return nil, ErrorEmptyDatabasePath + } + + db, err := InitDB(settings.Path) + if err != nil { + return nil, err + } + + us := &UserStorage{db: db, path: settings.Path} + + if err := us.createBuckets(); err != nil { + return nil, fmt.Errorf("failed to create buckets: %w", err) + } + + return us, nil + +} + +type UserStorage struct { + db *bolt.DB + path string +} + +func (us *UserStorage) createBuckets() error { + return us.db.Update(func(tx *bolt.Tx) error { + if _, err := tx.CreateBucketIfNotExists([]byte(UserBucket)); err != nil { + return fmt.Errorf("failed to create user bucket: %w", err) + } + if _, err := tx.CreateBucketIfNotExists([]byte(UserDataBucket)); err != nil { + return fmt.Errorf("failed to create user data bucket: %w", err) + } + if _, err := tx.CreateBucketIfNotExists([]byte(UserByFederatedIDBucket)); err != nil { + return fmt.Errorf("failed to create federated id bucket: %w", err) + } + if _, err := tx.CreateBucketIfNotExists([]byte(UserByUsername)); err != nil { + return fmt.Errorf("failed to create username bucket: %w", err) + } + if _, err := tx.CreateBucketIfNotExists([]byte(UserByPhoneNumberBucket)); err != nil { + return fmt.Errorf("failed to create phone number bucket: %w", err) + } + if _, err := tx.CreateBucketIfNotExists([]byte(UserByEmailBucket)); err != nil { + return fmt.Errorf("failed to create email bucket: %w", err) + } + return nil + }) +} + +func (us *UserStorage) Close(context.Context) error { + if err := CloseDB(us.db); err != nil { + return fmt.Errorf("failed to close user storage: %w") + } + + return nil +} + +func (us *UserStorage) Connect(context.Context) error { + db, err := InitDB(us.path) + if err != nil { + return fmt.Errorf("failed to connect to db") + } + + us.db = db + + return nil +} + +func (us *UserStorage) Ready(context.Context) error { + if err := Ready(us.db); err != nil { + return fmt.Errorf("not ready: %w", err) + } + + return nil +} + +func (us *UserStorage) ImportJSON(data []byte, clearOldData bool) error { + if clearOldData { + us.db.Update(func(tx *bolt.Tx) error { + tx.DeleteBucket([]byte(UserBucket)) + tx.DeleteBucket([]byte(UserByFederatedIDBucket)) + tx.DeleteBucket([]byte(UserByUsername)) + tx.DeleteBucket([]byte(UserByPhoneNumberBucket)) + tx.DeleteBucket([]byte(UserByEmailBucket)) + return nil + }) + if err := us.createBuckets(); err != nil { + return err + } + } + + ud := []model.User{} + + if err := json.Unmarshal(data, &ud); err != nil { + return err + } + + return nil +} + +func (us *UserStorage) UserByID(ctx context.Context, id string) (model.User, error) { + var res model.User + + err := us.db.View(func(tx *bolt.Tx) error { + ub := tx.Bucket([]byte(UserBucket)) + + u := ub.Get([]byte(id)) + if u == nil { + return l.ErrorUserNotFound + } + + var err error + + res, err = model.UserFromJSON(u) + if err != nil { + return fmt.Errorf("failed to convert user to json: %w", err) + } + + return nil + }) + if err != nil { + return model.User{}, fmt.Errorf("failed to view into db: %w", err) + } + return res, nil +} + +func (us *UserStorage) GetUserByFederatedID(ctx context.Context, idType model.UserFederatedType, userIdentityTypeOther, externalID string) (model.User, error) { + var res model.User + sid := string(idType) + ":" + userIdentityTypeOther + ":" + externalID + + err := us.db.View(func(tx *bolt.Tx) error { + usib := tx.Bucket([]byte(UserByFederatedIDBucket)) + + userID := usib.Get([]byte(sid)) + if userID == nil { + return l.ErrorUserNotFound + } + + ub := tx.Bucket([]byte(UserBucket)) + + u := ub.Get(userID) + if u == nil { + return l.ErrorUserNotFound + } + + var err error + res, err = model.UserFromJSON(u) + return err + }) + if err != nil { + return model.User{}, err + } + return res, nil +} + +func (us *UserStorage) UserData(ctx context.Context, userID string, fields ...model.UserDataField) (model.UserData, error) { + var res model.UserData + + err := us.db.View(func(tx *bolt.Tx) error { + ub := tx.Bucket([]byte(UserBucket)) + + u := ub.Get([]byte(userID)) + if u == nil { + return l.ErrorUserNotFound + } + + var err error + res, err = model.UserDataFromJSON(u) + if err != nil { + return err + } + + return nil + }) + if err != nil { + return model.UserData{}, err + } + + return model.FilterUserDataFields(res, fields...), nil +} + +func (us *UserStorage) UserBySecondaryID(ctx context.Context, idt model.AuthIdentityType, id string) (model.User, error) { + switch idt { + case model.AuthIdentityTypePhone: + user, err := us.userByPhone(ctx, id) + if err != nil { + return model.User{}, fmt.Errorf("failed to find user by phone: %w", err) + } + + return user, nil + case model.AuthIdentityTypeEmail: + user, err := us.userByEmail(ctx, id) + if err != nil { + return model.User{}, fmt.Errorf("failed to find user by email: %w", err) + } + + return user, nil + case model.AuthIdentityTypeUsername: + user, err := us.userByUsername(ctx, id) + if err != nil { + return model.User{}, fmt.Errorf("failed to find user by name: %w", err) + } + + return user, nil + } + + return model.User{}, fmt.Errorf("invalid id type") +} + +func (us *UserStorage) userByPhone(ctx context.Context, Phone string) (model.User, error) { + var res model.User + err := us.db.View(func(tx *bolt.Tx) error { + upnb := tx.Bucket([]byte(UserByPhoneNumberBucket)) + + userID := upnb.Get([]byte(Phone)) + if userID == nil { + return l.ErrorUserNotFound + } + + ub := tx.Bucket([]byte(UserBucket)) + + u := ub.Get(userID) + if u == nil { + return l.ErrorUserNotFound + } + + var err error + res, err = model.UserFromJSON(u) + if err != nil { + return err + } + + return nil + }) + if err != nil { + return model.User{}, err + } + + return res, nil +} + +func (us *UserStorage) userByEmail(ctx context.Context, email string) (model.User, error) { + var res model.User + err := us.db.View(func(tx *bolt.Tx) error { + ueb := tx.Bucket([]byte(UserByEmailBucket)) + + userID := ueb.Get([]byte(email)) + if userID == nil { + return l.ErrorUserNotFound + } + + ub := tx.Bucket([]byte(UserBucket)) + + u := ub.Get(userID) + if u == nil { + return l.ErrorUserNotFound + } + + var err error + res, err = model.UserFromJSON(u) + if err != nil { + return err + } + + return nil + }) + if err != nil { + return model.User{}, err + } + + return res, nil +} + +func (us *UserStorage) userByUsername(ctx context.Context, username string) (model.User, error) { + var res model.User + err := us.db.View(func(tx *bolt.Tx) error { + unpb := tx.Bucket([]byte(UserByUsername)) + key := username + + userID := unpb.Get([]byte(key)) + if userID == nil { + return l.ErrorUserNotFound + } + + ub := tx.Bucket([]byte(UserBucket)) + + u := ub.Get(userID) + if u == nil { + return l.ErrorUserNotFound + } + + var err error + + res, err = model.UserFromJSON(u) + if err != nil { + return err + } + + return err + }) + if err != nil { + return model.User{}, err + } + + return res, nil +} From 5170b998d90454096ced9fa74e1fa8e37162346e Mon Sep 17 00:00:00 2001 From: Ogurtsov-Pavel Date: Thu, 13 Jul 2023 16:39:44 +0300 Subject: [PATCH 2/3] GetUserByFederatedID -> UserByFederatedID --- storage/boltdb/user_storage.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/storage/boltdb/user_storage.go b/storage/boltdb/user_storage.go index 5fb491e6..126a7846 100644 --- a/storage/boltdb/user_storage.go +++ b/storage/boltdb/user_storage.go @@ -147,7 +147,7 @@ func (us *UserStorage) UserByID(ctx context.Context, id string) (model.User, err return res, nil } -func (us *UserStorage) GetUserByFederatedID(ctx context.Context, idType model.UserFederatedType, userIdentityTypeOther, externalID string) (model.User, error) { +func (us *UserStorage) UserByFederatedID(ctx context.Context, idType model.UserFederatedType, userIdentityTypeOther, externalID string) (model.User, error) { var res model.User sid := string(idType) + ":" + userIdentityTypeOther + ":" + externalID From 338fc5e811b82a427882353e11ce28af43718ca1 Mon Sep 17 00:00:00 2001 From: Ogurtsov-Pavel Date: Thu, 13 Jul 2023 16:41:34 +0300 Subject: [PATCH 3/3] get users data from UserDataBucket except of UserBucket --- storage/boltdb/user_storage.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/storage/boltdb/user_storage.go b/storage/boltdb/user_storage.go index 126a7846..81ed76bc 100644 --- a/storage/boltdb/user_storage.go +++ b/storage/boltdb/user_storage.go @@ -180,7 +180,7 @@ func (us *UserStorage) UserData(ctx context.Context, userID string, fields ...mo var res model.UserData err := us.db.View(func(tx *bolt.Tx) error { - ub := tx.Bucket([]byte(UserBucket)) + ub := tx.Bucket([]byte(UserDataBucket)) u := ub.Get([]byte(userID)) if u == nil {