diff --git a/example.config.yml b/example.config.yml index b23a734..d1cc996 100644 --- a/example.config.yml +++ b/example.config.yml @@ -2,9 +2,6 @@ url: https://my.nextcloud.com login: admin password: mypassword app-name: testapp -groups-to-create: - - grp1 - - grp2 - - grp3 +share-folder: /Documents not-existing-user: this-user-should-not-exist not-existing-group: this-group-should-not-exist \ No newline at end of file diff --git a/gonextcloud.iml b/gonextcloud.iml index eacc75a..8021953 100644 --- a/gonextcloud.iml +++ b/gonextcloud.iml @@ -1,9 +1,9 @@ - + \ No newline at end of file diff --git a/gonextcloud_test.go b/gonextcloud_test.go index 156149c..3945afd 100644 --- a/gonextcloud_test.go +++ b/gonextcloud_test.go @@ -20,13 +20,13 @@ var c *Client const password = "somecomplicatedpassword" type Config struct { - URL string `yaml:"url"` - Login string `yaml:"login"` - Password string `yaml:"password"` - AppName string `yaml:"app-name"` - GroupsToCreate []string `yaml:"groups-to-create"` - NotExistingUser string `yaml:"not-existing-user"` - NotExistingGroup string `yaml:"not-existing-group"` + URL string `yaml:"url"` + Login string `yaml:"login"` + Password string `yaml:"password"` + AppName string `yaml:"app-name"` + ShareFolder string `yaml:"share-folder"` + NotExistingUser string `yaml:"not-existing-user"` + NotExistingGroup string `yaml:"not-existing-group"` } // LoadConfig loads the test configuration @@ -307,9 +307,19 @@ func TestUserDelete(t *testing.T) { func TestInvalidBaseRequest(t *testing.T) { c.baseURL = &url.URL{} _, err := c.baseRequest(routes.capabilities, "admin", "invalid", nil, http.MethodGet) + c = nil assert.Error(t, err) } +func TestShareList(t *testing.T) { + if err := initClient(); err != nil { + return + } + s, err := c.SharesList() + assert.Nil(t, err) + assert.NotNil(t, s) +} + func TestLogout(t *testing.T) { err := c.Logout() assert.Nil(t, err) diff --git a/routes.go b/routes.go index 66068b8..b43e9f2 100644 --- a/routes.go +++ b/routes.go @@ -9,17 +9,19 @@ type Routes struct { groups *url.URL apps *url.URL monitor *url.URL + shares *url.URL } const badRequest = 998 var ( - apiPath = &url.URL{Path: "/ocs/v1.php"} + apiPath = &url.URL{Path: "/ocs/v2.php"} routes = Routes{ capabilities: &url.URL{Path: apiPath.Path + "/cloud/capabilities"}, users: &url.URL{Path: apiPath.Path + "/cloud/users"}, groups: &url.URL{Path: apiPath.Path + "/cloud/groups"}, apps: &url.URL{Path: apiPath.Path + "/cloud/apps"}, monitor: &url.URL{Path: apiPath.Path + "/apps/serverinfo/api/v1/info"}, + shares: &url.URL{Path: apiPath.Path + "/apps/files_sharing/api/v1/shares"}, } ) diff --git a/shares.go b/shares.go new file mode 100644 index 0000000..09f61a5 --- /dev/null +++ b/shares.go @@ -0,0 +1,156 @@ +package gonextcloud + +import ( + "fmt" + req "github.com/levigross/grequests" + "github.com/partitio/gonextcloud/types" + "net/http" + "strconv" + "sync" +) + +func (c *Client) SharesList() ([]types.Share, error) { + res, err := c.baseRequest(routes.shares, "", "", nil, http.MethodGet) + if err != nil { + return nil, err + } + var r types.SharesListResponse + res.JSON(&r) + return r.Ocs.Data, nil +} + +func (c *Client) Shares(path string, reshares bool, subfiles bool) ([]types.Share, error) { + ro := &req.RequestOptions{ + Params: map[string]string{ + "path": path, + "reshares": strconv.FormatBool(reshares), + "subfiles": strconv.FormatBool(subfiles), + }, + } + res, err := c.baseRequest(routes.shares, "", "", ro, http.MethodGet) + if err != nil { + return nil, err + } + var r types.SharesListResponse + res.JSON(&r) + return r.Ocs.Data, nil +} + +func (c *Client) Share(shareID string) (types.Share, error) { + res, err := c.baseRequest(routes.shares, shareID, "", nil, http.MethodGet) + if err != nil { + return types.Share{}, err + } + var r types.SharesListResponse + res.JSON(&r) + return r.Ocs.Data[0], nil +} + +func (c *Client) ShareCreate( + path string, + shareType types.ShareType, + permission types.SharePermission, + shareWith string, + publicUpload bool, + password string, +) (types.Share, error) { + + if (shareType == types.UserShare || shareType == types.GroupShare) && shareWith == "" { + return types.Share{}, fmt.Errorf("shareWith cannot be empty for ShareType UserShare or GroupShare") + } + ro := &req.RequestOptions{ + Data: map[string]string{ + "path": path, + "shareType": strconv.Itoa(int(shareType)), + "shareWith": shareWith, + "publicUpload": strconv.FormatBool(publicUpload), + "password": password, + "permissions": strconv.Itoa(int(permission)), + }, + } + res, err := c.baseRequest(routes.shares, "", "", ro, http.MethodPost) + if err != nil { + return types.Share{}, err + } + var r types.SharesResponse + res.JSON(&r) + return r.Ocs.Data, nil +} + +func (c *Client) ShareDelete(shareID int) error { + _, err := c.baseRequest(routes.shares, strconv.Itoa(shareID), "", nil, http.MethodDelete) + return err +} + +// expireDate expireDate expects a well formatted date string, e.g. ‘YYYY-MM-DD’ +func (c *Client) ShareUpdate(shareUpdate types.ShareUpdate) error { + errs := make(chan types.UpdateError) + var wg sync.WaitGroup + wg.Add(4) + go func() { + defer wg.Done() + if err := c.ShareUpdatePassword(shareUpdate.ShareID, shareUpdate.Password); err != nil { + errs <- types.UpdateError{ + Field: "password", + Error: err, + } + } + }() + go func() { + defer wg.Done() + if err := c.ShareUpdateExpireDate(shareUpdate.ShareID, shareUpdate.ExpireDate); err != nil { + errs <- types.UpdateError{ + Field: "expireDate", + Error: err, + } + } + }() + go func() { + defer wg.Done() + if err := c.ShareUpdatePermissions(shareUpdate.ShareID, shareUpdate.Permissions); err != nil { + errs <- types.UpdateError{ + Field: "permissions", + Error: err, + } + } + }() + go func() { + defer wg.Done() + if err := c.ShareUpdatePublicUpload(shareUpdate.ShareID, shareUpdate.PublicUpload); err != nil { + errs <- types.UpdateError{ + Field: "publicUpload", + Error: err, + } + } + }() + go func() { + wg.Wait() + close(errs) + }() + return types.NewUpdateError(errs) +} + +// expireDate expects a well formatted date string, e.g. ‘YYYY-MM-DD’ +func (c *Client) ShareUpdateExpireDate(shareID int, expireDate string) error { + return c.baseShareUpdate(strconv.Itoa(shareID), "expireDate", expireDate) +} + +func (c *Client) ShareUpdatePublicUpload(shareID int, public bool) error { + return c.baseShareUpdate(strconv.Itoa(shareID), "publicUpload", strconv.FormatBool(public)) +} + +func (c *Client) ShareUpdatePassword(shareID int, password string) error { + return c.baseShareUpdate(strconv.Itoa(shareID), "password", password) +} + +func (c *Client) ShareUpdatePermissions(shareID int, permissions types.SharePermission) error { + return c.baseShareUpdate(strconv.Itoa(shareID), "permissions", strconv.Itoa(int(permissions))) +} + +func (c *Client) baseShareUpdate(shareID string, key string, value string) error { + ro := &req.RequestOptions{ + Data: map[string]string{key: value}, + } + _, err := c.baseRequest(routes.shares, shareID, "", ro, http.MethodPut) + return err +} diff --git a/types/errors.go b/types/errors.go index be24610..6677176 100644 --- a/types/errors.go +++ b/types/errors.go @@ -38,7 +38,7 @@ type UserUpdateError struct { func (e *UserUpdateError) Error() string { var errors []string for k, e := range e.Errors { - errors = append(errors, fmt.Sprintf("%s: %s", k, e.Error())) + errors = append(errors, fmt.Sprintf("%s: %v", k, e)) } return strings.Join(errors, ",") } diff --git a/types/responses.go b/types/responses.go index 93b0a2b..d456d5a 100644 --- a/types/responses.go +++ b/types/responses.go @@ -90,11 +90,21 @@ type CapabilitiesResponse struct { type MonitoringResponse struct { Ocs struct { - Meta struct { - Status string `json:"status"` - Statuscode int `json:"statuscode"` - Message string `json:"message"` - } `json:"meta"` + Meta Meta `json:"meta"` Data Monitoring `json:"data"` } `json:"ocs"` } + +type SharesListResponse struct { + Ocs struct { + Meta Meta `json:"meta"` + Data []Share `json:"data"` + } `json:"ocs"` +} + +type SharesResponse struct { + Ocs struct { + Meta Meta `json:"meta"` + Data Share `json:"data"` + } `json:"ocs"` +} diff --git a/types/shares.go b/types/shares.go new file mode 100644 index 0000000..7073e94 --- /dev/null +++ b/types/shares.go @@ -0,0 +1,53 @@ +package types + +type ShareType int +type SharePermission int + +const ( + UserShare ShareType = 0 + GroupShare ShareType = 1 + PublicLinkShare ShareType = 3 + FederatedCloudShare ShareType = 6 + + ReadPermission SharePermission = 1 + UpdatePermission SharePermission = 2 + CreatePermission SharePermission = 4 + DeletePermission SharePermission = 8 + ReSharePermission SharePermission = 16 + AllPermissions SharePermission = 31 +) + +type ShareUpdate struct { + ShareID int + Permissions SharePermission + Password string + PublicUpload bool + ExpireDate string +} + +type Share struct { + ID string `json:"id"` + ShareType int `json:"share_type"` + UIDOwner string `json:"uid_owner"` + DisplaynameOwner string `json:"displayname_owner"` + Permissions int `json:"permissions"` + Stime int `json:"stime"` + Parent interface{} `json:"parent"` + Expiration string `json:"expiration"` + Token string `json:"token"` + UIDFileOwner string `json:"uid_file_owner"` + DisplaynameFileOwner string `json:"displayname_file_owner"` + Path string `json:"path"` + ItemType string `json:"item_type"` + Mimetype string `json:"mimetype"` + StorageID string `json:"storage_id"` + Storage int `json:"storage"` + ItemSource int `json:"item_source"` + FileSource int `json:"file_source"` + FileParent int `json:"file_parent"` + FileTarget string `json:"file_target"` + ShareWith string `json:"share_with"` + ShareWithDisplayname string `json:"share_with_displayname"` + MailSend int `json:"mail_send"` + Tags []string `json:"tags"` +} diff --git a/utils.go b/utils.go index 2e6588a..9d87df2 100644 --- a/utils.go +++ b/utils.go @@ -41,7 +41,7 @@ func (c *Client) baseRequest(route *url.URL, name string, subroute string, ro *r js := res.String() var r types.BaseResponse json.Unmarshal([]byte(js), &r) - if r.Ocs.Meta.Statuscode != 100 { + if r.Ocs.Meta.Statuscode != 200 { err := types.ErrorFromMeta(r.Ocs.Meta) return nil, err }