From cf40536870383a522f09a3f4ab521fb39b6c37d0 Mon Sep 17 00:00:00 2001 From: Timo Furrer Date: Fri, 15 Jul 2022 15:17:07 +0200 Subject: [PATCH 1/2] Fix HTTP method for EditProject with avatar --- projects.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects.go b/projects.go index d9c83d252..1aadf902b 100644 --- a/projects.go +++ b/projects.go @@ -897,7 +897,7 @@ func (s *ProjectsService) EditProject(pid interface{}, opt *EditProjectOptions, req, err = s.client.NewRequest(http.MethodPut, u, opt, options) } else { req, err = s.client.UploadRequest( - http.MethodPost, + http.MethodPut, u, opt.Avatar.Image, opt.Avatar.Filename, From 02292fff2c19f9b27b4cb982b8c4d0dae3a72f2d Mon Sep 17 00:00:00 2001 From: Timo Furrer Date: Sat, 16 Jul 2022 11:08:28 +0200 Subject: [PATCH 2/2] Fully support project and group avatars This change set adds support for project and group avatars in the same fashion this is already implemented for topics. --- groups.go | 47 +++++++++++++++++++++++++++++++++++++++++++++-- projects.go | 14 ++++++++++++-- 2 files changed, 57 insertions(+), 4 deletions(-) diff --git a/groups.go b/groups.go index 4b7713912..ca855755f 100644 --- a/groups.go +++ b/groups.go @@ -18,10 +18,13 @@ package gitlab import ( "bytes" + "encoding/json" "fmt" "io" "net/http" "time" + + retryablehttp "github.com/hashicorp/go-retryablehttp" ) // GroupsService handles communication with the group related methods of @@ -90,6 +93,15 @@ type GroupAvatar struct { Image io.Reader } +// MarshalJSON implements the json.Marshaler interface. +func (a *GroupAvatar) MarshalJSON() ([]byte, error) { + if a.Filename == "" && a.Image == nil { + return []byte(`""`), nil + } + type alias GroupAvatar + return json.Marshal((*alias)(a)) +} + // LDAPGroupLink represents a GitLab LDAP group link. // // GitLab API docs: https://docs.gitlab.com/ce/api/groups.html#ldap-group-links @@ -312,6 +324,7 @@ func (s *GroupsService) DownloadAvatar(gid interface{}, options ...RequestOption type CreateGroupOptions struct { Name *string `url:"name,omitempty" json:"name,omitempty"` Path *string `url:"path,omitempty" json:"path,omitempty"` + Avatar *GroupAvatar `url:"-" json:"-"` Description *string `url:"description,omitempty" json:"description,omitempty"` MembershipLock *bool `url:"membership_lock,omitempty" json:"membership_lock,omitempty"` Visibility *VisibilityValue `url:"visibility,omitempty" json:"visibility,omitempty"` @@ -336,7 +349,22 @@ type CreateGroupOptions struct { // // GitLab API docs: https://docs.gitlab.com/ce/api/groups.html#new-group func (s *GroupsService) CreateGroup(opt *CreateGroupOptions, options ...RequestOptionFunc) (*Group, *Response, error) { - req, err := s.client.NewRequest(http.MethodPost, "groups", opt, options) + var err error + var req *retryablehttp.Request + + if opt.Avatar == nil { + req, err = s.client.NewRequest(http.MethodPost, "groups", opt, options) + } else { + req, err = s.client.UploadRequest( + http.MethodPost, + "groups", + opt.Avatar.Image, + opt.Avatar.Filename, + UploadAvatar, + opt, + options, + ) + } if err != nil { return nil, nil, err } @@ -420,6 +448,7 @@ func (s *GroupsService) TransferSubGroup(gid interface{}, opt *TransferSubGroupO type UpdateGroupOptions struct { Name *string `url:"name,omitempty" json:"name,omitempty"` Path *string `url:"path,omitempty" json:"path,omitempty"` + Avatar *GroupAvatar `url:"-" json:"avatar,omitempty"` Description *string `url:"description,omitempty" json:"description,omitempty"` MembershipLock *bool `url:"membership_lock,omitempty" json:"membership_lock,omitempty"` Visibility *VisibilityValue `url:"visibility,omitempty" json:"visibility,omitempty"` @@ -453,7 +482,21 @@ func (s *GroupsService) UpdateGroup(gid interface{}, opt *UpdateGroupOptions, op } u := fmt.Sprintf("groups/%s", PathEscape(group)) - req, err := s.client.NewRequest(http.MethodPut, u, opt, options) + var req *retryablehttp.Request + + if opt.Avatar == nil || (opt.Avatar.Filename == "" && opt.Avatar.Image == nil) { + req, err = s.client.NewRequest(http.MethodPut, u, opt, options) + } else { + req, err = s.client.UploadRequest( + http.MethodPut, + u, + opt.Avatar.Image, + opt.Avatar.Filename, + UploadAvatar, + opt, + options, + ) + } if err != nil { return nil, nil, err } diff --git a/projects.go b/projects.go index 1aadf902b..1bef75f98 100644 --- a/projects.go +++ b/projects.go @@ -17,6 +17,7 @@ package gitlab import ( + "encoding/json" "fmt" "io" "net/http" @@ -704,6 +705,15 @@ type ProjectAvatar struct { Image io.Reader } +// MarshalJSON implements the json.Marshaler interface. +func (a *ProjectAvatar) MarshalJSON() ([]byte, error) { + if a.Filename == "" && a.Image == nil { + return []byte(`""`), nil + } + type alias ProjectAvatar + return json.Marshal((*alias)(a)) +} + // CreateProject creates a new project owned by the authenticated user. // // GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#create-project @@ -805,7 +815,7 @@ type EditProjectOptions struct { AutoDevopsDeployStrategy *string `url:"auto_devops_deploy_strategy,omitempty" json:"auto_devops_deploy_strategy,omitempty"` AutoDevopsEnabled *bool `url:"auto_devops_enabled,omitempty" json:"auto_devops_enabled,omitempty"` AutocloseReferencedIssues *bool `url:"autoclose_referenced_issues,omitempty" json:"autoclose_referenced_issues,omitempty"` - Avatar *ProjectAvatar `url:"-" json:"-"` + Avatar *ProjectAvatar `url:"-" json:"avatar,omitempty"` BuildCoverageRegex *string `url:"build_coverage_regex,omitempty" json:"build_coverage_regex,omitempty"` BuildGitStrategy *string `url:"build_git_strategy,omitempty" json:"build_git_strategy,omitempty"` BuildTimeout *int `url:"build_timeout,omitempty" json:"build_timeout,omitempty"` @@ -893,7 +903,7 @@ func (s *ProjectsService) EditProject(pid interface{}, opt *EditProjectOptions, var req *retryablehttp.Request - if opt.Avatar == nil { + if opt.Avatar == nil || (opt.Avatar.Filename == "" && opt.Avatar.Image == nil) { req, err = s.client.NewRequest(http.MethodPut, u, opt, options) } else { req, err = s.client.UploadRequest(