Skip to content
This repository was archived by the owner on Dec 10, 2024. It is now read-only.

Commit 42949f8

Browse files
authored
Merge pull request #2058 from heidiberry/project-markdown-uploads
Add support for the project markdown uploads API
2 parents 8186bd9 + 24995ab commit 42949f8

File tree

3 files changed

+375
-0
lines changed

3 files changed

+375
-0
lines changed

gitlab.go

+2
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,7 @@ type Client struct {
196196
ProjectFeatureFlags *ProjectFeatureFlagService
197197
ProjectImportExport *ProjectImportExportService
198198
ProjectIterations *ProjectIterationsService
199+
ProjectMarkdownUploads *ProjectMarkdownUploadsService
199200
ProjectMembers *ProjectMembersService
200201
ProjectMirrors *ProjectMirrorService
201202
ProjectRepositoryStorageMove *ProjectRepositoryStorageMoveService
@@ -435,6 +436,7 @@ func newClient(options ...ClientOptionFunc) (*Client, error) {
435436
c.ProjectFeatureFlags = &ProjectFeatureFlagService{client: c}
436437
c.ProjectImportExport = &ProjectImportExportService{client: c}
437438
c.ProjectIterations = &ProjectIterationsService{client: c}
439+
c.ProjectMarkdownUploads = &ProjectMarkdownUploadsService{client: c}
438440
c.ProjectMembers = &ProjectMembersService{client: c}
439441
c.ProjectMirrors = &ProjectMirrorService{client: c}
440442
c.ProjectRepositoryStorageMove = &ProjectRepositoryStorageMoveService{client: c}

project_markdown_uploads.go

+210
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
//
2+
// Copyright 2024, Sander van Harmelen
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
//
16+
17+
package gitlab
18+
19+
import (
20+
"bytes"
21+
"fmt"
22+
"io"
23+
"net/http"
24+
"time"
25+
)
26+
27+
// ProjectMarkdownUploadsService handles communication with the project markdown uploads
28+
// related methods of the GitLab API.
29+
//
30+
// Gitlab API docs: https://docs.gitlab.com/ee/api/project_markdown_uploads.html
31+
type ProjectMarkdownUploadsService struct {
32+
client *Client
33+
}
34+
35+
// ProjectMarkdownUploadedFile represents a single project markdown uploaded file.
36+
//
37+
// Gitlab API docs: https://docs.gitlab.com/ee/api/project_markdown_uploads.html
38+
type ProjectMarkdownUploadedFile struct {
39+
ID int `json:"id"`
40+
Alt string `json:"alt"`
41+
URL string `json:"url"`
42+
FullPath string `json:"full_path"`
43+
Markdown string `json:"markdown"`
44+
}
45+
46+
// ProjectMarkdownUpload represents a single project markdown upload.
47+
//
48+
// Gitlab API docs: https://docs.gitlab.com/ee/api/project_markdown_uploads.html
49+
type ProjectMarkdownUpload struct {
50+
ID int `json:"id"`
51+
Size int `json:"size"`
52+
Filename string `json:"filename"`
53+
CreatedAt *time.Time `json:"created_at"`
54+
UploadedBy *User `json:"uploaded_by"`
55+
}
56+
57+
// Gets a string representation of a ProjectMarkdownUpload.
58+
//
59+
// GitLab API docs: https://docs.gitlab.com/ee/api/project_markdown_uploads.html
60+
func (m ProjectMarkdownUpload) String() string {
61+
return Stringify(m)
62+
}
63+
64+
// UploadProjectMarkdown uploads a markdown file to a project.
65+
//
66+
// GitLab docs:
67+
// https://docs.gitlab.com/ee/api/project_markdown_uploads.html#upload-a-file
68+
func (s *ProjectMarkdownUploadsService) UploadProjectMarkdown(pid interface{}, content io.Reader, options ...RequestOptionFunc) (*ProjectMarkdownUploadedFile, *Response, error) {
69+
project, err := parseID(pid)
70+
if err != nil {
71+
return nil, nil, err
72+
}
73+
u := fmt.Sprintf("projects/%s/uploads", PathEscape(project))
74+
75+
// We need to create the request as a GET request to make sure the options
76+
// are set correctly. After the request is created we will overwrite both
77+
// the method and the body.
78+
req, err := s.client.NewRequest(http.MethodPost, u, nil, options)
79+
if err != nil {
80+
return nil, nil, err
81+
}
82+
83+
// Overwrite the method and body.
84+
req.Method = http.MethodPost
85+
req.SetBody(content)
86+
87+
f := new(ProjectMarkdownUploadedFile)
88+
resp, err := s.client.Do(req, f)
89+
if err != nil {
90+
return nil, resp, err
91+
}
92+
93+
return f, resp, nil
94+
}
95+
96+
// ListProjectMarkdownUploads gets all markdown uploads for a project.
97+
//
98+
// GitLab API Docs:
99+
// https://docs.gitlab.com/ee/api/project_markdown_uploads.html#list-uploads
100+
func (s *ProjectMarkdownUploadsService) ListProjectMarkdownUploads(pid interface{}, options ...RequestOptionFunc) ([]*ProjectMarkdownUpload, *Response, error) {
101+
project, err := parseID(pid)
102+
if err != nil {
103+
return nil, nil, err
104+
}
105+
u := fmt.Sprintf("projects/%s/uploads", PathEscape(project))
106+
107+
req, err := s.client.NewRequest(http.MethodGet, u, nil, options)
108+
if err != nil {
109+
return nil, nil, err
110+
}
111+
112+
var uploads []*ProjectMarkdownUpload
113+
resp, err := s.client.Do(req, &uploads)
114+
if err != nil {
115+
return nil, resp, err
116+
}
117+
118+
return uploads, resp, err
119+
}
120+
121+
// DownloadProjectMarkdownUploadByID downloads a specific upload by ID.
122+
//
123+
// GitLab API Docs:
124+
// https://docs.gitlab.com/ee/api/project_markdown_uploads.html#download-an-uploaded-file-by-id
125+
func (s *ProjectMarkdownUploadsService) DownloadProjectMarkdownUploadByID(pid interface{}, uploadID int, options ...RequestOptionFunc) ([]byte, *Response, error) {
126+
project, err := parseID(pid)
127+
if err != nil {
128+
return nil, nil, err
129+
}
130+
u := fmt.Sprintf("projects/%s/uploads/%d", PathEscape(project), uploadID)
131+
132+
req, err := s.client.NewRequest(http.MethodGet, u, nil, options)
133+
if err != nil {
134+
return nil, nil, err
135+
}
136+
137+
var f bytes.Buffer
138+
resp, err := s.client.Do(req, &f)
139+
if err != nil {
140+
return nil, resp, err
141+
}
142+
143+
return f.Bytes(), resp, err
144+
}
145+
146+
// DownloadProjectMarkdownUploadBySecretAndFilename downloads a specific upload
147+
// by secret and filename.
148+
//
149+
// GitLab API Docs:
150+
// https://docs.gitlab.com/ee/api/project_markdown_uploads.html#download-an-uploaded-file-by-secret-and-filename
151+
func (s *ProjectMarkdownUploadsService) DownloadProjectMarkdownUploadBySecretAndFilename(pid interface{}, secret string, filename string, options ...RequestOptionFunc) ([]byte, *Response, error) {
152+
project, err := parseID(pid)
153+
if err != nil {
154+
return nil, nil, err
155+
}
156+
u := fmt.Sprintf("projects/%s/uploads/%s/%s", PathEscape(project), PathEscape(secret), PathEscape(filename))
157+
158+
req, err := s.client.NewRequest(http.MethodGet, u, nil, options)
159+
if err != nil {
160+
return nil, nil, err
161+
}
162+
163+
var f bytes.Buffer
164+
resp, err := s.client.Do(req, &f)
165+
if err != nil {
166+
return nil, resp, err
167+
}
168+
169+
return f.Bytes(), resp, err
170+
}
171+
172+
// DeleteProjectMarkdownUploadByID deletes an upload by ID.
173+
//
174+
// GitLab API Docs:
175+
// https://docs.gitlab.com/ee/api/project_markdown_uploads.html#delete-an-uploaded-file-by-id
176+
func (s *ProjectMarkdownUploadsService) DeleteProjectMarkdownUploadByID(pid interface{}, uploadID int, options ...RequestOptionFunc) (*Response, error) {
177+
project, err := parseID(pid)
178+
if err != nil {
179+
return nil, err
180+
}
181+
u := fmt.Sprintf("projects/%s/uploads/%d", PathEscape(project), uploadID)
182+
183+
req, err := s.client.NewRequest(http.MethodDelete, u, nil, options)
184+
if err != nil {
185+
return nil, err
186+
}
187+
188+
return s.client.Do(req, nil)
189+
}
190+
191+
// DeleteProjectMarkdownUploadBySecretAndFilename deletes an upload
192+
// by secret and filename.
193+
//
194+
// GitLab API Docs:
195+
// https://docs.gitlab.com/ee/api/project_markdown_uploads.html#delete-an-uploaded-file-by-secret-and-filename
196+
func (s *ProjectMarkdownUploadsService) DeleteProjectMarkdownUploadBySecretAndFilename(pid interface{}, secret string, filename string, options ...RequestOptionFunc) (*Response, error) {
197+
project, err := parseID(pid)
198+
if err != nil {
199+
return nil, err
200+
}
201+
u := fmt.Sprintf("projects/%s/uploads/%s/%s",
202+
PathEscape(project), PathEscape(secret), PathEscape(filename))
203+
204+
req, err := s.client.NewRequest(http.MethodDelete, u, nil, options)
205+
if err != nil {
206+
return nil, err
207+
}
208+
209+
return s.client.Do(req, nil)
210+
}

project_markdown_uploads_test.go

+163
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
package gitlab
2+
3+
import (
4+
"fmt"
5+
"net/http"
6+
"strings"
7+
"testing"
8+
"time"
9+
10+
"github.com/stretchr/testify/require"
11+
)
12+
13+
func TestProjectMarkdownUploads_UploadProjectMarkdown(t *testing.T) {
14+
mux, client := setup(t)
15+
16+
mux.HandleFunc("/api/v4/projects/1/uploads", func(w http.ResponseWriter, r *http.Request) {
17+
testMethod(t, r, http.MethodPost)
18+
fmt.Fprint(w, `
19+
{
20+
"id": 5,
21+
"alt": "dk",
22+
"url": "/uploads/66dbcd21ec5d24ed6ea225176098d52b/dk.png",
23+
"full_path": "/-/project/1234/uploads/66dbcd21ec5d24ed6ea225176098d52b/dk.png",
24+
"markdown": "![dk](/uploads/66dbcd21ec5d24ed6ea225176098d52b/dk.png)"
25+
}
26+
`)
27+
})
28+
29+
want := &ProjectMarkdownUploadedFile{
30+
ID: 5,
31+
Alt: "dk",
32+
URL: "/uploads/66dbcd21ec5d24ed6ea225176098d52b/dk.png",
33+
FullPath: "/-/project/1234/uploads/66dbcd21ec5d24ed6ea225176098d52b/dk.png",
34+
Markdown: "![dk](/uploads/66dbcd21ec5d24ed6ea225176098d52b/dk.png)",
35+
}
36+
37+
content := strings.NewReader("bar = baz")
38+
upload, resp, err := client.ProjectMarkdownUploads.UploadProjectMarkdown(1, content)
39+
require.NoError(t, err)
40+
require.NotNil(t, resp)
41+
require.Equal(t, want, upload)
42+
}
43+
44+
func TestProjectMarkdownUploads_ListProjectMarkdownUploads(t *testing.T) {
45+
mux, client := setup(t)
46+
47+
mux.HandleFunc("/api/v4/projects/1/uploads", func(w http.ResponseWriter, r *http.Request) {
48+
testMethod(t, r, http.MethodGet)
49+
fmt.Fprint(w, `
50+
[
51+
{
52+
"id": 1,
53+
"size": 1024,
54+
"filename": "image.png",
55+
"created_at":"2024-06-20T15:53:03.000Z",
56+
"uploaded_by": {
57+
"id": 18,
58+
"name" : "Alexandra Bashirian",
59+
"username" : "eileen.lowe"
60+
}
61+
},
62+
{
63+
"id": 2,
64+
"size": 512,
65+
"filename": "other-image.png",
66+
"created_at":"2024-06-19T15:53:03.000Z",
67+
"uploaded_by": null
68+
}
69+
]
70+
`)
71+
})
72+
73+
created1 := time.Date(2024, 6, 20, 15, 53, 3, 0, time.UTC)
74+
created2 := time.Date(2024, 6, 19, 15, 53, 3, 0, time.UTC)
75+
want := []*ProjectMarkdownUpload{
76+
{
77+
ID: 1,
78+
Size: 1024,
79+
Filename: "image.png",
80+
CreatedAt: &created1,
81+
UploadedBy: &User{
82+
ID: 18,
83+
Name: "Alexandra Bashirian",
84+
Username: "eileen.lowe",
85+
},
86+
},
87+
{
88+
ID: 2,
89+
Size: 512,
90+
Filename: "other-image.png",
91+
CreatedAt: &created2,
92+
},
93+
}
94+
95+
uploads, resp, err := client.ProjectMarkdownUploads.ListProjectMarkdownUploads(1)
96+
require.NoError(t, err)
97+
require.NotNil(t, resp)
98+
require.Equal(t, want, uploads)
99+
}
100+
101+
func TestProjectMarkdownUploads_DownloadProjectMarkdownUploadByID(t *testing.T) {
102+
mux, client := setup(t)
103+
104+
mux.HandleFunc("/api/v4/projects/1/uploads/2", func(w http.ResponseWriter, r *http.Request) {
105+
testMethod(t, r, http.MethodGet)
106+
fmt.Fprint(w, strings.TrimSpace(`
107+
bar = baz
108+
`))
109+
})
110+
111+
want := []byte("bar = baz")
112+
113+
bytes, resp, err := client.ProjectMarkdownUploads.DownloadProjectMarkdownUploadByID(1, 2)
114+
require.NoError(t, err)
115+
require.NotNil(t, resp)
116+
require.Equal(t, want, bytes)
117+
}
118+
119+
func TestProjectMarkdownUploads_DownloadProjectMarkdownUploadBySecretAndFilename(t *testing.T) {
120+
mux, client := setup(t)
121+
122+
mux.HandleFunc("/api/v4/projects/1/uploads/secret/filename", func(w http.ResponseWriter, r *http.Request) {
123+
testMethod(t, r, http.MethodGet)
124+
fmt.Fprint(w, strings.TrimSpace(`
125+
bar = baz
126+
`))
127+
})
128+
129+
want := []byte("bar = baz")
130+
131+
bytes, resp, err := client.ProjectMarkdownUploads.DownloadProjectMarkdownUploadBySecretAndFilename(1, "secret", "filename")
132+
require.NoError(t, err)
133+
require.NotNil(t, resp)
134+
require.Equal(t, want, bytes)
135+
}
136+
137+
func TestProjectMarkdownUploads_DeleteProjectMarkdownUploadByID(t *testing.T) {
138+
mux, client := setup(t)
139+
140+
mux.HandleFunc("/api/v4/projects/1/uploads/2", func(w http.ResponseWriter, r *http.Request) {
141+
testMethod(t, r, http.MethodDelete)
142+
w.WriteHeader(204)
143+
})
144+
145+
resp, err := client.ProjectMarkdownUploads.DeleteProjectMarkdownUploadByID(1, 2)
146+
require.NoError(t, err)
147+
require.NotNil(t, resp)
148+
require.Equal(t, 204, resp.StatusCode)
149+
}
150+
151+
func TestProjectMarkdownUploads_DeleteProjectMarkdownUploadBySecretAndFilename(t *testing.T) {
152+
mux, client := setup(t)
153+
154+
mux.HandleFunc("/api/v4/projects/1/uploads/secret/filename", func(w http.ResponseWriter, r *http.Request) {
155+
testMethod(t, r, http.MethodDelete)
156+
w.WriteHeader(204)
157+
})
158+
159+
resp, err := client.ProjectMarkdownUploads.DeleteProjectMarkdownUploadBySecretAndFilename(1, "secret", "filename")
160+
require.NoError(t, err)
161+
require.NotNil(t, resp)
162+
require.Equal(t, 204, resp.StatusCode)
163+
}

0 commit comments

Comments
 (0)