diff --git a/account.go b/account.go index 0478609..563ee5d 100644 --- a/account.go +++ b/account.go @@ -4,8 +4,6 @@ package etebase import ( - "log" - "github.com/etesync/etebase-go/internal/codec" "github.com/etesync/etebase-go/internal/crypto" ) @@ -187,23 +185,60 @@ func (acc *Account) Logout() error { } // Collection is not implemented yet. -func (acc *Account) Collection() error { - resp, err := acc.client.WithToken(acc.session.Token).Post("/collection/", nil) +func (acc *Account) CreateCollection(col *EncryptedCollection) error { + resp, err := acc.client.WithToken(acc.session.Token).Post("/collection", col) + if err != nil { return err } defer resp.Body.Close() - log.Printf("resp.Status = %+v\n", resp.Status) var body interface{} if err := codec.NewDecoder(resp.Body).Decode(&body); err != nil { return err } - log.Printf("body = %+v\n", body) return err } +func (acc *Account) GetCollection(id string) (*EncryptedCollection, error) { + resp, err := acc.client.WithToken(acc.session.Token).Get("/collection/" + id) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + var body EncryptedCollection + if err := codec.NewDecoder(resp.Body).Decode(&body); err != nil { + return nil, err + } + + return &body, nil +} + +func (acc *Account) ListCollections(types [][]byte) ([]EncryptedCollection, error) { + req := struct { + CollectionTypes [][]byte `msgpack:"collectionTypes"` + }{ + types, + } + + resp, err := acc.client.WithToken(acc.session.Token).Post("/collection/list_multi", req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + var body struct { + Data []EncryptedCollection `msgpack:"data"` + } + if err := codec.NewDecoder(resp.Body).Decode(&body); err != nil { + return nil, err + } + + return body.Data, nil +} + // Signup a new user account and returns an Account instance. func Signup(c *Client, user User, password string) (*Account, error) { acc := newAccount(c) diff --git a/encrypted_models.go b/encrypted_models.go new file mode 100644 index 0000000..0a2a3d2 --- /dev/null +++ b/encrypted_models.go @@ -0,0 +1,76 @@ +package etebase + +import ( + "time" + + "github.com/etesync/etebase-go/internal/codec" + "github.com/google/uuid" +) + +const ( + CurrentVersion uint8 = 1 +) + +type CollectionAccessLevel uint32 + +const ( + ReadOnly CollectionAccessLevel = iota + Admin + ReadWrite +) + +type EncryptedCollection struct { + Item EncryptedItem `msgpack:"item"` + AccessLevel CollectionAccessLevel `msgpack:"accessLevel"` + Type []byte `msgpack:"collectionType"` + Key []byte `msgpack:"collectionKey"` + Stoken string `msgpack:"stoken"` +} + +func NewCollection(cType string, meta interface{}, content []byte) (*EncryptedCollection, error) { + metaBytes, err := codec.Marshal(meta) + if err != nil { + return nil, err + } + + uid1 := uuid.New().String() + uid2 := uuid.New().String() + return &EncryptedCollection{ + Item: EncryptedItem{ + UID: uid1, + Version: CurrentVersion, + Content: EncryptedRevision{ + UID: uid2, + Meta: metaBytes, + Chunks: []ChunkArrayItem{}, + }, + }, + Type: []byte(time.Now().String()), + Key: []byte("key"), + }, nil +} + +type EncryptedItem struct { + UID string `msgpack:"uid"` + Version uint8 `msgpack:"version"` + Etag []byte `msgpack:"etag"` + Content EncryptedRevision `msgpack:"content"` +} + +type EncryptedRevision struct { + UID string `msgpack:"uid"` + Meta []byte `msgpack:"meta"` + Deleted bool `msgpack:"deleted"` + Chunks []ChunkArrayItem `msgpack:"chunks"` +} + +type ChunkArrayItem struct { + Data string + Option []byte +} + +func (c ChunkArrayItem) MarshalMsgpack() ([]byte, error) { + return codec.Marshal([]interface{}{ + c.Data, c.Option, + }) +} diff --git a/etebase_test.go b/etebase_test.go index e1b49fe..8fe24ed 100644 --- a/etebase_test.go +++ b/etebase_test.go @@ -89,9 +89,38 @@ func (s *EtebaseSuite) TestLogin() { } func (s *EtebaseSuite) TestCollections() { + col, err := etebase.NewCollection("testType", nil, nil) + col.AccessLevel = etebase.Admin + s.Require().NoError(err) + s.Require().NoError( - s.account.Collection(), + s.account.CreateCollection(col), ) + + s.Run("ListMulti", func() { + found, err := s.account.ListCollections([][]byte{ + col.Type, + }) + s.Require().NoError(err) + s.Require().NotEmpty(found) + }) + + s.Run("GetCollection", func() { + found, err := s.account.GetCollection(col.Item.UID) + s.Require().NoError(err) + s.Require().NotEmpty(found.Stoken) + + expected := col + expected.Stoken = found.Stoken + + s.Require().Equal(expected, found) + }) + + s.Run("NotFound", func() { + _, err := s.account.GetCollection("not-an-existing-uid") + s.Require().Error(err) + s.Require().Contains(err.Error(), "not found") + }) } // TestLogout logs-out an account twice. The second time it shouldn't be diff --git a/go.mod b/go.mod index 4f746a5..80fd0f0 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/etesync/etebase-go go 1.14 require ( + github.com/google/uuid v1.1.2 github.com/stretchr/testify v1.6.1 github.com/vmihailenco/msgpack/v5 v5.0.0 golang.org/x/crypto v0.0.0-20201116153603-4be66e5b6582 diff --git a/go.sum b/go.sum index f27dab8..21c2f57 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,8 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= diff --git a/types.go b/types.go index 363f108..07b37d9 100644 --- a/types.go +++ b/types.go @@ -15,11 +15,12 @@ type User struct { type ErrorResponse struct { Code string `msgpack:"code"` Detail string `msgpack:"detail"` + Field string `msgpack:"field,omitempty"` Errors []ErrorResponse `msgpack:"errors,omitempty"` } func (err *ErrorResponse) Error() string { - return fmt.Sprintf("code: %s, detail: %s", err.Code, err.Detail) + return fmt.Sprintf("code: %s, detail: %s (%+v)", err.Code, err.Detail, err.Errors) } type SignupRequest struct {