Skip to content

Commit 5fecf96

Browse files
authored
feat!: improve implementation of the storage api (nedpals#2)
* feat: implimentation of the supabase storage api * feat: Addedmore methods for the storage api * fix: typo and format
1 parent c4e1527 commit 5fecf96

File tree

1 file changed

+357
-4
lines changed

1 file changed

+357
-4
lines changed

storage.go

Lines changed: 357 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,25 @@
11
package supabase
22

33
import (
4+
5+
"bufio"
46
"bytes"
57
"context"
68
"encoding/json"
7-
// "errors"
9+
"io"
10+
"regexp"
11+
"strconv"
812
"fmt"
913
"net/http"
1014
)
1115

1216
type Storage struct {
1317
client *Client
14-
// *bucket
15-
// *file
18+
1619
}
1720

21+
// Storage buckets methods
22+
1823
type bucket struct {
1924
Name string `json:"name"`
2025
}
@@ -28,7 +33,6 @@ type bucketResponse struct {
2833
type bucketMessage struct {
2934
Message string `json:"message"`
3035
}
31-
// type file struct{}
3236

3337
type bucketOption struct {
3438
Id string `json:"id"`
@@ -42,6 +46,7 @@ type storageError struct {
4246
Message string `json:"message"`
4347
}
4448

49+
4550
// CreateBucket creates a new storage bucket
4651
// @param: option: a bucketOption with the name and id of the bucket you want to create
4752
// @returns: bucket: a response with the details of the bucket of the bucket created
@@ -174,3 +179,351 @@ func (s *Storage) DeleteBucket(ctx context.Context, id string) (*bucketResponse,
174179
return &res, nil
175180
}
176181

182+
183+
func (s Storage) From(bucketId string) *file {
184+
return &file{BucketId: bucketId}
185+
}
186+
187+
// Storage Objects methods
188+
189+
type file struct{
190+
BucketId string
191+
header http.Header
192+
}
193+
194+
type SortBy struct {
195+
Column string `json:"column"`
196+
Order string `json:"order"`
197+
}
198+
199+
type FileResponse struct {
200+
Key string `json:"key"`
201+
Message string `json:"message"`
202+
}
203+
204+
type FileSearchOptions struct {
205+
Limit int `json:"limit"`
206+
Offset int `json:"offset"`
207+
SortBy SortBy `json:"sortBy"`
208+
}
209+
210+
type FileObject struct {
211+
Name string `json:"name"`
212+
BucketId string `json:"bucket_id"`
213+
Owner string `json:"owner"`
214+
Id string `json:"id"`
215+
UpdatedAt string `json:"updated_at"`
216+
CreatedAt string `json:"created_at"`
217+
LastAccessedAt string `json:"last_accessed_at"`
218+
Metadata interface{} `json:"metadata"`
219+
Buckets bucket `json:"buckets"`
220+
}
221+
222+
type ListFileRequest struct {
223+
Limit int `json:"limit"`
224+
Offset int `json:"offset"`
225+
SortBy SortBy `json:"sortBy"`
226+
Prefix string `json:"prefix"`
227+
}
228+
229+
type SignedUrlResponse struct {
230+
SignedUrl string `json:"signedURL"`
231+
}
232+
233+
const (
234+
defaultLimit = 100
235+
defaultOffset = 0
236+
defaultFileCacheControl = "3600"
237+
defaultFileContent = "text/pain;charset=UTF-8"
238+
defaultFileUpsert = false
239+
defaultSortColumn = "name"
240+
defaultSortOrder = "asc"
241+
)
242+
243+
func (f *file) UploadOrUpdate(path string, data io.Reader, update bool) FileResponse {
244+
s := &Storage{}
245+
f.header.Set("cache-control", defaultFileCacheControl)
246+
f.header.Set("content-type", defaultFileContent)
247+
f.header.Set("x-upsert", strconv.FormatBool(defaultFileUpsert))
248+
249+
body := bufio.NewReader(data)
250+
_path := removeEmptyFolder(f.BucketId + "/" + path)
251+
client := &http.Client{}
252+
253+
254+
var (
255+
res *http.Response
256+
err error
257+
)
258+
259+
if update {
260+
var req *http.Request
261+
req, err = http.NewRequest(http.MethodPut, s.client.BaseURL+"/object/"+_path, body)
262+
injectAuthorizationHeader(req, s.client.apiKey)
263+
if err != nil {
264+
panic(err)
265+
}
266+
res, err = client.Do(req)
267+
}else {
268+
var req *http.Request
269+
req, err = http.NewRequest(http.MethodPost, s.client.BaseURL+"/object/"+_path, body)
270+
f.header.Set("content-type", defaultFileContent)
271+
injectAuthorizationHeader(req, s.client.apiKey)
272+
if err != nil {
273+
panic(err)
274+
}
275+
res, err = client.Do(req)
276+
if err != nil {
277+
panic(err)
278+
}
279+
}
280+
if err != nil {
281+
panic(err)
282+
}
283+
284+
resBody, err := io.ReadAll(res.Body)
285+
if err != nil {
286+
panic(err)
287+
}
288+
var response FileResponse
289+
if err = json.Unmarshal(resBody, &response); err != nil {
290+
panic(err)
291+
}
292+
293+
return response
294+
}
295+
296+
// Update updates a file object in a storage bucket
297+
func (f *file) Update(path string, data io.Reader) FileResponse {
298+
return f.UploadOrUpdate(path, data, true)
299+
}
300+
301+
// Upload uploads a file object to a storage bucket
302+
func (f *file) Upload(path string, data io.Reader) FileResponse {
303+
return f.UploadOrUpdate(path, data, false)
304+
}
305+
306+
// Move moves a file object
307+
func (f *file) Move(fromPath string, toPath string) FileResponse {
308+
_json, _ := json.Marshal(map[string]interface{}{
309+
"bucketId": f.BucketId,
310+
"sourceKey": fromPath,
311+
"destintionKey": toPath,
312+
})
313+
s := &Storage{}
314+
req, err := http.NewRequest(http.MethodPost, s.client.BaseURL+"/object/move", bytes.NewBuffer(_json))
315+
injectAuthorizationHeader(req, s.client.apiKey)
316+
if err != nil {
317+
panic(err)
318+
}
319+
client := &http.Client{}
320+
res, err := client.Do(req)
321+
if err != nil {
322+
panic(err)
323+
}
324+
325+
body, err := io.ReadAll(res.Body)
326+
if err != nil {
327+
panic(err)
328+
}
329+
330+
var response FileResponse
331+
if err := json.Unmarshal(body, &response); err != nil {
332+
panic(err)
333+
}
334+
335+
return response
336+
}
337+
338+
// CreatSignedUrl create a signed url for a file object
339+
func (f *file) CreatSignedUrl(filePath string, expiresIn int) SignedUrlResponse {
340+
_json, _ := json.Marshal(map[string]interface{}{
341+
"expiresIn": expiresIn,
342+
})
343+
s := &Storage{}
344+
req, err := http.NewRequest(http.MethodPost, s.client.BaseURL+"/object/sign/"+f.BucketId+"/"+filePath, bytes.NewBuffer(_json))
345+
injectAuthorizationHeader(req, s.client.apiKey)
346+
347+
if err != nil {
348+
panic(err)
349+
}
350+
client := &http.Client{}
351+
res, err := client.Do(req)
352+
if err != nil {
353+
panic(err)
354+
}
355+
356+
body, err := io.ReadAll(res.Body)
357+
if err != nil {
358+
panic(err)
359+
}
360+
361+
var response SignedUrlResponse
362+
if err := json.Unmarshal(body, &response); err != nil {
363+
panic(err)
364+
}
365+
response.SignedUrl = s.client.BaseURL+response.SignedUrl
366+
367+
return response
368+
}
369+
370+
// GetPublicUrl get a public signed url of a file object
371+
func (f *file) GetPublicUrl(filePath string) SignedUrlResponse {
372+
s := &Storage{}
373+
var response SignedUrlResponse
374+
response.SignedUrl = s.client.BaseURL + "/object/public" + f.BucketId + "/" + filePath
375+
376+
return response
377+
}
378+
379+
// Remove deletes a file object
380+
func (f *file) Remove(filePaths []string) FileResponse {
381+
_json, _ := json.Marshal(map[string]interface{}{
382+
"prefixex": filePaths,
383+
})
384+
s := &Storage{}
385+
req, err := http.NewRequest(http.MethodPost, s.client.BaseURL+"/object/"+f.BucketId, bytes.NewBuffer(_json))
386+
injectAuthorizationHeader(req, s.client.apiKey)
387+
388+
if err != nil {
389+
panic(err)
390+
}
391+
client := &http.Client{}
392+
res, err := client.Do(req)
393+
if err != nil {
394+
panic(err)
395+
}
396+
397+
body, err := io.ReadAll(res.Body)
398+
if err != nil {
399+
panic(err)
400+
}
401+
402+
var response FileResponse
403+
if err := json.Unmarshal(body, &response); err != nil {
404+
panic(err)
405+
}
406+
407+
return response
408+
}
409+
410+
// List list all file object
411+
func (f *file) List(queryPath string, options FileSearchOptions) []FileObject {
412+
if options.Limit == 0 {
413+
options.Limit = defaultLimit
414+
}
415+
if options.Offset == 0 {
416+
options.Offset = defaultOffset
417+
}
418+
if options.SortBy.Order == "" {
419+
options.SortBy.Order = defaultSortOrder
420+
}
421+
if options.SortBy.Column == "" {
422+
options.SortBy.Column = defaultSortColumn
423+
}
424+
425+
_body := ListFileRequest{
426+
Limit: options.Limit,
427+
Offset: options.Offset,
428+
SortBy: SortBy{
429+
Column: options.SortBy.Column,
430+
Order: options.SortBy.Order,
431+
},
432+
Prefix: queryPath,
433+
}
434+
435+
_json, _ := json.Marshal(_body)
436+
s := &Storage{}
437+
req, err := http.NewRequest(http.MethodPost, s.client.BaseURL+"/object/list/"+f.BucketId, bytes.NewBuffer(_json))
438+
injectAuthorizationHeader(req, s.client.apiKey)
439+
440+
if err != nil {
441+
panic(err)
442+
}
443+
client := &http.Client{}
444+
res, err := client.Do(req)
445+
if err != nil {
446+
panic(err)
447+
}
448+
449+
body, err := io.ReadAll(res.Body)
450+
if err != nil {
451+
panic(err)
452+
}
453+
454+
var response []FileObject
455+
if err := json.Unmarshal(body, &response); err != nil {
456+
panic(err)
457+
}
458+
459+
return response
460+
461+
}
462+
463+
// Copy copies a file object
464+
func (f *file) Copy(fromPath, toPath string) FileResponse {
465+
_json, _ := json.Marshal(map[string]interface{}{
466+
"bucketId": f.BucketId,
467+
"sourceKey": fromPath,
468+
"destintionKey": toPath,
469+
})
470+
s := &Storage{}
471+
req, err := http.NewRequest(http.MethodPost, s.client.BaseURL+"/object/copy", bytes.NewBuffer(_json))
472+
injectAuthorizationHeader(req, s.client.apiKey)
473+
474+
if err != nil {
475+
panic(err)
476+
}
477+
client := &http.Client{}
478+
res, err := client.Do(req)
479+
if err != nil {
480+
panic(err)
481+
}
482+
483+
body, err := io.ReadAll(res.Body)
484+
if err != nil {
485+
panic(err)
486+
}
487+
488+
var response FileResponse
489+
if err := json.Unmarshal(body, &response); err != nil {
490+
panic(err)
491+
}
492+
493+
return response
494+
}
495+
496+
// Download retrieves a file object
497+
func (f *file) Download(filePath string) FileResponse {
498+
// _json, _ := json.Marshal(map[string]interface{}{
499+
// })
500+
s := &Storage{}
501+
req, err := http.NewRequest(http.MethodGet, s.client.BaseURL+"/object/"+f.BucketId + "/" + filePath, nil)
502+
503+
injectAuthorizationHeader(req, s.client.apiKey)
504+
if err != nil {
505+
panic(err)
506+
}
507+
client := &http.Client{}
508+
res, err := client.Do(req)
509+
if err != nil {
510+
panic(err)
511+
}
512+
513+
body, err := io.ReadAll(res.Body)
514+
if err != nil {
515+
panic(err)
516+
}
517+
518+
var response FileResponse
519+
if err := json.Unmarshal(body, &response); err != nil {
520+
panic(err)
521+
}
522+
523+
return response
524+
}
525+
526+
func removeEmptyFolder(filePath string) string {
527+
return regexp.MustCompile(`\/\/`).ReplaceAllString(filePath, "/")
528+
}
529+

0 commit comments

Comments
 (0)