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

Commit af5cd6c

Browse files
committed
initial commit
1 parent 6f818ef commit af5cd6c

10 files changed

+350
-1
lines changed

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
# backend-go
2-
Go client library for StaticBackend
2+
Go client library for StaticBackend's backend API

account.go

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package backend
2+
3+
type AccountParams struct {
4+
Email string `json:"email"`
5+
Password string `json:"password"`
6+
}
7+
8+
// Register creates a new user and returns their session token.
9+
func Register(email, password string) (string, error) {
10+
body := AccountParams{
11+
Email: email,
12+
Password: password,
13+
}
14+
var token string
15+
if err := Post("", "/register", body, &token); err != nil {
16+
return "", err
17+
}
18+
19+
return token, nil
20+
}
21+
22+
// Login authenticate a user and returns their session token
23+
func Login(email, password string) (string, error) {
24+
body := AccountParams{
25+
Email: email,
26+
Password: password,
27+
}
28+
var token string
29+
if err := Post("", "/login", body, &token); err != nil {
30+
return "", err
31+
}
32+
33+
return token, nil
34+
}

account_test.go

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package backend_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/staticbackendhq/backend-go"
7+
)
8+
9+
func TestRegisterAndLogin(t *testing.T) {
10+
email, pass := "unit", "unit"
11+
12+
tok, err := backend.Register(email, pass)
13+
if err != nil {
14+
t.Error(err)
15+
}
16+
17+
tok2, err := backend.Login(email, pass)
18+
if err != nil {
19+
t.Error(err)
20+
} else if tok != tok2 {
21+
t.Errorf("register/login tokens does not match, expected %s got %s", tok, tok2)
22+
}
23+
}

client.go

+86
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package backend
2+
3+
import (
4+
"bytes"
5+
"encoding/json"
6+
"fmt"
7+
"io"
8+
"io/ioutil"
9+
"net/http"
10+
"time"
11+
)
12+
13+
// PublicKey is required for all HTTP requests.
14+
var (
15+
PublicKey string
16+
Verbose bool
17+
Region string
18+
)
19+
20+
func request(token, method, url, ct string, body io.Reader, v interface{}) error {
21+
host := "http://localhost:8099"
22+
23+
start := time.Now()
24+
25+
req, err := http.NewRequest(method, fmt.Sprintf("%s%s", host, url), body)
26+
if err != nil {
27+
return err
28+
}
29+
30+
req.Header.Set("Content-Type", ct)
31+
32+
// We provide authentication
33+
req.Header.Set("SB-PUBLIC-KEY", PublicKey)
34+
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
35+
36+
res, err := http.DefaultClient.Do(req)
37+
if err != nil {
38+
return err
39+
}
40+
defer res.Body.Close()
41+
42+
if Verbose {
43+
fmt.Printf("%d\t%s\t%v\t%s bytes\n", res.StatusCode, url, time.Since(start), res.Header.Get("Content-Length"))
44+
}
45+
46+
// Did we got an error
47+
if res.StatusCode > 299 {
48+
b, err := ioutil.ReadAll(res.Body)
49+
if err != nil {
50+
return fmt.Errorf("error reading body: %v", err)
51+
}
52+
53+
return fmt.Errorf("error returned by the backend: %s", string(b))
54+
}
55+
56+
if res.Header.Get("Content-Type") == "application/json" {
57+
if err := json.NewDecoder(res.Body).Decode(v); err != nil {
58+
return fmt.Errorf("unable to decode the response body: %v", err)
59+
}
60+
61+
return nil
62+
}
63+
64+
b, err := ioutil.ReadAll(res.Body)
65+
if err != nil {
66+
return fmt.Errorf("error reading body: %v", err)
67+
}
68+
69+
v = b
70+
return nil
71+
}
72+
73+
// Get sends an HTTP GET request to the backend
74+
func Get(token, url string, v interface{}) error {
75+
return request(token, http.MethodGet, url, "application/json", nil, v)
76+
}
77+
78+
func Post(token, url string, body interface{}, v interface{}) error {
79+
b, err := json.Marshal(body)
80+
if err != nil {
81+
return fmt.Errorf("error while encoding the body to JSON: %v", err)
82+
}
83+
84+
buf := bytes.NewReader(b)
85+
return request(token, "POST", url, "application/json", buf, v)
86+
}

client_test.go

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package backend_test
2+
3+
import (
4+
"fmt"
5+
"log"
6+
"testing"
7+
8+
"github.com/staticbackendhq/backend-go"
9+
)
10+
11+
var token string
12+
13+
func init() {
14+
backend.PublicKey = "unit-test"
15+
16+
// this user is created by default in dev mod as an admin
17+
t, err := backend.Login("[email protected]", "test123")
18+
if err != nil {
19+
log.Fatal("unable to login to the backend: ", err)
20+
}
21+
22+
token = t
23+
}
24+
25+
type Task struct {
26+
ID string `json:"id"`
27+
Name string `json:"name"`
28+
Done bool `json:"done"`
29+
}
30+
31+
func TestGetRequest(t *testing.T) {
32+
// we add a tmp doc
33+
var insertedTask Task
34+
if err := backend.Create(token, "pub_test", Task{Name: "test"}, &insertedTask); err != nil {
35+
t.Error(err)
36+
}
37+
38+
var s Task
39+
u := fmt.Sprintf("/db/pub_test/%s", insertedTask.ID)
40+
if err := backend.Get(token, u, &s); err != nil {
41+
t.Error(err)
42+
} else if insertedTask.ID != s.ID {
43+
t.Errorf("expected inserted to be %s got %s", insertedTask.ID, s.ID)
44+
}
45+
}

db.go

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package backend
2+
3+
import (
4+
"fmt"
5+
"net/url"
6+
"strconv"
7+
)
8+
9+
// Create adds a new document to a repository and returns the created document.
10+
func Create(token, repo string, body interface{}, v interface{}) error {
11+
return Post(token, fmt.Sprintf("/db/%s", repo), body, v)
12+
}
13+
14+
// ListParams are used to page results and sort.
15+
type ListParams struct {
16+
Page int
17+
Size int
18+
Descending bool
19+
}
20+
21+
// ListResult is used for list document
22+
type ListResult struct {
23+
Page int `json:"page"`
24+
PageSize int `json:"size"`
25+
Total int `json:"total"`
26+
Results interface{} `json:"results"`
27+
}
28+
29+
// List returns a list of document in a specific repository.
30+
func List(token, repo string, v interface{}, params *ListParams) (meta ListResult, err error) {
31+
meta.Results = v
32+
33+
qs := url.Values{}
34+
if params != nil {
35+
qs.Add("page", strconv.Itoa(params.Page))
36+
qs.Add("size", strconv.Itoa(params.Size))
37+
if params.Descending {
38+
qs.Add("desc", "true")
39+
}
40+
}
41+
42+
err = Get(token, fmt.Sprintf("/db/%s?%s", repo, qs.Encode()), &meta)
43+
return
44+
}
45+
46+
// GetByID returns a specific document.
47+
func GetByID(token, repo, id string, v interface{}) error {
48+
return Get(token, fmt.Sprintf("/db/%s/%s", repo, id), v)
49+
}

db_test.go

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package backend_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/staticbackendhq/backend-go"
7+
)
8+
9+
func TestList(t *testing.T) {
10+
// we add a tmp doc
11+
var insertedTask Task
12+
if err := backend.Create(token, "tasks", Task{Name: "test"}, &insertedTask); err != nil {
13+
t.Error(err)
14+
}
15+
16+
var results []Task
17+
meta, err := backend.List(token, "tasks", &results, nil)
18+
if err != nil {
19+
t.Error(err)
20+
} else if len(results) < 1 {
21+
t.Errorf("expected tasks repo to have 1 doc got %d", len(results))
22+
} else if meta.Total < 1 {
23+
t.Errorf("expected total to be >= 1 goc %d", meta.Total)
24+
} else if results[len(results)-1].ID != insertedTask.ID {
25+
t.Errorf("expected last task ID to be %s got %s", insertedTask.ID, results[len(results)-1].ID)
26+
}
27+
}

go.mod

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module github.com/staticbackendhq/backend-go
2+
3+
go 1.15

storage.go

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package backend
2+
3+
import (
4+
"bytes"
5+
"fmt"
6+
"io"
7+
"mime/multipart"
8+
"net/url"
9+
)
10+
11+
// StoreFile uploads a new file and returns its public URL using SB CDN.
12+
func StoreFile(token, filename string, file io.ReadSeeker) (string, error) {
13+
// multipart form data
14+
var buf bytes.Buffer
15+
w := multipart.NewWriter(&buf)
16+
17+
fw, err := w.CreateFormFile("file", filename)
18+
if err != nil {
19+
return "", fmt.Errorf("error creating form field: %v", err)
20+
}
21+
22+
if _, err := io.Copy(fw, file); err != nil {
23+
return "", fmt.Errorf("error copying file data to form field: %v", err)
24+
}
25+
26+
w.Close()
27+
28+
var fileURL string
29+
if err := request(token, "POST", "/storage/upload", w.FormDataContentType(), &buf, &fileURL); err != nil {
30+
return "", fmt.Errorf("error while uploading file: %v", err)
31+
}
32+
33+
return fileURL, nil
34+
}
35+
36+
// DownloadFile retrieves the file content as []byte
37+
func DownloadFile(token, fileURL string) ([]byte, error) {
38+
u, err := url.Parse(fileURL)
39+
if err != nil {
40+
return nil, err
41+
}
42+
43+
var buf []byte
44+
err = Get(token, u.Path, &buf)
45+
return buf, err
46+
}

storage_test.go

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package backend_test
2+
3+
import (
4+
"io/ioutil"
5+
"os"
6+
"strings"
7+
"testing"
8+
9+
"github.com/staticbackendhq/backend-go"
10+
)
11+
12+
func TestUploadFile(t *testing.T) {
13+
f, err := os.Open("storage_test.go")
14+
if err != nil {
15+
t.Fatal(err)
16+
}
17+
18+
u, err := backend.StoreFile(token, "unittest.go", f)
19+
if err != nil {
20+
t.Error(err)
21+
} else if strings.HasPrefix(u, "/_servefile_/") == false {
22+
t.Errorf("expected URL to have http as prefix got %s", u)
23+
}
24+
25+
orig, err := ioutil.ReadAll(f)
26+
if err != nil {
27+
t.Fatal(err)
28+
}
29+
30+
buf, err := backend.DownloadFile(token, "http://localhost:8099"+u)
31+
if err != nil {
32+
t.Error(err)
33+
} else if len(orig) != len(buf) {
34+
t.Errorf("uploaded buffer and disk file differ, expected length %d got %d", len(orig), len(buf))
35+
}
36+
}

0 commit comments

Comments
 (0)