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

Commit fb2e75b

Browse files
committed
first PoC for a simple JS function runtime
1 parent b89381b commit fb2e75b

File tree

9 files changed

+246
-7
lines changed

9 files changed

+246
-7
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ deploy:
1212
scp staticbackend sb-poc:/home/dstpierre/sb
1313

1414
test:
15-
@JWT_SECRET=okdevmode go test --race --cover ./...
15+
@JWT_SECRET=okdevmode go test -v --race --cover ./...
1616

1717
docker:
1818
docker build . -t staticbackend:latest

function.go

Lines changed: 0 additions & 1 deletion
This file was deleted.

function/runtime.go

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
package function
2+
3+
import (
4+
"fmt"
5+
"staticbackend/db"
6+
"staticbackend/internal"
7+
8+
"github.com/dop251/goja"
9+
"go.mongodb.org/mongo-driver/bson/primitive"
10+
"go.mongodb.org/mongo-driver/mongo"
11+
)
12+
13+
type ExecutionEnvironment struct {
14+
Auth internal.Auth
15+
DB *mongo.Database
16+
Base *db.Base
17+
}
18+
19+
type Result struct {
20+
OK bool `json:"ok"`
21+
Content interface{} `json:"content"`
22+
}
23+
24+
func (env *ExecutionEnvironment) Execute() error {
25+
vm := goja.New()
26+
vm.SetFieldNameMapper(goja.TagFieldNameMapper("json", true))
27+
28+
env.addHelpers(vm)
29+
env.addDatabaseFunctions(vm)
30+
31+
result, err := vm.RunString(`
32+
log("works here");
33+
function handle() {
34+
var o = {
35+
desc: "yep",
36+
done: false,
37+
subobj: {
38+
yep: "working",
39+
status: true
40+
}
41+
};
42+
var result = create("jsexec", o);
43+
log(result);
44+
if (!result.ok) {
45+
log("ERROR");
46+
log(result.content);
47+
return;
48+
}
49+
var result = get("jsexec", result.content.id)
50+
log("result.ok", result.ok);
51+
log("result.content", result.content)
52+
}`)
53+
54+
if err != nil {
55+
return err
56+
}
57+
58+
handler, ok := goja.AssertFunction(vm.Get("handle"))
59+
if !ok {
60+
return fmt.Errorf(`unable to find function "handle": %v`, err)
61+
}
62+
63+
resp, err := handler(goja.Undefined())
64+
if err != nil {
65+
return fmt.Errorf("error executing your function: %v", err)
66+
}
67+
68+
fmt.Println("resp", resp)
69+
fmt.Println("result", result)
70+
return nil
71+
}
72+
73+
func (env *ExecutionEnvironment) addHelpers(vm *goja.Runtime) {
74+
vm.Set("log", func(call goja.FunctionCall) goja.Value {
75+
if len(call.Arguments) == 0 {
76+
return goja.Undefined()
77+
}
78+
79+
var params []interface{}
80+
for _, v := range call.Arguments {
81+
params = append(params, v.Export())
82+
}
83+
fmt.Println(params...)
84+
return goja.Undefined()
85+
})
86+
}
87+
88+
func (env *ExecutionEnvironment) addDatabaseFunctions(vm *goja.Runtime) {
89+
vm.Set("create", func(call goja.FunctionCall) goja.Value {
90+
if len(call.Arguments) != 2 {
91+
return vm.ToValue(Result{Content: "argument missmatch: you need 2 arguments for create(col, doc"})
92+
}
93+
var col string
94+
if err := vm.ExportTo(call.Argument(0), &col); err != nil {
95+
return vm.ToValue(Result{Content: "the first argument should be a string"})
96+
}
97+
doc := make(map[string]interface{})
98+
if err := vm.ExportTo(call.Argument(1), &doc); err != nil {
99+
return vm.ToValue(Result{Content: "the second argument should be an object"})
100+
}
101+
102+
doc, err := env.Base.Add(env.Auth, env.DB, col, doc)
103+
if err != nil {
104+
return vm.ToValue(Result{Content: fmt.Sprintf("error calling create(): %s", err.Error())})
105+
}
106+
107+
if err := env.clean(doc); err != nil {
108+
return vm.ToValue(Result{Content: err.Error()})
109+
}
110+
return vm.ToValue(Result{OK: true, Content: doc})
111+
})
112+
vm.Set("get", func(call goja.FunctionCall) goja.Value {
113+
if len(call.Arguments) != 2 {
114+
return vm.ToValue(Result{Content: "argument missmatch: you need 2 arguments for get(repo, id)"})
115+
}
116+
var col, id string
117+
if err := vm.ExportTo(call.Argument(0), &col); err != nil {
118+
return vm.ToValue(Result{Content: "the first argument should be a string"})
119+
}
120+
if err := vm.ExportTo(call.Argument(1), &id); err != nil {
121+
return vm.ToValue(Result{Content: "the second argument should be a string"})
122+
}
123+
124+
doc, err := env.Base.GetByID(env.Auth, env.DB, col, id)
125+
if err != nil {
126+
return vm.ToValue(Result{Content: fmt.Sprintf("error calling get(): %s", err.Error())})
127+
}
128+
129+
if err := env.clean(doc); err != nil {
130+
return vm.ToValue(Result{Content: err.Error()})
131+
}
132+
133+
return vm.ToValue(Result{OK: true, Content: doc})
134+
})
135+
}
136+
137+
func (*ExecutionEnvironment) clean(doc map[string]interface{}) error {
138+
if id, ok := doc["id"]; ok {
139+
oid, ok := id.(primitive.ObjectID)
140+
if !ok {
141+
return fmt.Errorf("unable to cast document id")
142+
}
143+
doc["id"] = oid.Hex()
144+
}
145+
146+
if id, ok := doc[internal.FieldAccountID]; ok {
147+
oid, ok := id.(primitive.ObjectID)
148+
if !ok {
149+
return fmt.Errorf("unable to cast document accountId")
150+
}
151+
doc[internal.FieldAccountID] = oid.Hex()
152+
}
153+
154+
return nil
155+
}

functions.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package staticbackend
2+
3+
import (
4+
"net/http"
5+
"staticbackend/db"
6+
"staticbackend/function"
7+
"staticbackend/middleware"
8+
)
9+
10+
type functions struct {
11+
base *db.Base
12+
}
13+
14+
type ExecData struct {
15+
FunctionName string `json:"name"`
16+
}
17+
18+
func (f *functions) exec(w http.ResponseWriter, r *http.Request) {
19+
conf, auth, err := middleware.Extract(r, true)
20+
if err != nil {
21+
http.Error(w, err.Error(), http.StatusUnauthorized)
22+
return
23+
}
24+
25+
var data ExecData
26+
if err := parseBody(r.Body, &data); err != nil {
27+
http.Error(w, err.Error(), http.StatusBadRequest)
28+
return
29+
}
30+
31+
curDB := client.Database(conf.Name)
32+
33+
env := &function.ExecutionEnvironment{
34+
Auth: auth,
35+
DB: curDB,
36+
Base: f.base,
37+
}
38+
if err := env.Execute(); err != nil {
39+
http.Error(w, err.Error(), http.StatusInternalServerError)
40+
return
41+
}
42+
43+
w.WriteHeader(http.StatusOK)
44+
}

functions_test.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package staticbackend
2+
3+
import (
4+
"io"
5+
"net/http"
6+
"testing"
7+
)
8+
9+
func TestFunctionsExecuteGetByID(t *testing.T) {
10+
data := ExecData{FunctionName: "unittest"}
11+
resp := dbPost(t, funexec.exec, "", data)
12+
if resp.StatusCode != http.StatusOK {
13+
b, err := io.ReadAll(resp.Body)
14+
if err != nil {
15+
t.Fatal(err)
16+
}
17+
defer resp.Body.Close()
18+
19+
t.Log(string(b))
20+
t.Errorf("expected status 200 got %s", resp.Status)
21+
}
22+
}

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ go 1.13
44

55
require (
66
github.com/aws/aws-sdk-go v1.27.2
7+
github.com/dop251/goja v0.0.0-20210804101310-32956a348b49
78
github.com/gbrlsnchs/jwt/v3 v3.0.0-rc.1
89
github.com/go-redis/redis/v8 v8.4.4
910
github.com/golang/snappy v0.0.4 // indirect
@@ -15,5 +16,4 @@ require (
1516
go.mongodb.org/mongo-driver v1.7.0
1617
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97
1718
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
18-
golang.org/x/text v0.3.6 // indirect
1919
)

go.sum

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,26 @@ github.com/aws/aws-sdk-go v1.27.2 h1:yr0Lp4bcrIiP8x4JI9wPG+/t4hjdNJghmYJcKX4wh/g
33
github.com/aws/aws-sdk-go v1.27.2/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
44
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
55
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
6+
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
67
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
78
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
89
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
910
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
1011
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
12+
github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91 h1:Izz0+t1Z5nI16/II7vuEo/nHjodOg0p7+OiDpjX5t1E=
13+
github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
14+
github.com/dop251/goja v0.0.0-20210804101310-32956a348b49 h1:CtSi0QlA2Hy+nOh8JAZoiEBLW5pliAiKJ3l1Iq1472I=
15+
github.com/dop251/goja v0.0.0-20210804101310-32956a348b49/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk=
16+
github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA7c8pLvoGndExHudxTDKZ84Pyvv+90pbBjbTz0Y=
1117
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
1218
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
1319
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
1420
github.com/gbrlsnchs/jwt/v3 v3.0.0-rc.1 h1:/opyYiz6HZoBVAU8ypemFOTtzuKFE9kiKstP6RYE1Z4=
1521
github.com/gbrlsnchs/jwt/v3 v3.0.0-rc.1/go.mod h1:JEL7eYb4ETfz9AYni+/4BV09MrMgGwju0G/k4XF8QMg=
1622
github.com/go-redis/redis/v8 v8.4.4 h1:fGqgxCTR1sydaKI00oQf3OmkU/DIe/I/fYXvGklCIuc=
1723
github.com/go-redis/redis/v8 v8.4.4/go.mod h1:nA0bQuF0i5JFx4Ta9RZxGKXFrQ8cRWntra97f0196iY=
24+
github.com/go-sourcemap/sourcemap v2.1.3+incompatible h1:W1iEw64niKVGogNgBN3ePyLFfuisuzeidWPMPWmECqU=
25+
github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg=
1826
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
1927
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
2028
github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0=
@@ -75,11 +83,13 @@ github.com/klauspost/compress v1.13.1 h1:wXr2uRxZTJXHLly6qhJabee5JqIhTRoLBhDOA74
7583
github.com/klauspost/compress v1.13.1/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
7684
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
7785
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
78-
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
7986
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
87+
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
88+
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
8089
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
81-
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
8290
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
91+
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
92+
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
8393
github.com/magefile/mage v1.9.0 h1:t3AU2wNwehMCW97vuqQLtw6puppWXHO+O2MHo5a50XE=
8494
github.com/magefile/mage v1.9.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
8595
github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE=
@@ -203,16 +213,18 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi
203213
google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
204214
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
205215
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
206-
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
207216
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
217+
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
218+
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
208219
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
209220
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
210221
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
211222
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
212223
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
213224
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
214225
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
215-
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
216226
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
227+
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
228+
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
217229
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
218230
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

main_test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ const (
2828

2929
var (
3030
database *Database
31+
funexec *functions
3132
wsURL string
3233
pubKey string
3334
adminToken string
@@ -60,6 +61,8 @@ func TestMain(m *testing.M) {
6061
base: &db.Base{PublishDocument: volatile.PublishDocument},
6162
}
6263

64+
funexec = &functions{base: &db.Base{PublishDocument: volatile.PublishDocument}}
65+
6366
os.Exit(m.Run())
6467
}
6568

server.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,10 @@ func Start(dbHost, port string) {
138138
}
139139
http.Handle("/sse/msg", middleware.Chain(http.HandlerFunc(receiveMessage), pubWithDB...))
140140

141+
// server-side functions
142+
f := &functions{base: &db.Base{PublishDocument: volatile.PublishDocument}}
143+
http.Handle("/exec", middleware.Chain(http.HandlerFunc(f.exec), stdAuth...))
144+
141145
// ui routes
142146
webUI := ui{base: &db.Base{PublishDocument: volatile.PublishDocument}}
143147
http.HandleFunc("/ui/login", webUI.auth)

0 commit comments

Comments
 (0)