diff --git a/apps.go b/apps.go new file mode 100644 index 0000000..8c7f080 --- /dev/null +++ b/apps.go @@ -0,0 +1,78 @@ +package gonextcloud + +import ( + req "github.com/levigross/grequests" + "github.com/partitio/gonextcloud/types" + "net/http" +) + +func (c *Client) AppList() ([]string, error) { + res, err := c.baseRequest(routes.apps, "", "", nil, http.MethodGet) + if err != nil { + return nil, err + } + var r types.AppListResponse + res.JSON(&r) + return r.Ocs.Data.Apps, nil +} + +func (c *Client) AppListEnabled() ([]string, error) { + ro := &req.RequestOptions{ + Params: map[string]string{"filter": "enabled"}, + } + res, err := c.baseRequest(routes.apps, "", "", ro, http.MethodGet) + if err != nil { + return nil, err + } + var r types.AppListResponse + res.JSON(&r) + return r.Ocs.Data.Apps, nil +} + +func (c *Client) AppListDisabled() ([]string, error) { + ro := &req.RequestOptions{ + Params: map[string]string{"filter": "disabled"}, + } + res, err := c.baseRequest(routes.apps, "", "", ro, http.MethodGet) + if err != nil { + return nil, err + } + var r types.AppListResponse + res.JSON(&r) + return r.Ocs.Data.Apps, nil +} + +func (c *Client) AppInfos(name string) (types.App, error) { + res, err := c.baseRequest(routes.apps, name, "", nil, http.MethodGet) + if err != nil { + return types.App{}, err + } + var r types.AppResponse + res.JSON(&r) + return r.Ocs.Data, nil +} + +func (c *Client) AppEnable(name string) error { + res, err := c.baseRequest(routes.apps, name, "", nil, http.MethodPut) + if err != nil { + return err + } + var r types.BaseResponse + res.JSON(&r) + return nil +} + +func (c *Client) AppDisable(name string) error { + res, err := c.baseRequest(routes.apps, name, "", nil, http.MethodDelete) + if err != nil { + return err + } + var r types.BaseResponse + res.JSON(&r) + return nil +} + +func (c *Client) appsBaseRequest(name string, route string, ro *req.RequestOptions, method string) error { + _, err := c.baseRequest(routes.apps, name, route, ro, method) + return err +} diff --git a/client/auth.go b/auth.go similarity index 77% rename from client/auth.go rename to auth.go index 398e316..bd6e782 100644 --- a/client/auth.go +++ b/auth.go @@ -1,9 +1,9 @@ -package client +package gonextcloud import ( "fmt" req "github.com/levigross/grequests" - "github.com/partitio/gonextcloud/client/types" + "github.com/partitio/gonextcloud/types" ) var unauthorized = fmt.Errorf("login first") @@ -24,10 +24,7 @@ func (c *Client) Login(username string, password string) error { } var r types.CapabilitiesResponse res.JSON(&r) - if r.Ocs.Meta.Statuscode != 100 { - e := types.ErrorFromMeta(r.Ocs.Meta) - return &e - } + // No need to check for Ocs.Meta.StatusCode as capabilities are always returned c.capabilities = &r.Ocs.Data.Capabilities // Check if authentication failed if !c.loggedIn() { @@ -39,10 +36,16 @@ func (c *Client) Login(username string, password string) error { func (c *Client) Logout() error { c.session.CloseIdleConnections() + c.session.HTTPClient.Jar = nil + // Clear capabilities as it is used to check for valid authentication + c.capabilities = nil return nil } func (c *Client) loggedIn() bool { // When authentication failed, capabilities doesn't contains core information + if c.capabilities == nil { + return false + } return c.capabilities.Core.WebdavRoot != "" } diff --git a/client/client.go b/client.go similarity index 95% rename from client/client.go rename to client.go index a049450..60ae36b 100644 --- a/client/client.go +++ b/client.go @@ -38,11 +38,11 @@ For example, to list all the Nextcloud's instance users: } */ -package client +package gonextcloud import ( req "github.com/levigross/grequests" - "github.com/partitio/gonextcloud/client/types" + "github.com/partitio/gonextcloud/types" "net/url" ) diff --git a/client/apps.go b/client/apps.go deleted file mode 100644 index ffd6911..0000000 --- a/client/apps.go +++ /dev/null @@ -1,35 +0,0 @@ -package client - -import ( - req "github.com/levigross/grequests" - "github.com/partitio/gonextcloud/client/types" -) - -func (c *Client) AppList() ([]string, error) { - if !c.loggedIn() { - return nil, unauthorized - } - u := c.baseURL.ResolveReference(routes.apps) - res, err := c.session.Get(u.String(), nil) - if err != nil { - return nil, err - } - var r types.AppListResponse - res.JSON(&r) - return r.Ocs.Data.Apps, nil - return nil, nil -} - -func (c *Client) appsBaseRequest(name string, route string, ro *req.RequestOptions, method string) error { - res, err := c.baseRequest(routes.apps, name, route, ro, method) - if err != nil { - return err - } - var r types.UserResponse - res.JSON(&r) - if r.Ocs.Meta.Statuscode != 100 { - e := types.ErrorFromMeta(r.Ocs.Meta) - return &e - } - return nil -} diff --git a/client/types/errors.go b/client/types/errors.go deleted file mode 100644 index 783a8e9..0000000 --- a/client/types/errors.go +++ /dev/null @@ -1,19 +0,0 @@ -package types - -import "fmt" - -type APIError struct { - Code int - Message string -} - -func ErrorFromMeta(meta Meta) APIError { - return APIError{ - meta.Statuscode, - meta.Message, - } -} - -func (e *APIError) Error() string { - return fmt.Sprintf("%d : %s", e.Code, e.Message) -} diff --git a/cover/gonextcloud.cov b/cover/gonextcloud.cov new file mode 100644 index 0000000..4a198b6 --- /dev/null +++ b/cover/gonextcloud.cov @@ -0,0 +1,125 @@ +mode: count +github.com/partitio/gonextcloud/utils.go:13.138,14.19 1 21 +github.com/partitio/gonextcloud/utils.go:17.2,18.16 2 20 +github.com/partitio/gonextcloud/utils.go:21.2,21.20 1 20 +github.com/partitio/gonextcloud/utils.go:24.2,28.30 2 20 +github.com/partitio/gonextcloud/utils.go:37.2,37.16 1 20 +github.com/partitio/gonextcloud/utils.go:41.2,44.34 4 19 +github.com/partitio/gonextcloud/utils.go:48.2,48.17 1 18 +github.com/partitio/gonextcloud/utils.go:14.19,16.3 1 1 +github.com/partitio/gonextcloud/utils.go:18.16,20.3 1 16 +github.com/partitio/gonextcloud/utils.go:21.20,23.3 1 6 +github.com/partitio/gonextcloud/utils.go:28.30,30.3 1 2 +github.com/partitio/gonextcloud/utils.go:30.8,30.38 1 18 +github.com/partitio/gonextcloud/utils.go:30.38,32.3 1 5 +github.com/partitio/gonextcloud/utils.go:32.8,32.37 1 13 +github.com/partitio/gonextcloud/utils.go:32.37,34.3 1 10 +github.com/partitio/gonextcloud/utils.go:34.8,34.40 1 3 +github.com/partitio/gonextcloud/utils.go:34.40,36.3 1 3 +github.com/partitio/gonextcloud/utils.go:37.16,39.3 1 1 +github.com/partitio/gonextcloud/utils.go:44.34,47.3 2 1 +github.com/partitio/gonextcloud/utils.go:51.39,58.2 4 10 +github.com/partitio/gonextcloud/apps.go:8.46,9.19 1 0 +github.com/partitio/gonextcloud/apps.go:12.2,14.16 3 0 +github.com/partitio/gonextcloud/apps.go:17.2,20.17 4 0 +github.com/partitio/gonextcloud/apps.go:9.19,11.3 1 0 +github.com/partitio/gonextcloud/apps.go:14.16,16.3 1 0 +github.com/partitio/gonextcloud/apps.go:23.106,25.16 2 0 +github.com/partitio/gonextcloud/apps.go:28.2,30.34 3 0 +github.com/partitio/gonextcloud/apps.go:34.2,34.12 1 0 +github.com/partitio/gonextcloud/apps.go:25.16,27.3 1 0 +github.com/partitio/gonextcloud/apps.go:30.34,33.3 2 0 +github.com/partitio/gonextcloud/auth.go:11.64,22.16 7 3 +github.com/partitio/gonextcloud/auth.go:25.2,30.19 4 2 +github.com/partitio/gonextcloud/auth.go:34.2,34.12 1 1 +github.com/partitio/gonextcloud/auth.go:22.16,24.3 1 1 +github.com/partitio/gonextcloud/auth.go:30.19,33.3 2 1 +github.com/partitio/gonextcloud/auth.go:37.33,43.2 4 1 +github.com/partitio/gonextcloud/auth.go:45.34,47.27 1 39 +github.com/partitio/gonextcloud/auth.go:50.2,50.45 1 38 +github.com/partitio/gonextcloud/auth.go:47.27,49.3 1 1 +github.com/partitio/gonextcloud/client.go:58.50,60.16 2 3 +github.com/partitio/gonextcloud/client.go:67.2,74.16 2 3 +github.com/partitio/gonextcloud/client.go:60.16,62.17 2 2 +github.com/partitio/gonextcloud/client.go:62.17,64.4 1 0 +github.com/partitio/gonextcloud/groups.go:9.48,11.16 2 1 +github.com/partitio/gonextcloud/groups.go:14.2,16.31 3 1 +github.com/partitio/gonextcloud/groups.go:11.16,13.3 1 0 +github.com/partitio/gonextcloud/groups.go:19.60,21.16 2 0 +github.com/partitio/gonextcloud/groups.go:24.2,26.30 3 0 +github.com/partitio/gonextcloud/groups.go:21.16,23.3 1 0 +github.com/partitio/gonextcloud/groups.go:29.63,34.16 3 0 +github.com/partitio/gonextcloud/groups.go:37.2,39.31 3 0 +github.com/partitio/gonextcloud/groups.go:34.16,36.3 1 0 +github.com/partitio/gonextcloud/groups.go:42.49,48.72 2 1 +github.com/partitio/gonextcloud/groups.go:51.2,51.12 1 1 +github.com/partitio/gonextcloud/groups.go:48.72,50.3 1 0 +github.com/partitio/gonextcloud/groups.go:54.49,55.77 1 1 +github.com/partitio/gonextcloud/groups.go:58.2,58.12 1 1 +github.com/partitio/gonextcloud/groups.go:55.77,57.3 1 0 +github.com/partitio/gonextcloud/groups.go:61.67,63.16 2 0 +github.com/partitio/gonextcloud/groups.go:66.2,68.30 3 0 +github.com/partitio/gonextcloud/groups.go:63.16,65.3 1 0 +github.com/partitio/gonextcloud/groups.go:71.107,73.16 2 2 +github.com/partitio/gonextcloud/groups.go:76.2,78.12 3 2 +github.com/partitio/gonextcloud/groups.go:73.16,75.3 1 0 +github.com/partitio/gonextcloud/users.go:12.47,13.19 1 1 +github.com/partitio/gonextcloud/users.go:16.2,18.16 3 1 +github.com/partitio/gonextcloud/users.go:21.2,23.30 3 1 +github.com/partitio/gonextcloud/users.go:13.19,15.3 1 0 +github.com/partitio/gonextcloud/users.go:18.16,20.3 1 0 +github.com/partitio/gonextcloud/users.go:26.57,27.16 1 11 +github.com/partitio/gonextcloud/users.go:30.2,30.19 1 10 +github.com/partitio/gonextcloud/users.go:33.2,36.16 4 10 +github.com/partitio/gonextcloud/users.go:39.2,43.55 4 10 +github.com/partitio/gonextcloud/users.go:46.2,46.34 1 9 +github.com/partitio/gonextcloud/users.go:50.2,50.25 1 9 +github.com/partitio/gonextcloud/users.go:27.16,29.3 1 1 +github.com/partitio/gonextcloud/users.go:30.19,32.3 1 0 +github.com/partitio/gonextcloud/users.go:36.16,38.3 1 0 +github.com/partitio/gonextcloud/users.go:43.55,45.3 1 1 +github.com/partitio/gonextcloud/users.go:46.34,49.3 2 0 +github.com/partitio/gonextcloud/users.go:53.62,54.19 1 1 +github.com/partitio/gonextcloud/users.go:57.2,62.16 4 1 +github.com/partitio/gonextcloud/users.go:65.2,67.34 3 1 +github.com/partitio/gonextcloud/users.go:71.2,71.30 1 1 +github.com/partitio/gonextcloud/users.go:54.19,56.3 1 0 +github.com/partitio/gonextcloud/users.go:62.16,64.3 1 0 +github.com/partitio/gonextcloud/users.go:67.34,70.3 2 0 +github.com/partitio/gonextcloud/users.go:74.69,82.2 2 2 +github.com/partitio/gonextcloud/users.go:84.48,86.2 1 1 +github.com/partitio/gonextcloud/users.go:88.48,93.2 2 1 +github.com/partitio/gonextcloud/users.go:95.49,100.2 2 1 +github.com/partitio/gonextcloud/users.go:102.58,104.2 1 0 +github.com/partitio/gonextcloud/users.go:106.67,108.2 1 1 +github.com/partitio/gonextcloud/users.go:110.79,112.2 1 1 +github.com/partitio/gonextcloud/users.go:114.67,116.2 1 1 +github.com/partitio/gonextcloud/users.go:118.71,120.2 1 1 +github.com/partitio/gonextcloud/users.go:122.71,124.2 1 1 +github.com/partitio/gonextcloud/users.go:126.71,128.2 1 1 +github.com/partitio/gonextcloud/users.go:130.73,132.2 1 1 +github.com/partitio/gonextcloud/users.go:134.64,136.2 1 1 +github.com/partitio/gonextcloud/users.go:138.63,139.19 1 1 +github.com/partitio/gonextcloud/users.go:142.2,145.16 4 1 +github.com/partitio/gonextcloud/users.go:148.2,150.34 3 1 +github.com/partitio/gonextcloud/users.go:154.2,154.31 1 1 +github.com/partitio/gonextcloud/users.go:139.19,141.3 1 0 +github.com/partitio/gonextcloud/users.go:145.16,147.3 1 0 +github.com/partitio/gonextcloud/users.go:150.34,153.3 2 0 +github.com/partitio/gonextcloud/users.go:157.64,164.2 2 1 +github.com/partitio/gonextcloud/users.go:166.67,173.2 2 0 +github.com/partitio/gonextcloud/users.go:175.68,182.2 2 1 +github.com/partitio/gonextcloud/users.go:184.67,191.2 2 1 +github.com/partitio/gonextcloud/users.go:193.71,194.19 1 2 +github.com/partitio/gonextcloud/users.go:197.2,200.16 4 2 +github.com/partitio/gonextcloud/users.go:203.2,205.34 3 2 +github.com/partitio/gonextcloud/users.go:209.2,209.24 1 1 +github.com/partitio/gonextcloud/users.go:194.19,196.3 1 0 +github.com/partitio/gonextcloud/users.go:200.16,202.3 1 0 +github.com/partitio/gonextcloud/users.go:205.34,208.3 2 1 +github.com/partitio/gonextcloud/users.go:212.83,220.2 2 8 +github.com/partitio/gonextcloud/users.go:222.106,224.16 2 16 +github.com/partitio/gonextcloud/users.go:227.2,229.34 3 15 +github.com/partitio/gonextcloud/users.go:233.2,233.12 1 15 +github.com/partitio/gonextcloud/users.go:224.16,226.3 1 1 +github.com/partitio/gonextcloud/users.go:229.34,232.3 2 0 diff --git a/coverage.html b/coverage.html new file mode 100644 index 0000000..82d599a --- /dev/null +++ b/coverage.html @@ -0,0 +1,663 @@ + + + + + + + + +
+ +
+ not tracked + + no coverage + low coverage + * + * + * + * + * + * + * + * + high coverage + +
+
+
+ + + + + + + + + + + + + +
+ + + diff --git a/gonextcloud_test.go b/gonextcloud_test.go index ac4a01d..3b1a9f2 100644 --- a/gonextcloud_test.go +++ b/gonextcloud_test.go @@ -1,16 +1,23 @@ package gonextcloud import ( - "github.com/partitio/gonextcloud/client" + "fmt" + "github.com/fatih/structs" + "github.com/partitio/gonextcloud/types" "github.com/stretchr/testify/assert" "gopkg.in/yaml.v2" "io/ioutil" + "net/http" + "net/url" "os" + "strings" "testing" ) var config = Config{} -var c *client.Client +var c *Client + +const password = "somecomplicatedpassword" type Config struct { URL string `yaml:"url"` @@ -37,10 +44,6 @@ func LoadConfig() error { return nil } -func TestTruth(t *testing.T) { - assert.Equal(t, true, true, "seriously ??!") -} - func TestLoadConfig(t *testing.T) { err := LoadConfig() assert.Nil(t, err) @@ -48,13 +51,13 @@ func TestLoadConfig(t *testing.T) { func TestClient(t *testing.T) { var err error - c, err = client.NewClient(config.URL) + c, err = NewClient(config.URL) assert.Nil(t, err, "aie") } func TestLoginFail(t *testing.T) { err := c.Login("", "") - assert.NotNil(t, err) + assert.Error(t, err) } func TestLogin(t *testing.T) { @@ -77,13 +80,13 @@ func TestExistingUser(t *testing.T) { func TestEmptyUser(t *testing.T) { u, err := c.User("") - assert.NotNil(t, err) + assert.Error(t, err) assert.Empty(t, u) } func TestNonExistingUser(t *testing.T) { _, err := c.User(config.NotExistingUser) - assert.NotNil(t, err) + assert.Error(t, err) } func TestUserSearch(t *testing.T) { @@ -93,12 +96,76 @@ func TestUserSearch(t *testing.T) { } func TestUserCreate(t *testing.T) { - err := c.UserCreate(config.NotExistingUser, "somecomplicatedpassword") + err := c.UserCreate(config.NotExistingUser, password, nil) + assert.Nil(t, err) +} + +func TestUserCreateFull(t *testing.T) { + if err := initClient(); err != nil { + return + } + username := fmt.Sprintf("%s-2", config.NotExistingUser) + user := &types.User{ + ID: username, + Displayname: strings.ToUpper(username), + Email: "some@address.com", + Address: "Main Street, City", + Twitter: "@me", + Phone: "42 42 242 424", + Website: "my.site.com", + } + err := c.UserCreate(username, password, user) + assert.Nil(t, err) + u, err := c.User(username) + assert.Nil(t, err) + o := structs.Map(user) + r := structs.Map(u) + for k := range o { + if ignoredUserField(k) { + continue + } + assert.Equal(t, o[k], r[k]) + } + // Clean up + err = c.UserDelete(u.ID) + assert.Nil(t, err) +} + +func TestUserUpdate(t *testing.T) { + if err := initClient(); err != nil { + return + } + username := fmt.Sprintf("%s-2", config.NotExistingUser) + err := c.UserCreate(username, password, nil) + assert.Nil(t, err) + user := &types.User{ + ID: username, + Displayname: strings.ToUpper(username), + Email: "some@address.com", + Address: "Main Street, City", + Twitter: "@me", + Phone: "42 42 242 424", + Website: "my.site.com", + } + err = c.UserUpdate(user) + assert.Nil(t, err) + u, err := c.User(username) + assert.Nil(t, err) + o := structs.Map(user) + r := structs.Map(u) + for k := range o { + if ignoredUserField(k) { + continue + } + assert.Equal(t, o[k], r[k]) + } + // Clean up + err = c.UserDelete(u.ID) assert.Nil(t, err) } func TestUserCreateExisting(t *testing.T) { - err := c.UserCreate(config.NotExistingUser, "somecomplicatedpassword") + err := c.UserCreate(config.NotExistingUser, password, nil) assert.NotNil(t, err) } @@ -193,7 +260,7 @@ func TestUserGroupAdd(t *testing.T) { func TestUserGroupSubAdminList(t *testing.T) { gs, err := c.UserGroupSubAdminList(config.NotExistingUser) - assert.NotNil(t, err) + assert.Nil(t, err) assert.Empty(t, gs) } @@ -239,7 +306,49 @@ func TestUserDelete(t *testing.T) { assert.Nil(t, err) } +func TestInvalidBaseRequest(t *testing.T) { + c.baseURL = &url.URL{} + _, err := c.baseRequest(routes.capabilities, "admin", "invalid", nil, http.MethodGet) + assert.Error(t, err) +} + func TestLogout(t *testing.T) { err := c.Logout() assert.Nil(t, err) + assert.Nil(t, c.session.HTTPClient.Jar) +} + +func TestLoggedIn(t *testing.T) { + c := &Client{} + c.capabilities = &types.Capabilities{} + assert.False(t, c.loggedIn()) +} + +func TestLoginInvalidURL(t *testing.T) { + c, _ = NewClient("") + err := c.Login("", "") + assert.Error(t, err) +} + +func TestBaseRequest(t *testing.T) { + c, _ = NewClient("") + _, err := c.baseRequest(routes.capabilities, "admin", "invalid", nil, http.MethodGet) + assert.Error(t, err) +} + +func initClient() error { + if c == nil { + if err := LoadConfig(); err != nil { + return err + } + var err error + c, err = NewClient(config.URL) + if err != nil { + return err + } + if err = c.Login(config.Login, config.Password); err != nil { + return err + } + } + return nil } diff --git a/client/groups.go b/groups.go similarity index 70% rename from client/groups.go rename to groups.go index 5b4d75a..67aacb8 100644 --- a/client/groups.go +++ b/groups.go @@ -1,8 +1,8 @@ -package client +package gonextcloud import ( req "github.com/levigross/grequests" - "github.com/partitio/gonextcloud/client/types" + "github.com/partitio/gonextcloud/types" "net/http" ) @@ -13,10 +13,6 @@ func (c *Client) GroupList() ([]string, error) { } var r types.GroupListResponse res.JSON(&r) - if r.Ocs.Meta.Statuscode != 100 { - e := types.ErrorFromMeta(r.Ocs.Meta) - return nil, &e - } return r.Ocs.Data.Groups, nil } @@ -27,10 +23,6 @@ func (c *Client) GroupUsers(name string) ([]string, error) { } var r types.UserListResponse res.JSON(&r) - if r.Ocs.Meta.Statuscode != 100 { - e := types.ErrorFromMeta(r.Ocs.Meta) - return nil, &e - } return r.Ocs.Data.Users, nil } @@ -44,10 +36,6 @@ func (c *Client) GroupSearch(search string) ([]string, error) { } var r types.GroupListResponse res.JSON(&r) - if r.Ocs.Meta.Statuscode != 100 { - e := types.ErrorFromMeta(r.Ocs.Meta) - return nil, &e - } return r.Ocs.Data.Groups, nil } @@ -77,23 +65,10 @@ func (c *Client) GroupSubAdminList(name string) ([]string, error) { } var r types.UserListResponse res.JSON(&r) - if r.Ocs.Meta.Statuscode != 100 { - e := types.ErrorFromMeta(r.Ocs.Meta) - return nil, &e - } return r.Ocs.Data.Users, nil } func (c *Client) groupBaseRequest(name string, route string, ro *req.RequestOptions, method string) error { - res, err := c.baseRequest(routes.groups, name, route, ro, method) - if err != nil { - return err - } - var r types.GroupListResponse - res.JSON(&r) - if r.Ocs.Meta.Statuscode != 100 { - e := types.ErrorFromMeta(r.Ocs.Meta) - return &e - } - return nil + _, err := c.baseRequest(routes.groups, name, route, ro, method) + return err } diff --git a/client/interface.go b/interface.go similarity index 95% rename from client/interface.go rename to interface.go index c9c3b21..271f7da 100644 --- a/client/interface.go +++ b/interface.go @@ -1,6 +1,6 @@ -package client +package gonextcloud -import "github.com/partitio/gonextcloud/client/types" +import "github.com/partitio/gonextcloud/types" type BaseClient interface { NewClient(hostname string) (*Client, error) diff --git a/client/routes.go b/routes.go similarity index 95% rename from client/routes.go rename to routes.go index a2d7a91..1841110 100644 --- a/client/routes.go +++ b/routes.go @@ -1,4 +1,4 @@ -package client +package gonextcloud import "net/url" diff --git a/client/types/app.go b/types/app.go similarity index 100% rename from client/types/app.go rename to types/app.go diff --git a/client/types/capabilities.go b/types/capabilities.go similarity index 100% rename from client/types/capabilities.go rename to types/capabilities.go diff --git a/types/errors.go b/types/errors.go new file mode 100644 index 0000000..7e15103 --- /dev/null +++ b/types/errors.go @@ -0,0 +1,55 @@ +package types + +import ( + "fmt" + "strings" +) + +type APIError struct { + Code int + Message string +} + +func ErrorFromMeta(meta Meta) *APIError { + return &APIError{ + meta.Statuscode, + meta.Message, + } +} + +func (e *APIError) Error() string { + return fmt.Sprintf("%d : %s", e.Code, e.Message) +} + +type UpdateError struct { + Field string + Error error +} + +type UserUpdateError struct { + Errors map[string]error +} + +func (e *UserUpdateError) Error() string { + var errors []string + for k, e := range e.Errors { + errors = append(errors, fmt.Sprintf("%s: %s", k, e.Error())) + } + return strings.Join(errors, ",") +} + +func NewUpdateError(errors chan UpdateError) *UserUpdateError { + empty := true + var ue UserUpdateError + for e := range errors { + if ue.Errors == nil { + empty = false + ue.Errors = map[string]error{e.Field: e.Error} + } + ue.Errors[e.Field] = e.Error + } + if !empty { + return &ue + } + return nil +} diff --git a/client/types/responses.go b/types/responses.go similarity index 100% rename from client/types/responses.go rename to types/responses.go diff --git a/client/types/user.go b/types/user.go similarity index 100% rename from client/types/user.go rename to types/user.go diff --git a/client/users.go b/users.go similarity index 75% rename from client/users.go rename to users.go index 9ffc287..b4e3cb3 100644 --- a/client/users.go +++ b/users.go @@ -1,20 +1,20 @@ -package client +package gonextcloud import ( "encoding/json" + "github.com/fatih/structs" req "github.com/levigross/grequests" - "github.com/partitio/gonextcloud/client/types" + "github.com/partitio/gonextcloud/types" "net/http" "path" "strconv" + "strings" + "sync" ) func (c *Client) UserList() ([]string, error) { - if !c.loggedIn() { - return nil, unauthorized - } - u := c.baseURL.ResolveReference(routes.users) - res, err := c.session.Get(u.String(), nil) + res, err := c.baseRequest(routes.users, "", "", nil, http.MethodGet) + //res, err := c.session.Get(u.String(), nil) if err != nil { return nil, err } @@ -27,12 +27,7 @@ func (c *Client) User(name string) (*types.User, error) { if name == "" { return nil, &types.APIError{Message: "name cannot be empty"} } - if !c.loggedIn() { - return nil, unauthorized - } - u := c.baseURL.ResolveReference(routes.users) - u.Path = path.Join(u.Path, name) - res, err := c.session.Get(u.String(), nil) + res, err := c.baseRequest(routes.users, name, "", nil, http.MethodGet) if err != nil { return nil, err } @@ -43,42 +38,36 @@ func (c *Client) User(name string) (*types.User, error) { if err := json.Unmarshal([]byte(js), &r); err != nil { return nil, err } - if r.Ocs.Meta.Statuscode != 100 { - e := types.ErrorFromMeta(r.Ocs.Meta) - return nil, &e - } return &r.Ocs.Data, nil } func (c *Client) UserSearch(search string) ([]string, error) { - if !c.loggedIn() { - return nil, unauthorized - } - u := c.baseURL.ResolveReference(routes.users) ro := &req.RequestOptions{ Params: map[string]string{"search": search}, } - res, err := c.session.Get(u.String(), ro) + res, err := c.baseRequest(routes.users, "", "", ro, http.MethodGet) if err != nil { return nil, err } var r types.UserListResponse res.JSON(&r) - if r.Ocs.Meta.Statuscode != 100 { - e := types.ErrorFromMeta(r.Ocs.Meta) - return nil, &e - } return r.Ocs.Data.Users, nil } -func (c *Client) UserCreate(username string, password string) error { +func (c *Client) UserCreate(username string, password string, user *types.User) error { ro := &req.RequestOptions{ Data: map[string]string{ "userid": username, "password": password, }, } - return c.userBaseRequest("", "", ro, http.MethodPost) + if err := c.userBaseRequest("", "", ro, http.MethodPost); err != nil { + return err + } + if user == nil { + return nil + } + return c.UserUpdate(user) } func (c *Client) UserDelete(name string) error { @@ -103,6 +92,31 @@ func (c *Client) UserSendWelcomeEmail(name string) error { return c.userBaseRequest(name, "welcome", nil, http.MethodPost) } +func (c *Client) UserUpdate(user *types.User) error { + m := structs.Map(user) + errs := make(chan types.UpdateError) + var wg sync.WaitGroup + for k := range m { + if !ignoredUserField(k) && m[k].(string) != "" { + wg.Add(1) + go func(key string, value string) { + defer wg.Done() + if err := c.userUpdateAttribute(user.ID, strings.ToLower(key), value); err != nil { + errs <- types.UpdateError{ + Field: key, + Error: err, + } + } + }(k, m[k].(string)) + } + } + go func() { + wg.Wait() + close(errs) + }() + return types.NewUpdateError(errs) +} + func (c *Client) UserUpdateEmail(name string, email string) error { return c.userUpdateAttribute(name, "email", email) } @@ -136,21 +150,12 @@ func (c *Client) UserUpdateQuota(name string, quota int) error { } func (c *Client) UserGroupList(name string) ([]string, error) { - if !c.loggedIn() { - return nil, unauthorized - } - u := c.baseURL.ResolveReference(routes.users) - u.Path = path.Join(u.Path, name, "groups") - res, err := c.session.Get(u.String(), nil) + res, err := c.baseRequest(routes.users, name, "groups", nil, http.MethodGet) if err != nil { return nil, err } var r types.GroupListResponse res.JSON(&r) - if r.Ocs.Meta.Statuscode != 100 { - e := types.ErrorFromMeta(r.Ocs.Meta) - return nil, &e - } return r.Ocs.Data.Groups, nil } @@ -202,10 +207,6 @@ func (c *Client) UserGroupSubAdminList(name string) ([]string, error) { } var r types.BaseResponse res.JSON(&r) - if r.Ocs.Meta.Statuscode != 100 { - e := types.ErrorFromMeta(r.Ocs.Meta) - return nil, &e - } return r.Ocs.Data, nil } @@ -220,15 +221,16 @@ func (c *Client) userUpdateAttribute(name string, key string, value string) erro } func (c *Client) userBaseRequest(name string, route string, ro *req.RequestOptions, method string) error { - res, err := c.baseRequest(routes.users, name, route, ro, method) - if err != nil { - return err - } - var r types.UserResponse - res.JSON(&r) - if r.Ocs.Meta.Statuscode != 100 { - e := types.ErrorFromMeta(r.Ocs.Meta) - return &e - } - return nil + _, err := c.baseRequest(routes.users, name, route, ro, method) + return err +} + +func ignoredUserField(key string) bool { + keys := []string{"ID", "Quota", "Enabled", "Groups", "Language"} + for _, k := range keys { + if key == k { + return true + } + } + return false } diff --git a/client/utils.go b/utils.go similarity index 78% rename from client/utils.go rename to utils.go index 090a0b1..94b0863 100644 --- a/client/utils.go +++ b/utils.go @@ -1,7 +1,9 @@ -package client +package gonextcloud import ( + "encoding/json" req "github.com/levigross/grequests" + "github.com/partitio/gonextcloud/types" "net/http" "net/url" "path" @@ -35,6 +37,14 @@ func (c *Client) baseRequest(route *url.URL, name string, subroute string, ro *r if err != nil { return nil, err } + // As we cannot read the ReaderCloser twice, we use the string content + js := res.String() + var r types.BaseResponse + json.Unmarshal([]byte(js), &r) + if r.Ocs.Meta.Statuscode != 100 { + err := types.ErrorFromMeta(r.Ocs.Meta) + return nil, err + } return res, nil }