From 7c306ed92214e35538730eb2106c277c53cc3fba Mon Sep 17 00:00:00 2001 From: Timo Furrer Date: Mon, 14 Feb 2022 16:12:45 +0100 Subject: [PATCH 1/2] Support `avatar` field in create and update method of the topic service --- examples/topics.go | 69 ++++++++++++++++++++++++++++++++++++++++++++++ topics.go | 66 ++++++++++++++++++++++++++++++++++++++++---- topics_test.go | 12 ++++---- 3 files changed, 135 insertions(+), 12 deletions(-) create mode 100644 examples/topics.go diff --git a/examples/topics.go b/examples/topics.go new file mode 100644 index 000000000..dd23989d8 --- /dev/null +++ b/examples/topics.go @@ -0,0 +1,69 @@ +// +// Copyright 2021, Timo Furrer +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package main + +import ( + "fmt" + "log" + "os" + + "github.com/xanzy/go-gitlab" +) + +func topicExample() { + git, err := gitlab.NewClient("yourtokengoeshere") + if err != nil { + log.Fatal(err) + } + + // New topic + topic, _, err := git.Topics.CreateTopic(&gitlab.CreateTopicOptions{ + Name: gitlab.String("My Topic 2"), + Description: gitlab.String("Some description"), + }) + if err != nil { + panic(err) + } + + fmt.Printf("Topic: %+v\n", topic) + + // Set topic avatar + avatarFile, err := os.Open("5746961_detect_direction_gps_location_map_icon.png") + if err != nil { + panic(err) + } + topic, _, err = git.Topics.UpdateTopic(topic.ID, &gitlab.UpdateTopicOptions{ + Avatar: &gitlab.TopicAvatar{ + Filename: gitlab.String("5746961_detect_direction_gps_location_map_icon.png"), + Image: avatarFile, + }, + }) + if err != nil { + panic(err) + } + fmt.Printf("Topic with Avatar: %+v\n", topic) + + // Remove topic avatar + topic, _, err = git.Topics.UpdateTopic(topic.ID, &gitlab.UpdateTopicOptions{ + Avatar: &gitlab.TopicAvatar{}, + }) + if err != nil { + panic(err) + } + + fmt.Printf("Topic without Avatar: %+v\n", topic) +} diff --git a/topics.go b/topics.go index 04f4473a0..db8b07a71 100644 --- a/topics.go +++ b/topics.go @@ -18,7 +18,10 @@ package gitlab import ( "fmt" + "io" "net/http" + + retryablehttp "github.com/hashicorp/go-retryablehttp" ) // TopicsService handles communication with the topics related methods @@ -91,14 +94,20 @@ func (s *TopicsService) GetTopic(topic int, options ...RequestOptionFunc) (*Topi return t, resp, err } +// TopicAvatar represents a GitLab topic avatar. +type TopicAvatar struct { + Filename *string + Image io.Reader +} + // CreateTopicOptions represents the available CreateTopic() options. // // GitLab API docs: // https://docs.gitlab.com/ee/api/topics.html#create-a-project-topic type CreateTopicOptions struct { - Name *string `url:"name,omitempty" json:"name,omitempty"` - Description *string `url:"description,omitempty" json:"description,omitempty"` - // Avatar *string `url:"avatar,omitempty" json:"avatar,omitempty"` + Name *string `url:"name,omitempty" json:"name,omitempty"` + Description *string `url:"description,omitempty" json:"description,omitempty"` + Avatar *TopicAvatar `url:"-" json:"-"` } // CreateTopic creates a new project topic. @@ -106,7 +115,22 @@ type CreateTopicOptions struct { // GitLab API docs: // https://docs.gitlab.com/ee/api/topics.html#create-a-project-topic func (s *TopicsService) CreateTopic(opt *CreateTopicOptions, options ...RequestOptionFunc) (*Topic, *Response, error) { - req, err := s.client.NewRequest(http.MethodPost, "topics", opt, options) + var err error + var req *retryablehttp.Request + + if opt.Avatar == nil { + req, err = s.client.NewRequest(http.MethodPost, "topics", opt, options) + } else { + req, err = s.client.UploadRequest( + http.MethodPost, + "topics", + opt.Avatar.Image, + *opt.Avatar.Filename, + UploadAvatar, + opt, + options, + ) + } if err != nil { return nil, nil, err } @@ -125,19 +149,49 @@ func (s *TopicsService) CreateTopic(opt *CreateTopicOptions, options ...RequestO // GitLab API docs: // https://docs.gitlab.com/ee/api/topics.html#update-a-project-topic type UpdateTopicOptions struct { + Name *string `url:"name,omitempty" json:"name,omitempty"` + Description *string `url:"description,omitempty" json:"description,omitempty"` + Avatar *TopicAvatar `url:"-" json:"-"` +} + +type RemoveAvatarUpdateTopicOptions struct { Name *string `url:"name,omitempty" json:"name,omitempty"` Description *string `url:"description,omitempty" json:"description,omitempty"` - // Avatar *string `url:"avatar,omitempty" json:"avatar,omitempty"` + Avatar *string `url:"avatar,omitempty" json:"avatar,omitempty"` } // UpdateTopic updates a project topic. Only available to administrators. // +// To remove a topic avatar (see https://docs.gitlab.com/ee/api/topics.html#remove-a-topic-avatar), +// set the TopicAvatar.Filename to an empty string and set TopicAvatar.Image to nil. +// // GitLab API docs: // https://docs.gitlab.com/ee/api/topics.html#update-a-project-topic func (s *TopicsService) UpdateTopic(topic int, opt *UpdateTopicOptions, options ...RequestOptionFunc) (*Topic, *Response, error) { u := fmt.Sprintf("topics/%d", topic) - req, err := s.client.NewRequest(http.MethodPut, u, opt, options) + var err error + var req *retryablehttp.Request + + if opt.Avatar == nil { + req, err = s.client.NewRequest(http.MethodPut, u, opt, options) + } else if (TopicAvatar{}) == *opt.Avatar { + req, err = s.client.NewRequest(http.MethodPut, u, &RemoveAvatarUpdateTopicOptions{ + Name: opt.Name, + Description: opt.Description, + Avatar: String(""), + }, 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/topics_test.go b/topics_test.go index 69126e32b..b905afbd9 100644 --- a/topics_test.go +++ b/topics_test.go @@ -123,19 +123,19 @@ func TestTopicsService_CreateTopic(t *testing.T) { fmt.Fprint(w, `{ "id": 1, "name": "topic1", - "description": null, + "description": "description", "total_projects_count": 0, "avatar_url": null }`) }) - opt := &CreateTopicOptions{Name: String("topic1")} + opt := &CreateTopicOptions{Name: String("topic1"), Description: String("description")} release, _, err := client.Topics.CreateTopic(opt) if err != nil { t.Errorf("Topics.CreateTopic returned error: %v", err) } - want := &Topic{ID: 1, Name: "topic1"} + want := &Topic{ID: 1, Name: "topic1", Description: "description", TotalProjectsCount: 0} if !reflect.DeepEqual(want, release) { t.Errorf("Topics.CreateTopic returned %+v, want %+v", release, want) } @@ -150,19 +150,19 @@ func TestTopicsService_UpdateTopic(t *testing.T) { fmt.Fprint(w, `{ "id": 1, "name": "topic1", - "description": null, + "description": "description", "total_projects_count": 0, "avatar_url": null }`) }) - opt := &UpdateTopicOptions{Name: String("topic1")} + opt := &UpdateTopicOptions{Name: String("topic1"), Description: String("description")} release, _, err := client.Topics.UpdateTopic(1, opt) if err != nil { t.Errorf("Topics.UpdateTopic returned error: %v", err) } - want := &Topic{ID: 1, Name: "topic1"} + want := &Topic{ID: 1, Name: "topic1", Description: "description", TotalProjectsCount: 0} if !reflect.DeepEqual(want, release) { t.Errorf("Topics.UpdateTopic returned %+v, want %+v", release, want) } From 9c271143b337c94d5ae96c8f64e7d45d3f11c8a2 Mon Sep 17 00:00:00 2001 From: Sander van Harmelen Date: Mon, 7 Mar 2022 12:09:19 +0100 Subject: [PATCH 2/2] Improve the solution a little --- examples/topics.go | 2 +- topics.go | 46 ++++++++++++++++++++++------------------------ 2 files changed, 23 insertions(+), 25 deletions(-) diff --git a/examples/topics.go b/examples/topics.go index dd23989d8..ef4f09d0a 100644 --- a/examples/topics.go +++ b/examples/topics.go @@ -48,7 +48,7 @@ func topicExample() { } topic, _, err = git.Topics.UpdateTopic(topic.ID, &gitlab.UpdateTopicOptions{ Avatar: &gitlab.TopicAvatar{ - Filename: gitlab.String("5746961_detect_direction_gps_location_map_icon.png"), + Filename: "5746961_detect_direction_gps_location_map_icon.png", Image: avatarFile, }, }) diff --git a/topics.go b/topics.go index db8b07a71..c21d776c5 100644 --- a/topics.go +++ b/topics.go @@ -17,6 +17,7 @@ package gitlab import ( + "encoding/json" "fmt" "io" "net/http" @@ -94,12 +95,6 @@ func (s *TopicsService) GetTopic(topic int, options ...RequestOptionFunc) (*Topi return t, resp, err } -// TopicAvatar represents a GitLab topic avatar. -type TopicAvatar struct { - Filename *string - Image io.Reader -} - // CreateTopicOptions represents the available CreateTopic() options. // // GitLab API docs: @@ -110,6 +105,21 @@ type CreateTopicOptions struct { Avatar *TopicAvatar `url:"-" json:"-"` } +// TopicAvatar represents a GitLab topic avatar. +type TopicAvatar struct { + Filename string + Image io.Reader +} + +// MarshalJSON implements the json.Marshaler interface. +func (a *TopicAvatar) MarshalJSON() ([]byte, error) { + if a.Filename == "" && a.Image == nil { + return []byte(`""`), nil + } + type alias TopicAvatar + return json.Marshal((*alias)(a)) +} + // CreateTopic creates a new project topic. // // GitLab API docs: @@ -125,7 +135,7 @@ func (s *TopicsService) CreateTopic(opt *CreateTopicOptions, options ...RequestO http.MethodPost, "topics", opt.Avatar.Image, - *opt.Avatar.Filename, + opt.Avatar.Filename, UploadAvatar, opt, options, @@ -151,19 +161,13 @@ func (s *TopicsService) CreateTopic(opt *CreateTopicOptions, options ...RequestO type UpdateTopicOptions struct { Name *string `url:"name,omitempty" json:"name,omitempty"` Description *string `url:"description,omitempty" json:"description,omitempty"` - Avatar *TopicAvatar `url:"-" json:"-"` -} - -type RemoveAvatarUpdateTopicOptions struct { - Name *string `url:"name,omitempty" json:"name,omitempty"` - Description *string `url:"description,omitempty" json:"description,omitempty"` - Avatar *string `url:"avatar,omitempty" json:"avatar,omitempty"` + Avatar *TopicAvatar `url:"-" json:"avatar,omitempty"` } // UpdateTopic updates a project topic. Only available to administrators. // -// To remove a topic avatar (see https://docs.gitlab.com/ee/api/topics.html#remove-a-topic-avatar), -// set the TopicAvatar.Filename to an empty string and set TopicAvatar.Image to nil. +// To remove a topic avatar set the TopicAvatar.Filename to an empty string +// and set TopicAvatar.Image to nil. // // GitLab API docs: // https://docs.gitlab.com/ee/api/topics.html#update-a-project-topic @@ -173,20 +177,14 @@ func (s *TopicsService) UpdateTopic(topic int, opt *UpdateTopicOptions, options var err error 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 if (TopicAvatar{}) == *opt.Avatar { - req, err = s.client.NewRequest(http.MethodPut, u, &RemoveAvatarUpdateTopicOptions{ - Name: opt.Name, - Description: opt.Description, - Avatar: String(""), - }, options) } else { req, err = s.client.UploadRequest( http.MethodPut, u, opt.Avatar.Image, - *opt.Avatar.Filename, + opt.Avatar.Filename, UploadAvatar, opt, options,