├── .gitignore ├── go.mod ├── go.sum ├── .github └── workflows │ └── gofmt.yml ├── LICENSE ├── readme.md ├── contributing.md ├── examples.md └── main.go /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | scripts/ 3 | utapi-go 4 | tmp/ 5 | tmp/* 6 | *.log 7 | 8 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/jesses-code-adventures/utapi-go 2 | 3 | go 1.21.0 4 | 5 | require github.com/joho/godotenv v1.5.1 6 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= 2 | github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= 3 | -------------------------------------------------------------------------------- /.github/workflows/gofmt.yml: -------------------------------------------------------------------------------- 1 | name: Go Format Check 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | gofmt: 7 | name: Check Go Formatting 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - name: Check out code 12 | uses: actions/checkout@v2 13 | 14 | - name: Set up Go 15 | uses: actions/setup-go@v2 16 | with: 17 | go-version: '1.21.0' 18 | 19 | - name: Check formatting 20 | run: | 21 | unformatted=$(gofmt -l .) 22 | if [ -n "$unformatted" ]; then 23 | echo "These files are not formatted properly:" 24 | echo "$unformatted" 25 | exit 1 26 | fi 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # utapi-go 2 | 3 | A thin wrapper for the uploadthing api. 4 | 5 | If you'd like to add something you need, feel free to contribute in line with [contributing.md](contributing.md). 6 | 7 | 8 | ## why? 9 | 10 | You have uploaded a large file to uploadthing and you'd like to process that big boy in go. 11 | 12 | ## setup 13 | 14 | You will need a .env file with your uploadthing secret key. 15 | 16 | ```.env 17 | UPLOADTHING_SECRET=sk_************************* 18 | ``` 19 | 20 | ## usage 21 | 22 | After adding your import statement as below, run go mod tidy. 23 | 24 | ```go 25 | package main 26 | 27 | import ( 28 | "github.com/jesses-code-adventures/utapi-go" 29 | "os" 30 | "fmt" 31 | ) 32 | 33 | func main() { 34 | // Create api handler 35 | utApi, err := utapi.NewUtApi() 36 | if err != nil { 37 | fmt.Println("Error creating uploadthing api handler") 38 | fmt.Println(fmt.Errorf("%s", err)) 39 | os.Exit(1) 40 | } 41 | 42 | // Example - deleting a file 43 | // This is the key returned by uploadthing when you create a file 44 | keys := []string{"fc8d296b-20f6-4173-bfa5-5d6c32fc9f6b-geat9r.csv"} 45 | resp, err := utApi.DeleteFiles(keys) 46 | if err != nil { 47 | fmt.Println("Error deleting files") 48 | fmt.Println(fmt.Errorf("%s", err)) 49 | } else { 50 | fmt.Println("Successfully deleted file") 51 | fmt.Println(resp.Success) 52 | } 53 | } 54 | ``` 55 | 56 | -------------------------------------------------------------------------------- /contributing.md: -------------------------------------------------------------------------------- 1 | # contributing 2 | 3 | Currently this package has parity with the [UTApi class in Uploadthing](https://github.com/pingdotgg/uploadthing/blob/main/packages/uploadthing/src/sdk/index.ts#L39), so this package doesn't require new features. 4 | 5 | If you have modifications you'd like to make to the code for cleanliness or other improvements, I'd recommend getting in touch before making the changes. 6 | 7 | ## scope 8 | 9 | This package should do nothing more than the [UTApi class in Uploadthing](https://github.com/pingdotgg/uploadthing/blob/main/packages/uploadthing/src/sdk/index.ts#L39) 10 | 11 | ## utapi features 12 | 13 | - [x] requestUploadThing 14 | - [x] deleteFiles 15 | - [x] getFileUrls 16 | - [x] listFiles 17 | - [x] renameFiles 18 | - [x] getSignedURL 19 | - [x] getUsageInfo 20 | 21 | ## dev setup 22 | 23 | 1. If you'd like to make a change, first clone the [Uploadthing](https://github.com/pingdotgg/uploadthing) repo and find the original function you plan to modify. 24 | 25 | ```bash 26 | git clone --depth 1 https://github.com/pingdotgg/uploadthing uploadthing_source; 27 | cd uploadthing_source; 28 | ## I recommend starting in the below file - the core functionality is there 29 | vim packages/uploadthing/src/sdk/index.ts 30 | ``` 31 | 32 | 2. Clone this repo and start a branch for your feature. 33 | 34 | ```bash 35 | git clone https://github.com/jesses-code-adventures/utapi-go; 36 | cd utapi-go; 37 | git checkout -b my_feature; 38 | ``` 39 | 40 | 3. Make your changes 41 | 42 | 4. Check for any upstream changes 43 | 44 | ```bash 45 | git pull --rebase origin master 46 | ``` 47 | 48 | 5. Push your branch to origin 49 | 50 | ```bash 51 | git push origin my_feature 52 | ``` 53 | 54 | 6. Go to Github 55 | - Create a pull request for your branch 56 | - In your pull request, explain your changes and link any relevant issues. 57 | - Wait for a review/test 58 | -------------------------------------------------------------------------------- /examples.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | Below is example usage for each of the available functions. 4 | 5 | At the bottom of the example you can find the construction of the utApi struct, which is used in the rest of the examples. 6 | 7 | ```go 8 | package main 9 | 10 | import ( 11 | "fmt" 12 | "github.com/jesses-code-adventures/utapi-go" 13 | "os" 14 | ) 15 | 16 | // Example - deleting a file 17 | // Keys is a slice containing the keys returned by uploadthing on upload success 18 | func deleteFileExample(utApi *utapi.UtApi, keys []string) { 19 | resp, err := utApi.DeleteFiles(keys) 20 | if err != nil { 21 | fmt.Println("Error deleting files") 22 | fmt.Println(fmt.Errorf("%s", err)) 23 | } else { 24 | fmt.Println("Successfully deleted file") 25 | fmt.Println(resp.Success) 26 | } 27 | } 28 | 29 | // Example - Getting file urls 30 | // Keys is a slice containing the keys returned by uploadthing on upload success 31 | func getFileUrlsExample(utApi *utapi.UtApi, keys []string) { 32 | resp, err := utApi.GetFileUrls(keys) 33 | if err != nil { 34 | fmt.Println("Error getting file urls") 35 | fmt.Println(fmt.Errorf("%s", err)) 36 | } else { 37 | fmt.Println("Successfully got file urls") 38 | fmt.Println(resp) 39 | } 40 | } 41 | 42 | // Example - Listing your files 43 | func listFilesExample(utApi *utapi.UtApi) { 44 | opts := utapi.ListFilesOpts{Limit: 10, Offset: 0} 45 | resp, err := utApi.ListFiles(opts) 46 | if err != nil { 47 | fmt.Println("Error listing files") 48 | fmt.Println(fmt.Errorf("%s", err)) 49 | } else { 50 | fmt.Println("Successfully listed files") 51 | fmt.Println(resp) 52 | } 53 | } 54 | 55 | // Example - Getting your usage info 56 | func getUsageInfoExample(utApi *utapi.UtApi) { 57 | resp, err := utApi.GetUsageInfo() 58 | if err != nil { 59 | fmt.Println("Error getting usage info") 60 | fmt.Println(fmt.Errorf("%s", err)) 61 | } else { 62 | fmt.Println("Successfully got usage info") 63 | fmt.Println(resp) 64 | } 65 | } 66 | 67 | // Example - Getting a presigned url 68 | func getSignedUrlExample(utApi *utapi.UtApi, fileKey string, expiresIn int) { 69 | opts := utapi.PresignedUrlOpts{FileKey: fileKey, ExpiresIn: expiresIn} 70 | resp, err := utApi.GetPresignedUrl(opts) 71 | if err != nil { 72 | fmt.Println("Error getting presigned url") 73 | fmt.Println(fmt.Errorf("%s", err)) 74 | } else { 75 | fmt.Println("Successfully got presigned url") 76 | fmt.Println(resp) 77 | } 78 | } 79 | 80 | // Example - Renaming files. 81 | // This example takes a single old name and a single new name. 82 | // You could aso construct an array of SingleFileRename structs and pass that. 83 | func renameFilesExample(utApi *utapi.UtApi, oldFileName string, newFileName string) { 84 | singleRename := utapi.SingleFileRename{FileKey: oldFileName, NewName: newFileName} 85 | opts := utapi.RenameFilesOpts{Updates: []utapi.SingleFileRename{singleRename}} 86 | err := utApi.RenameFiles(opts) 87 | if err != nil { 88 | fmt.Println("Error renaming files") 89 | fmt.Println(fmt.Errorf("%s", err)) 90 | } else { 91 | fmt.Println("Successfully renamed files") 92 | } 93 | } 94 | 95 | func main() { 96 | // Create api handler 97 | utApi, err := utapi.NewUtApi() 98 | if err != nil { 99 | fmt.Println("Error creating uploadthing api handler") 100 | fmt.Println(fmt.Errorf("%s", err)) 101 | os.Exit(1) 102 | } 103 | listFilesExample(utApi) 104 | } 105 | ``` 106 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package utapi 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "errors" 7 | "fmt" 8 | "github.com/joho/godotenv" 9 | "io" 10 | "net/http" 11 | "os" 12 | "strings" 13 | ) 14 | 15 | // Types for the uploadthing api to consume 16 | 17 | type uploadthingConfig struct { 18 | Host string 19 | ApiKey string 20 | Version string 21 | } 22 | 23 | type uploadthingHeaders struct { 24 | ContentType string 25 | ApiKey string 26 | SdkVersion string 27 | CacheControl string 28 | } 29 | 30 | type fileKeysPayload struct { 31 | FileKeys []string `json:"fileKeys"` 32 | } 33 | 34 | type SingleFileRename struct { 35 | FileKey string `json:"fileKey"` 36 | NewName string `json:"newName"` 37 | } 38 | 39 | // Arguments for the list files endpoint 40 | type ListFilesOpts struct { 41 | Limit int `json:"limit"` 42 | Offset int `json:"offset"` 43 | } 44 | 45 | // Arguments for the rename files endpoint 46 | type RenameFilesOpts struct { 47 | Updates []SingleFileRename `json:"updates"` 48 | } 49 | 50 | // Arguments for the presigned url endpoint 51 | type PresignedUrlOpts struct { 52 | FileKey string `json:"fileKey"` 53 | ExpiresIn int `json:"expiresIn"` 54 | } 55 | 56 | // Types to decode from the uploadthing api responses 57 | 58 | // Full response object for a delete file action 59 | type DeleteFileResponse struct { 60 | Success bool `json:"success"` 61 | } 62 | 63 | func parseDeleteFileResponse(resp *http.Response) (DeleteFileResponse, error) { 64 | if resp == nil { 65 | return DeleteFileResponse{}, fmt.Errorf("response is nil") 66 | } 67 | 68 | defer resp.Body.Close() 69 | body, err := io.ReadAll(resp.Body) 70 | if err != nil { 71 | return DeleteFileResponse{}, fmt.Errorf("error reading response body: %v", err) 72 | } 73 | 74 | var response DeleteFileResponse 75 | err = json.Unmarshal(body, &response) 76 | if err != nil { 77 | return DeleteFileResponse{}, fmt.Errorf("error unmarshaling response: %v", err) 78 | } 79 | 80 | return response, nil 81 | } 82 | 83 | // Status enum 84 | type uploadthingFileStatus int 85 | 86 | const ( 87 | DeletionPending uploadthingFileStatus = iota 88 | Failed 89 | Uploaded 90 | Uploading 91 | ) 92 | 93 | func createUploadthingFileStatus(status string) uploadthingFileStatus { 94 | switch status { 95 | case "Deletion Pending": 96 | return DeletionPending 97 | case "Failed": 98 | return Failed 99 | case "Uploaded": 100 | return Uploaded 101 | case "Uploading": 102 | return Uploading 103 | default: 104 | return Failed 105 | } 106 | } 107 | 108 | func (s uploadthingFileStatus) String() string { 109 | return [...]string{"Deletion Pending", "Failed", "Uploaded", "Uploading"}[s] 110 | } 111 | 112 | func (s uploadthingFileStatus) MarshalJSON() ([]byte, error) { 113 | return json.Marshal(s.String()) 114 | } 115 | 116 | // Represents a single uploadthing file 117 | type uploadthingFile struct { 118 | Key string `json:"key"` 119 | Id string `json:"id"` 120 | Status uploadthingFileStatus `json:"status"` 121 | } 122 | 123 | // Represents a full response struct for a list of files 124 | type UploadthingFileResponse struct { 125 | Files []uploadthingFile `json:"files"` 126 | } 127 | 128 | func parseUploadthingFileResponse(resp *http.Response) (UploadthingFileResponse, error) { 129 | if resp == nil { 130 | return UploadthingFileResponse{}, fmt.Errorf("response is nil") 131 | } 132 | defer resp.Body.Close() 133 | body, err := io.ReadAll(resp.Body) 134 | if err != nil { 135 | return UploadthingFileResponse{}, fmt.Errorf("error reading response body: %v", err) 136 | } 137 | var response UploadthingFileResponse 138 | json.Unmarshal(body, &response) 139 | if err != nil { 140 | return UploadthingFileResponse{}, fmt.Errorf("error unmarshaling response: %v", err) 141 | } 142 | return response, nil 143 | } 144 | 145 | // Represents a single uploadthing url 146 | type UploadthingUrl struct { 147 | Url string `json:"url"` 148 | Key string `json:"key"` 149 | } 150 | 151 | // Represents a full response struct for a list of urls 152 | type UploadthingUrlsResponse struct { 153 | Data []UploadthingUrl `json:"data"` 154 | } 155 | 156 | func parseUploadthingUrlsResponse(resp *http.Response) (UploadthingUrlsResponse, error) { 157 | if resp == nil { 158 | return UploadthingUrlsResponse{}, fmt.Errorf("response is nil") 159 | } 160 | defer resp.Body.Close() 161 | body, err := io.ReadAll(resp.Body) 162 | if err != nil { 163 | return UploadthingUrlsResponse{}, fmt.Errorf("error reading response body: %v", err) 164 | } 165 | var response UploadthingUrlsResponse 166 | json.Unmarshal(body, &response) 167 | if err != nil { 168 | return UploadthingUrlsResponse{}, fmt.Errorf("error unmarshaling response: %v", err) 169 | } 170 | return response, nil 171 | } 172 | 173 | // Represents a full response struct for uploadthing usage info 174 | type UploadthingUsageInfo struct { 175 | TotalBytes int `json:"totalBytes"` 176 | TotalReadable string `json:"totalReadable"` 177 | AppTotalBytes float32 `json:"appTotalBytes"` 178 | AppTotalReadable string `json:"appTotalReadable"` 179 | FilesUploaded int `json:"filesUploaded"` 180 | LimitBytes float32 `json:"limitBytes"` 181 | LimitReadable string `json:"limitReadable"` 182 | } 183 | 184 | func parseUploadthingUsageInfoResponse(resp *http.Response) (UploadthingUsageInfo, error) { 185 | if resp == nil { 186 | return UploadthingUsageInfo{}, fmt.Errorf("response is nil") 187 | } 188 | defer resp.Body.Close() 189 | body, err := io.ReadAll(resp.Body) 190 | if err != nil { 191 | return UploadthingUsageInfo{}, fmt.Errorf("error reading response body: %v", err) 192 | } 193 | var response UploadthingUsageInfo 194 | json.Unmarshal(body, &response) 195 | if err != nil { 196 | return UploadthingUsageInfo{}, fmt.Errorf("error unmarshaling response: %v", err) 197 | } 198 | return response, nil 199 | } 200 | 201 | func (u *UploadthingUsageInfo) AsString() string { 202 | return fmt.Sprintf("Total Bytes: %d\nTotal Readable: %s\nApp Total Bytes: %f\nApp Total Readable: %s\nFiles Uploaded: %d\nLimit Bytes: %f\nLimit Readable: %s", u.TotalBytes, u.TotalReadable, u.AppTotalBytes, u.AppTotalReadable, u.FilesUploaded, u.LimitBytes, u.LimitReadable) 203 | } 204 | 205 | // Represents a full response struct for a presigned url 206 | type PresignedUrlResponse struct { 207 | Url string `json:"url"` 208 | } 209 | 210 | func parsePresignedUrlResponse(resp *http.Response) (PresignedUrlResponse, error) { 211 | if resp == nil { 212 | return PresignedUrlResponse{Url: ""}, fmt.Errorf("response is nil") 213 | } 214 | defer resp.Body.Close() 215 | body, err := io.ReadAll(resp.Body) 216 | if err != nil { 217 | return PresignedUrlResponse{Url: ""}, fmt.Errorf("error reading response body: %v", err) 218 | } 219 | 220 | var response PresignedUrlResponse 221 | err = json.Unmarshal(body, &response) 222 | if err != nil { 223 | return PresignedUrlResponse{Url: ""}, fmt.Errorf("error unmarshaling response: %v", err) 224 | } 225 | return response, err 226 | } 227 | 228 | // manage environment 229 | 230 | func setEnvironmentVariablesFromFile() error { 231 | return godotenv.Load(".env") 232 | } 233 | 234 | func handleSetEnvironmentVariables() error { 235 | err := setEnvironmentVariablesFromFile() 236 | if err != nil { 237 | fmt.Printf("Couldn't set environment variables from file") 238 | return err 239 | } 240 | return nil 241 | } 242 | 243 | func validateEnvironmentVariables(keys []string) error { 244 | for _, key := range keys { 245 | if os.Getenv(key) == "" { 246 | return errors.New(fmt.Sprintf("%s not set", key)) 247 | } 248 | } 249 | return nil 250 | } 251 | 252 | // functionality 253 | 254 | func getUploadthingConfig() (*uploadthingConfig, error) { 255 | err := handleSetEnvironmentVariables() 256 | if err != nil { 257 | return nil, err 258 | } 259 | err = validateEnvironmentVariables([]string{"UPLOADTHING_SECRET"}) 260 | if err != nil { 261 | return nil, err 262 | } 263 | return &uploadthingConfig{Host: "https://uploadthing.com", ApiKey: os.Getenv("UPLOADTHING_SECRET"), Version: "6.2.0"}, nil 264 | } 265 | 266 | func getUploadthingUrl(pathname string, host string) string { 267 | if !strings.HasPrefix(pathname, "/") { 268 | pathname = fmt.Sprintf("/%s", pathname) 269 | } 270 | if !strings.HasPrefix(pathname, "/api") { 271 | pathname = fmt.Sprintf("/api%s", pathname) 272 | } 273 | url := fmt.Sprintf("%s%s", host, pathname) 274 | return url 275 | } 276 | 277 | func getDebugMessage(url string, headers *uploadthingHeaders, body *bytes.Buffer) string { 278 | return fmt.Sprintf("url: %s, headers: %s, body: %s", url, headers, body.String()) 279 | } 280 | 281 | func getUploadthingHeaders(apiKey string, version string) *uploadthingHeaders { 282 | return &uploadthingHeaders{ContentType: "application/json", ApiKey: apiKey, SdkVersion: version, CacheControl: "no-store"} 283 | } 284 | 285 | func setHeaders(req *http.Request, headers *uploadthingHeaders) { 286 | req.Header.Set("Content-Type", headers.ContentType) 287 | req.Header.Set("x-uploadthing-api-key", headers.ApiKey) 288 | req.Header.Set("x-uploadthing-version", headers.SdkVersion) 289 | req.Header.Set("Cache-Control", "no-store") 290 | } 291 | 292 | // UtApi - Interact with the uploadthing api. 293 | // This struct is designed to replicate UTApi from the uploadthing typescript sdk. 294 | // Please note that responses are encoded into structs that mirror the current json. 295 | // Any errors are returned as-is. 296 | type UtApi struct { 297 | config *uploadthingConfig 298 | httpClient *http.Client 299 | } 300 | 301 | // Construct an instance of the UtApi struct. 302 | // This will read the UPLOADTHING_SECRET environment variable from the .env file. 303 | // If you don't have UPLOADTHING_SECRET set, the function will throw. 304 | func NewUtApi() (*UtApi, error) { 305 | config, err := getUploadthingConfig() 306 | if err != nil { 307 | return nil, err 308 | } 309 | return &UtApi{config: config, httpClient: &http.Client{}}, nil 310 | } 311 | 312 | // Handler for all uploadthing requests. 313 | // If this handler isn't used, you'll need to ensure that the correct headers are set in your separate solution. 314 | func (ut *UtApi) requestUploadthing(pathname string, body *bytes.Buffer) (*http.Response, error) { 315 | url := getUploadthingUrl(pathname, ut.config.Host) 316 | req, err := http.NewRequest(http.MethodPost, url, body) 317 | if err != nil { 318 | return nil, err 319 | } 320 | headers := getUploadthingHeaders(ut.config.ApiKey, ut.config.Version) 321 | setHeaders(req, headers) 322 | resp, err := ut.httpClient.Do(req) 323 | if err != nil { 324 | return nil, err 325 | } 326 | if resp.StatusCode != http.StatusOK { 327 | resp_body := bytes.NewBuffer([]byte{}) 328 | _, err := io.Copy(resp_body, resp.Body) 329 | if err != nil { 330 | return nil, fmt.Errorf("uploadthing request failed, status code: %d, body: %s, req: %s", resp.StatusCode, resp_body, getDebugMessage(url, headers, body)) 331 | } else { 332 | return nil, fmt.Errorf("uploadthing request failed, status code: %d, req: %s", resp.StatusCode, getDebugMessage(url, headers, body)) 333 | } 334 | } 335 | return resp, nil 336 | } 337 | 338 | // Delete files from uploadthing. 339 | func (ut *UtApi) DeleteFiles(fileKeys []string) (*DeleteFileResponse, error) { 340 | payload := fileKeysPayload{FileKeys: fileKeys} 341 | fileKeysJson, err := json.Marshal(payload) 342 | if err != nil { 343 | return nil, err 344 | } 345 | body := bytes.NewBuffer(fileKeysJson) 346 | utResponse, err := ut.requestUploadthing("/api/deleteFile", body) 347 | if err != nil { 348 | return nil, err 349 | } 350 | response, err := parseDeleteFileResponse(utResponse) 351 | if err != nil { 352 | return nil, err 353 | } 354 | return &response, nil 355 | } 356 | 357 | // Given an array of file keys, get the corresponding urls. 358 | func (ut *UtApi) GetFileUrls(fileKeys []string) (*UploadthingUrlsResponse, error) { 359 | payload := fileKeysPayload{FileKeys: fileKeys} 360 | fileKeysJson, err := json.Marshal(payload) 361 | if err != nil { 362 | return nil, err 363 | } 364 | body := bytes.NewBuffer(fileKeysJson) 365 | utResponse, err := ut.requestUploadthing("/api/getFileUrl", body) 366 | if err != nil { 367 | return nil, err 368 | } 369 | response, err := parseUploadthingUrlsResponse(utResponse) 370 | if err != nil { 371 | return nil, err 372 | } 373 | return &response, nil 374 | } 375 | 376 | // List files stored in uploadthing. 377 | func (ut *UtApi) ListFiles(opts ListFilesOpts) (*UploadthingFileResponse, error) { 378 | optsJson, err := json.Marshal(opts) 379 | if err != nil { 380 | return nil, err 381 | } 382 | body := bytes.NewBuffer(optsJson) 383 | utResponse, err := ut.requestUploadthing("/api/listFiles", body) 384 | if err != nil { 385 | return nil, err 386 | } 387 | response, err := parseUploadthingFileResponse(utResponse) 388 | if err != nil { 389 | return nil, err 390 | } 391 | return &response, nil 392 | } 393 | 394 | // Rename files in uploadthing. 395 | // No response is returned, but an error is returned if the request fails. 396 | // This is in line with the behaviour of the uploadthing typescript sdk. 397 | func (ut *UtApi) RenameFiles(files RenameFilesOpts) error { 398 | optsJson, err := json.Marshal(files) 399 | if err != nil { 400 | return err 401 | } 402 | body := bytes.NewBuffer(optsJson) 403 | _, err = ut.requestUploadthing("/api/renameFiles", body) 404 | if err != nil { 405 | return err 406 | } 407 | return nil 408 | } 409 | 410 | // Get usage info for the current uploadthing account. 411 | func (ut *UtApi) GetUsageInfo() (*UploadthingUsageInfo, error) { 412 | utResponse, err := ut.requestUploadthing("/api/getUsageInfo", bytes.NewBuffer([]byte{})) 413 | if err != nil { 414 | return nil, err 415 | } 416 | response, err := parseUploadthingUsageInfoResponse(utResponse) 417 | if err != nil { 418 | return nil, err 419 | } 420 | return &response, nil 421 | } 422 | 423 | // Generate a presigned url for a file. 424 | // expiresIn should be a duration in seconds. 425 | // The maximum value for expiresIn is 604800 (7 days). 426 | // You must accept overrides on the UploadThing dashboard for expiresIn to be accepted. 427 | func (ut *UtApi) GetPresignedUrl(opts PresignedUrlOpts) (string, error) { 428 | if opts.ExpiresIn > 604800 { 429 | return "", errors.New("expiresIn must be less than 604800") 430 | } 431 | optsJson, err := json.Marshal(opts) 432 | if err != nil { 433 | return "", err 434 | } 435 | utResponse, err := ut.requestUploadthing("/api/requestFileAccess", bytes.NewBuffer(optsJson)) 436 | if err != nil { 437 | return "", err 438 | } 439 | response, err := parsePresignedUrlResponse(utResponse) 440 | if err != nil { 441 | return "", err 442 | } 443 | return response.Url, nil 444 | } 445 | --------------------------------------------------------------------------------