Skip to content

Commit

Permalink
Merge pull request #1335 from LewisYearsley/image-upload-by-url
Browse files Browse the repository at this point in the history
Add image upload by url
  • Loading branch information
jacobbednarz authored Jul 18, 2023
2 parents b8dab62 + 5a38e52 commit ce65e08
Show file tree
Hide file tree
Showing 3 changed files with 121 additions and 21 deletions.
3 changes: 3 additions & 0 deletions .changelog/1335.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:enhancement
images: adds ability to upload image by url
```
41 changes: 28 additions & 13 deletions images.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ type Image struct {
// UploadImageParams is the data required for an Image Upload request.
type UploadImageParams struct {
File io.ReadCloser
URL string
Name string
RequireSignedURLs bool
Metadata map[string]interface{}
Expand All @@ -45,33 +46,43 @@ type UploadImageParams struct {
// write writes the image upload data to a multipart writer, so
// it can be used in an HTTP request.
func (b UploadImageParams) write(mpw *multipart.Writer) error {
if b.File == nil {
return errors.New("a file to upload must be specified")
if b.File == nil && b.URL == "" {
return errors.New("a file or url to upload must be specified")
}
name := b.Name
part, err := mpw.CreateFormFile("file", name)
if err != nil {
return err
}
_, err = io.Copy(part, b.File)
if err != nil {

if b.File != nil {
name := b.Name
part, err := mpw.CreateFormFile("file", name)
if err != nil {
return err
}
_, err = io.Copy(part, b.File)
if err != nil {
_ = b.File.Close()
return err
}
_ = b.File.Close()
return err
}
_ = b.File.Close()

if b.URL != "" {
err := mpw.WriteField("url", b.URL)
if err != nil {
return err
}
}

// According to the Cloudflare docs, this field defaults to false.
// For simplicity, we will only send it if the value is true, however
// if the default is changed to true, this logic will need to be updated.
if b.RequireSignedURLs {
err = mpw.WriteField("requireSignedURLs", "true")
err := mpw.WriteField("requireSignedURLs", "true")
if err != nil {
return err
}
}

if b.Metadata != nil {
part, err = mpw.CreateFormField("metadata")
part, err := mpw.CreateFormField("metadata")
if err != nil {
return err
}
Expand Down Expand Up @@ -151,6 +162,10 @@ func (api *API) UploadImage(ctx context.Context, rc *ResourceContainer, params U
return Image{}, ErrRequiredAccountLevelResourceContainer
}

if params.File != nil && params.URL != "" {
return Image{}, errors.New("file and url uploads are mutually exclusive and can only be performed individually")
}

uri := fmt.Sprintf("/accounts/%s/images/v1", rc.Identifier)

body := &bytes.Buffer{}
Expand Down
98 changes: 90 additions & 8 deletions images_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,61 @@ func TestUploadImage(t *testing.T) {
}
}

func TestUploadImageByUrl(t *testing.T) {
setup()
defer teardown()

handler := func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, http.MethodPost, r.Method, "Expected method 'POST', got %s", r.Method)

u, err := parseImageMultipartUpload(r)
if !assert.NoError(t, err) {
w.WriteHeader(http.StatusBadRequest)
return
}
assert.Equal(t, u.RequireSignedURLs, true)
assert.Equal(t, u.Metadata, map[string]interface{}{"meta": "metaID"})
assert.Equal(t, u.Url, "https://www.images-elsewhere.com/avatar.png")

w.Header().Set("content-type", "application/json")
fmt.Fprintf(w, `{
"success": true,
"errors": [],
"messages": [],
"result": {
"id": "ZxR0pLaXRldlBtaFhhO2FiZGVnaA",
"filename": "avatar.png",
"metadata": {
"meta": "metaID"
},
"requireSignedURLs": true,
"variants": [
"https://imagedelivery.net/MTt4OTd0b0w5aj/ZxR0pLaXRldlBtaFhhO2FiZGVnaA/hero",
"https://imagedelivery.net/MTt4OTd0b0w5aj/ZxR0pLaXRldlBtaFhhO2FiZGVnaA/original",
"https://imagedelivery.net/MTt4OTd0b0w5aj/ZxR0pLaXRldlBtaFhhO2FiZGVnaA/thumbnail"
],
"uploaded": "2014-01-02T02:20:00Z"
}
}
`)
}

mux.HandleFunc("/accounts/"+testAccountID+"/images/v1", handler)
want := expectedImageStruct

actual, err := client.UploadImage(context.Background(), AccountIdentifier(testAccountID), UploadImageParams{
URL: "https://www.images-elsewhere.com/avatar.png",
RequireSignedURLs: true,
Metadata: map[string]interface{}{
"meta": "metaID",
},
})

if assert.NoError(t, err) {
assert.Equal(t, want, actual)
}
}

func TestUpdateImage(t *testing.T) {
setup()
defer teardown()
Expand Down Expand Up @@ -197,6 +252,20 @@ func TestCreateImageDirectUploadURL(t *testing.T) {
}
}

func TestCreateImageConflictingTypes(t *testing.T) {
setup()
defer teardown()

_, err := client.UploadImage(context.Background(), AccountIdentifier(testAccountID), UploadImageParams{
URL: "https://example.com/foo.jpg",
File: fakeFile{
Buffer: bytes.NewBufferString("this is definitely an image"),
},
})

assert.Error(t, err)
}

func TestCreateImageDirectUploadURLV2(t *testing.T) {
setup()
defer teardown()
Expand Down Expand Up @@ -387,6 +456,7 @@ type imageMultipartUpload struct {
// this is for testing, never read an entire file into memory,
// especially when being done on a per-http request basis.
File []byte
Url string
RequireSignedURLs bool
Metadata map[string]interface{}
}
Expand Down Expand Up @@ -418,15 +488,27 @@ func parseImageMultipartUpload(r *http.Request) (imageMultipartUpload, error) {
}
}

f, _, err := r.FormFile("file")
if err != nil {
return u, err
}
defer f.Close()
if _, ok := r.MultipartForm.Value["url"]; ok {
urlBytes, err := getImageFormValue(r, "url")
if err != nil {
if !strings.HasPrefix(err.Error(), "no value found for key") {
return u, err
}
}
if urlBytes != nil {
u.Url = string(urlBytes)
}
} else {
f, _, err := r.FormFile("file")
if err != nil {
return u, err
}
defer f.Close()

u.File, err = io.ReadAll(f)
if err != nil {
return u, err
u.File, err = io.ReadAll(f)
if err != nil {
return u, err
}
}

return u, nil
Expand Down

0 comments on commit ce65e08

Please sign in to comment.