-
Notifications
You must be signed in to change notification settings - Fork 1.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Added new FileUploadV2 function to avoid server side file timeouts #1130
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -2,6 +2,7 @@ package slack | |||||
|
||||||
import ( | ||||||
"context" | ||||||
"encoding/json" | ||||||
"fmt" | ||||||
"io" | ||||||
"net/url" | ||||||
|
@@ -145,6 +146,44 @@ type ListFilesParameters struct { | |||||
Cursor string | ||||||
} | ||||||
|
||||||
type FileUploadV2Parameters struct { | ||||||
File string | ||||||
FileSize int | ||||||
Content string | ||||||
Reader io.Reader | ||||||
Filename string | ||||||
Title string | ||||||
InitialComment string | ||||||
Channel string | ||||||
ThreadTimestamp string | ||||||
AltTxt string | ||||||
SnippetText string | ||||||
} | ||||||
|
||||||
type getUploadURLExternalResponse struct { | ||||||
UploadURL string `json:"upload_url"` | ||||||
FileID string `json:"file_id"` | ||||||
SlackResponse | ||||||
} | ||||||
|
||||||
type uploadToExternalParams struct { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||||||
UploadURL string | ||||||
Reader io.Reader | ||||||
File string | ||||||
Content string | ||||||
Filename string | ||||||
} | ||||||
|
||||||
type FileSummary struct { | ||||||
ID string `json:"id"` | ||||||
Title string `json:"title"` | ||||||
} | ||||||
|
||||||
type completeUploadExternalResponse struct { | ||||||
SlackResponse | ||||||
Files []FileSummary `json:"files"` | ||||||
} | ||||||
|
||||||
type fileResponseFull struct { | ||||||
File `json:"file"` | ||||||
Paging `json:"paging"` | ||||||
|
@@ -416,3 +455,119 @@ func (api *Client) ShareFilePublicURLContext(ctx context.Context, fileID string) | |||||
} | ||||||
return &response.File, response.Comments, &response.Paging, nil | ||||||
} | ||||||
|
||||||
// getUploadURLExternal gets a URL and fileID from slack which can later be used to upload a file | ||||||
func (api *Client) getUploadURLExternal(ctx context.Context, fileSize int, fileName, altText, snippetText string) (*getUploadURLExternalResponse, error) { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How about defining struct since there are many parameters? |
||||||
values := url.Values{ | ||||||
"token": {api.token}, | ||||||
"filename": {fileName}, | ||||||
"length": {strconv.Itoa(fileSize)}, | ||||||
} | ||||||
if altText != "" { | ||||||
values.Add("initial_comment", altText) | ||||||
} | ||||||
if snippetText != "" { | ||||||
values.Add("thread_ts", snippetText) | ||||||
} | ||||||
response := &getUploadURLExternalResponse{} | ||||||
err := api.postMethod(ctx, "files.getUploadURLExternal", values, response) | ||||||
if err != nil { | ||||||
return nil, err | ||||||
} | ||||||
|
||||||
return response, response.Err() | ||||||
} | ||||||
|
||||||
// uploadToURL uploads the file to the provided URL using post method | ||||||
func (api *Client) uploadToURL(ctx context.Context, params uploadToExternalParams) (err error) { | ||||||
values := url.Values{} | ||||||
if params.Content != "" { | ||||||
values.Add("content", params.Content) | ||||||
values.Add("token", api.token) | ||||||
err = postForm(ctx, api.httpclient, params.UploadURL, values, nil, api) | ||||||
} else if params.File != "" { | ||||||
err = postLocalWithMultipartResponse(ctx, api.httpclient, params.UploadURL, params.File, "file", api.token, values, nil, api) | ||||||
} else if params.Reader != nil { | ||||||
err = postWithMultipartResponse(ctx, api.httpclient, params.UploadURL, params.Filename, "file", api.token, values, params.Reader, nil, api) | ||||||
} | ||||||
return err | ||||||
} | ||||||
|
||||||
// completeUploadExternal once files are uploaded, this completes the upload and shares it to the specified channel | ||||||
func (api *Client) completeUploadExternal(ctx context.Context, fileID string, params FileUploadV2Parameters) (file *completeUploadExternalResponse, err error) { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is preferable to define struct instead of using |
||||||
request := []FileSummary{{ID: fileID, Title: params.Title}} | ||||||
requestBytes, err := json.Marshal(request) | ||||||
if err != nil { | ||||||
return nil, err | ||||||
} | ||||||
values := url.Values{ | ||||||
"token": {api.token}, | ||||||
"files": {string(requestBytes)}, | ||||||
"channel_id": {params.Channel}, | ||||||
} | ||||||
|
||||||
if params.InitialComment != "" { | ||||||
values.Add("initial_comment", params.InitialComment) | ||||||
} | ||||||
if params.ThreadTimestamp != "" { | ||||||
values.Add("thread_ts", params.ThreadTimestamp) | ||||||
} | ||||||
response := &completeUploadExternalResponse{} | ||||||
err = api.postMethod(ctx, "files.completeUploadExternal", values, response) | ||||||
if err != nil { | ||||||
return nil, err | ||||||
} | ||||||
if response.Err() != nil { | ||||||
return nil, response.Err() | ||||||
} | ||||||
return response, nil | ||||||
} | ||||||
|
||||||
// UploadFileV2 uploads file to a given slack channel using 3 steps - | ||||||
// 1. Get an upload URL using files.getUploadURLExternal API | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
// 2. Send the file as a post to the URL provided by slack | ||||||
// 3. Complete the upload and share it to the specified channel using files.completeUploadExternal | ||||||
func (api *Client) UploadFileV2(params FileUploadV2Parameters) (*FileSummary, error) { | ||||||
return api.UploadFileV2Context(context.Background(), params) | ||||||
} | ||||||
|
||||||
// UploadFileV2 uploads file to a given slack channel using 3 steps with a custom context - | ||||||
// 1. Get an upload URL using files.getUploadURLExternal API | ||||||
// 2. Send the file as a post to the URL provided by slack | ||||||
// 3. Complete the upload and share it to the specified channel using files.completeUploadExternal | ||||||
func (api *Client) UploadFileV2Context(ctx context.Context, params FileUploadV2Parameters) (file *FileSummary, err error) { | ||||||
if params.Filename == "" { | ||||||
return nil, fmt.Errorf("file.upload.v2: filename cannot be empty") | ||||||
} | ||||||
if params.FileSize == 0 { | ||||||
return nil, fmt.Errorf("file.upload.v2: file size cannot be 0") | ||||||
} | ||||||
if params.Channel == "" { | ||||||
return nil, fmt.Errorf("file.upload.v2: channel cannot be empty") | ||||||
} | ||||||
u, err := api.getUploadURLExternal(ctx, params.FileSize, params.Filename, params.AltTxt, params.SnippetText) | ||||||
if err != nil { | ||||||
return nil, err | ||||||
} | ||||||
|
||||||
err = api.uploadToURL(ctx, uploadToExternalParams{ | ||||||
UploadURL: u.UploadURL, | ||||||
Reader: params.Reader, | ||||||
File: params.File, | ||||||
Content: params.Content, | ||||||
Filename: params.Filename, | ||||||
}) | ||||||
if err != nil { | ||||||
return nil, err | ||||||
} | ||||||
|
||||||
c, err := api.completeUploadExternal(ctx, u.FileID, params) | ||||||
if err != nil { | ||||||
return nil, err | ||||||
} | ||||||
if len(c.Files) != 1 { | ||||||
return nil, fmt.Errorf("file.upload.v2: something went wrong; received %d files instead of 1", len(c.Files)) | ||||||
} | ||||||
|
||||||
return &c.Files[0], nil | ||||||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[IMO] Prefer
UploadFileV2Parameters
. It would be easier to use if aligned with the method name.