merge v0.23.0-rc changes

This commit is contained in:
Gani Georgiev
2024-09-29 19:23:19 +03:00
parent ad92992324
commit 844f18cac3
753 changed files with 85141 additions and 63396 deletions
+263 -190
View File
@@ -12,29 +12,23 @@ import (
"os/exec"
"path/filepath"
"reflect"
"slices"
"strings"
"time"
"github.com/dop251/goja"
validation "github.com/go-ozzo/ozzo-validation/v4"
"github.com/labstack/echo/v5"
"github.com/labstack/echo/v5/middleware"
"github.com/golang-jwt/jwt/v4"
"github.com/pocketbase/dbx"
"github.com/pocketbase/pocketbase/apis"
"github.com/pocketbase/pocketbase/core"
"github.com/pocketbase/pocketbase/daos"
"github.com/pocketbase/pocketbase/forms"
"github.com/pocketbase/pocketbase/mails"
"github.com/pocketbase/pocketbase/models"
"github.com/pocketbase/pocketbase/models/schema"
"github.com/pocketbase/pocketbase/tokens"
"github.com/pocketbase/pocketbase/tools/cron"
"github.com/pocketbase/pocketbase/tools/filesystem"
"github.com/pocketbase/pocketbase/tools/hook"
"github.com/pocketbase/pocketbase/tools/inflector"
"github.com/pocketbase/pocketbase/tools/list"
"github.com/pocketbase/pocketbase/tools/mailer"
"github.com/pocketbase/pocketbase/tools/rest"
"github.com/pocketbase/pocketbase/tools/router"
"github.com/pocketbase/pocketbase/tools/security"
"github.com/pocketbase/pocketbase/tools/subscriptions"
"github.com/pocketbase/pocketbase/tools/types"
@@ -49,11 +43,11 @@ func hooksBinds(app core.App, loader *goja.Runtime, executors *vmsPool) {
appType := reflect.TypeOf(app)
appValue := reflect.ValueOf(app)
totalMethods := appType.NumMethod()
excludeHooks := []string{"OnBeforeServe"}
excludeHooks := []string{"OnServe"}
for i := 0; i < totalMethods; i++ {
method := appType.Method(i)
if !strings.HasPrefix(method.Name, "On") || list.ExistInSlice(method.Name, excludeHooks) {
if !strings.HasPrefix(method.Name, "On") || slices.Contains(excludeHooks, method.Name) {
continue // not a hook or excluded
}
@@ -69,9 +63,9 @@ func hooksBinds(app core.App, loader *goja.Runtime, executors *vmsPool) {
}
hookInstance := appValue.MethodByName(method.Name).Call(tagsAsValues)[0]
addFunc := hookInstance.MethodByName("Add")
hookBindFunc := hookInstance.MethodByName("BindFunc")
handlerType := addFunc.Type().In(0)
handlerType := hookBindFunc.Type().In(0)
handler := reflect.MakeFunc(handlerType, func(args []reflect.Value) (results []reflect.Value) {
handlerArgs := make([]any, len(args))
@@ -84,15 +78,10 @@ func hooksBinds(app core.App, loader *goja.Runtime, executors *vmsPool) {
res, err := executor.RunProgram(pr)
executor.Set("__args", goja.Undefined())
// check for returned error or false
// check for returned error value
if res != nil {
switch v := res.Export().(type) {
case error:
return v
case bool:
if !v {
return hook.StopPropagation
}
if resErr, ok := res.Export().(error); ok {
return resErr
}
}
@@ -103,20 +92,16 @@ func hooksBinds(app core.App, loader *goja.Runtime, executors *vmsPool) {
})
// register the wrapped hook handler
addFunc.Call([]reflect.Value{handler})
hookBindFunc.Call([]reflect.Value{handler})
})
}
}
func cronBinds(app core.App, loader *goja.Runtime, executors *vmsPool) {
scheduler := cron.New()
var wasServeTriggered bool
loader.Set("cronAdd", func(jobId, cronExpr, handler string) {
pr := goja.MustCompile("", "{("+handler+").apply(undefined)}", true)
err := scheduler.Add(jobId, cronExpr, func() {
err := app.Cron().Add(jobId, cronExpr, func() {
err := executors.run(func(executor *goja.Runtime) error {
_, err := executor.RunProgram(pr)
return err
@@ -133,32 +118,29 @@ func cronBinds(app core.App, loader *goja.Runtime, executors *vmsPool) {
if err != nil {
panic("[cronAdd] failed to register cron job " + jobId + ": " + err.Error())
}
// start the ticker (if not already)
if wasServeTriggered && scheduler.Total() > 0 && !scheduler.HasStarted() {
scheduler.Start()
}
})
// note: it is not necessary needed but it is here for consistency
loader.Set("cronRemove", func(jobId string) {
scheduler.Remove(jobId)
// stop the ticker if there are no other jobs
if scheduler.Total() == 0 {
scheduler.Stop()
}
app.Cron().Remove(jobId)
})
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
// start the ticker (if not already)
if scheduler.Total() > 0 && !scheduler.HasStarted() {
scheduler.Start()
}
// register the removal helper also in the executors to allow removing cron jobs from everywhere
oldFactory := executors.factory
executors.factory = func() *goja.Runtime {
vm := oldFactory()
wasServeTriggered = true
vm.Set("cronRemove", func(jobId string) {
app.Cron().Remove(jobId)
})
return nil
})
return vm
}
for _, item := range executors.items {
item.vm.Set("cronRemove", func(jobId string) {
app.Cron().Remove(jobId)
})
}
}
func routerBinds(app core.App, loader *goja.Runtime, executors *vmsPool) {
@@ -168,15 +150,15 @@ func routerBinds(app core.App, loader *goja.Runtime, executors *vmsPool) {
panic("[routerAdd] failed to wrap middlewares: " + err.Error())
}
wrappedHandler, err := wrapHandler(executors, handler)
wrappedHandler, err := wrapHandlerFunc(executors, handler)
if err != nil {
panic("[routerAdd] failed to wrap handler: " + err.Error())
}
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
e.Router.Add(strings.ToUpper(method), path, wrappedHandler, wrappedMiddlewares...)
app.OnServe().BindFunc(func(e *core.ServeEvent) error {
e.Router.Route(strings.ToUpper(method), path, wrappedHandler).Bind(wrappedMiddlewares...)
return nil
return e.Next()
})
})
@@ -186,40 +168,28 @@ func routerBinds(app core.App, loader *goja.Runtime, executors *vmsPool) {
panic("[routerUse] failed to wrap middlewares: " + err.Error())
}
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
e.Router.Use(wrappedMiddlewares...)
return nil
})
})
loader.Set("routerPre", func(middlewares ...goja.Value) {
wrappedMiddlewares, err := wrapMiddlewares(executors, middlewares...)
if err != nil {
panic("[routerPre] failed to wrap middlewares: " + err.Error())
}
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
e.Router.Pre(wrappedMiddlewares...)
return nil
app.OnServe().BindFunc(func(e *core.ServeEvent) error {
e.Router.Bind(wrappedMiddlewares...)
return e.Next()
})
})
}
func wrapHandler(executors *vmsPool, handler goja.Value) (echo.HandlerFunc, error) {
func wrapHandlerFunc(executors *vmsPool, handler goja.Value) (hook.HandlerFunc[*core.RequestEvent], error) {
if handler == nil {
return nil, errors.New("handler must be non-nil")
}
switch h := handler.Export().(type) {
case echo.HandlerFunc:
// "native" handler - no need to wrap
case hook.HandlerFunc[*core.RequestEvent]:
// "native" handler func - no need to wrap
return h, nil
case func(goja.FunctionCall) goja.Value, string:
pr := goja.MustCompile("", "{("+handler.String()+").apply(undefined, __args)}", true)
wrappedHandler := func(c echo.Context) error {
wrappedHandler := func(e *core.RequestEvent) error {
return executors.run(func(executor *goja.Runtime) error {
executor.Set("__args", []any{c})
executor.Set("__args", []any{e})
res, err := executor.RunProgram(pr)
executor.Set("__args", goja.Undefined())
@@ -240,29 +210,44 @@ func wrapHandler(executors *vmsPool, handler goja.Value) (echo.HandlerFunc, erro
}
}
func wrapMiddlewares(executors *vmsPool, rawMiddlewares ...goja.Value) ([]echo.MiddlewareFunc, error) {
wrappedMiddlewares := make([]echo.MiddlewareFunc, len(rawMiddlewares))
type gojaHookHandler struct {
priority int
id string
serializedFunc string
}
func wrapMiddlewares(executors *vmsPool, rawMiddlewares ...goja.Value) ([]*hook.Handler[*core.RequestEvent], error) {
wrappedMiddlewares := make([]*hook.Handler[*core.RequestEvent], len(rawMiddlewares))
for i, m := range rawMiddlewares {
if m == nil {
return nil, errors.New("middleware func must be non-nil")
return nil, errors.New("middleware must be non-nil")
}
switch v := m.Export().(type) {
case echo.MiddlewareFunc:
// "native" middleware - no need to wrap
case *hook.Handler[*core.RequestEvent]:
// "native" middleware handler - no need to wrap
wrappedMiddlewares[i] = v
case func(goja.FunctionCall) goja.Value, string:
pr := goja.MustCompile("", "{(("+m.String()+").apply(undefined, __args)).apply(undefined, __args2)}", true)
case hook.HandlerFunc[*core.RequestEvent]:
// "native" middleware func - wrap as handler
wrappedMiddlewares[i] = &hook.Handler[*core.RequestEvent]{
Func: v,
}
case *gojaHookHandler:
if v.serializedFunc == "" {
return nil, errors.New("missing or invalid Middleware function")
}
wrappedMiddlewares[i] = func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
pr := goja.MustCompile("", "{("+v.serializedFunc+").apply(undefined, __args)}", true)
wrappedMiddlewares[i] = &hook.Handler[*core.RequestEvent]{
Id: v.id,
Priority: v.priority,
Func: func(e *core.RequestEvent) error {
return executors.run(func(executor *goja.Runtime) error {
executor.Set("__args", []any{next})
executor.Set("__args2", []any{c})
executor.Set("__args", []any{e})
res, err := executor.RunProgram(pr)
executor.Set("__args", goja.Undefined())
executor.Set("__args2", goja.Undefined())
// check for returned error
if res != nil {
@@ -273,7 +258,28 @@ func wrapMiddlewares(executors *vmsPool, rawMiddlewares ...goja.Value) ([]echo.M
return err
})
}
},
}
case func(goja.FunctionCall) goja.Value, string:
pr := goja.MustCompile("", "{("+m.String()+").apply(undefined, __args)}", true)
wrappedMiddlewares[i] = &hook.Handler[*core.RequestEvent]{
Func: func(e *core.RequestEvent) error {
return executors.run(func(executor *goja.Runtime) error {
executor.Set("__args", []any{e})
res, err := executor.RunProgram(pr)
executor.Set("__args", goja.Undefined())
// check for returned error
if res != nil {
if v, ok := res.Export().(error); ok {
return v
}
}
return err
})
},
}
default:
return nil, errors.New("unsupported goja middleware type")
@@ -286,9 +292,10 @@ func wrapMiddlewares(executors *vmsPool, rawMiddlewares ...goja.Value) ([]echo.M
func baseBinds(vm *goja.Runtime) {
vm.SetFieldNameMapper(FieldMapper{})
// deprecated: use toString
vm.Set("readerToString", func(r io.Reader, maxBytes int) (string, error) {
if maxBytes == 0 {
maxBytes = rest.DefaultMaxMemory
maxBytes = router.DefaultMaxMemory
}
limitReader := io.LimitReader(r, int64(maxBytes))
@@ -301,6 +308,34 @@ func baseBinds(vm *goja.Runtime) {
return string(bodyBytes), nil
})
vm.Set("toString", func(raw any, maxReaderBytes int) (string, error) {
switch v := raw.(type) {
case io.Reader:
if maxReaderBytes == 0 {
maxReaderBytes = router.DefaultMaxMemory
}
limitReader := io.LimitReader(v, int64(maxReaderBytes))
bodyBytes, readErr := io.ReadAll(limitReader)
if readErr != nil {
return "", readErr
}
return string(bodyBytes), nil
default:
str, err := cast.ToStringE(v)
if err == nil {
return str, nil
}
// as a last attempt try to json encode the value
rawBytes, _ := json.Marshal(raw)
return string(rawBytes), nil
}
})
vm.Set("sleep", func(milliseconds int64) {
time.Sleep(time.Duration(milliseconds) * time.Millisecond)
})
@@ -313,6 +348,15 @@ func baseBinds(vm *goja.Runtime) {
return elem.Addr().Interface()
})
vm.Set("unmarshal", func(data, dst any) error {
raw, err := json.Marshal(data)
if err != nil {
return err
}
return json.Unmarshal(raw, &dst)
})
vm.Set("DynamicModel", func(call goja.ConstructorCall) *goja.Object {
shape, ok := call.Argument(0).Export().(map[string]any)
if !ok || len(shape) == 0 {
@@ -327,17 +371,17 @@ func baseBinds(vm *goja.Runtime) {
})
vm.Set("Record", func(call goja.ConstructorCall) *goja.Object {
var instance *models.Record
var instance *core.Record
collection, ok := call.Argument(0).Export().(*models.Collection)
collection, ok := call.Argument(0).Export().(*core.Collection)
if ok {
instance = models.NewRecord(collection)
instance = core.NewRecord(collection)
data, ok := call.Argument(1).Export().(map[string]any)
if ok {
instance.Load(data)
}
} else {
instance = &models.Record{}
instance = &core.Record{}
}
instanceValue := vm.ToValue(instance).(*goja.Object)
@@ -347,24 +391,91 @@ func baseBinds(vm *goja.Runtime) {
})
vm.Set("Collection", func(call goja.ConstructorCall) *goja.Object {
instance := &models.Collection{}
instance := &core.Collection{}
return structConstructorUnmarshal(vm, call, instance)
})
registerFactoryAsConstructor(vm, "BaseCollection", core.NewBaseCollection)
registerFactoryAsConstructor(vm, "AuthCollection", core.NewAuthCollection)
registerFactoryAsConstructor(vm, "ViewCollection", core.NewViewCollection)
vm.Set("FieldsList", func(call goja.ConstructorCall) *goja.Object {
instance := &core.FieldsList{}
return structConstructorUnmarshal(vm, call, instance)
})
vm.Set("Admin", func(call goja.ConstructorCall) *goja.Object {
instance := &models.Admin{}
return structConstructorUnmarshal(vm, call, instance)
})
// fields
// ---
vm.Set("Field", func(call goja.ConstructorCall) *goja.Object {
data, _ := call.Argument(0).Export().(map[string]any)
rawDataSlice, _ := json.Marshal([]any{data})
vm.Set("Schema", func(call goja.ConstructorCall) *goja.Object {
instance := &schema.Schema{}
return structConstructorUnmarshal(vm, call, instance)
})
fieldsList := core.NewFieldsList()
_ = fieldsList.UnmarshalJSON(rawDataSlice)
vm.Set("SchemaField", func(call goja.ConstructorCall) *goja.Object {
instance := &schema.SchemaField{}
if len(fieldsList) == 0 {
return nil
}
field := fieldsList[0]
fieldValue := vm.ToValue(field).(*goja.Object)
fieldValue.SetPrototype(call.This.Prototype())
return fieldValue
})
vm.Set("NumberField", func(call goja.ConstructorCall) *goja.Object {
instance := &core.NumberField{}
return structConstructorUnmarshal(vm, call, instance)
})
vm.Set("BoolField", func(call goja.ConstructorCall) *goja.Object {
instance := &core.BoolField{}
return structConstructorUnmarshal(vm, call, instance)
})
vm.Set("TextField", func(call goja.ConstructorCall) *goja.Object {
instance := &core.TextField{}
return structConstructorUnmarshal(vm, call, instance)
})
vm.Set("URLField", func(call goja.ConstructorCall) *goja.Object {
instance := &core.URLField{}
return structConstructorUnmarshal(vm, call, instance)
})
vm.Set("EmailField", func(call goja.ConstructorCall) *goja.Object {
instance := &core.EmailField{}
return structConstructorUnmarshal(vm, call, instance)
})
vm.Set("EditorField", func(call goja.ConstructorCall) *goja.Object {
instance := &core.EditorField{}
return structConstructorUnmarshal(vm, call, instance)
})
vm.Set("PasswordField", func(call goja.ConstructorCall) *goja.Object {
instance := &core.PasswordField{}
return structConstructorUnmarshal(vm, call, instance)
})
vm.Set("DateField", func(call goja.ConstructorCall) *goja.Object {
instance := &core.DateField{}
return structConstructorUnmarshal(vm, call, instance)
})
vm.Set("AutodateField", func(call goja.ConstructorCall) *goja.Object {
instance := &core.AutodateField{}
return structConstructorUnmarshal(vm, call, instance)
})
vm.Set("JSONField", func(call goja.ConstructorCall) *goja.Object {
instance := &core.JSONField{}
return structConstructorUnmarshal(vm, call, instance)
})
vm.Set("RelationField", func(call goja.ConstructorCall) *goja.Object {
instance := &core.RelationField{}
return structConstructorUnmarshal(vm, call, instance)
})
vm.Set("SelectField", func(call goja.ConstructorCall) *goja.Object {
instance := &core.SelectField{}
return structConstructorUnmarshal(vm, call, instance)
})
vm.Set("FileField", func(call goja.ConstructorCall) *goja.Object {
instance := &core.FileField{}
return structConstructorUnmarshal(vm, call, instance)
})
// ---
vm.Set("MailerMessage", func(call goja.ConstructorCall) *goja.Object {
instance := &mailer.Message{}
@@ -377,10 +488,28 @@ func baseBinds(vm *goja.Runtime) {
})
vm.Set("RequestInfo", func(call goja.ConstructorCall) *goja.Object {
instance := &models.RequestInfo{Context: models.RequestInfoContextDefault}
instance := &core.RequestInfo{Context: core.RequestInfoContextDefault}
return structConstructor(vm, call, instance)
})
// ```js
// new Middleware((e) => {
// return e.next()
// }, 100, "example_middleware")
// ```
vm.Set("Middleware", func(call goja.ConstructorCall) *goja.Object {
instance := &gojaHookHandler{}
instance.serializedFunc = call.Argument(0).String()
instance.priority = cast.ToInt(call.Argument(1).Export())
instance.id = cast.ToString(call.Argument(2).Export())
instanceValue := vm.ToValue(instance).(*goja.Object)
instanceValue.SetPrototype(call.This.Prototype())
return instanceValue
})
vm.Set("DateTime", func(call goja.ConstructorCall) *goja.Object {
instance := types.NowDateTime()
@@ -406,24 +535,6 @@ func baseBinds(vm *goja.Runtime) {
return instanceValue
})
vm.Set("Dao", func(call goja.ConstructorCall) *goja.Object {
concurrentDB, _ := call.Argument(0).Export().(dbx.Builder)
if concurrentDB == nil {
panic("[Dao] missing required Dao(concurrentDB, [nonconcurrentDB]) argument")
}
nonConcurrentDB, _ := call.Argument(1).Export().(dbx.Builder)
if nonConcurrentDB == nil {
nonConcurrentDB = concurrentDB
}
instance := daos.NewMultiDB(concurrentDB, nonConcurrentDB)
instanceValue := vm.ToValue(instance).(*goja.Object)
instanceValue.SetPrototype(call.This.Prototype())
return instanceValue
})
vm.Set("Cookie", func(call goja.ConstructorCall) *goja.Object {
instance := &http.Cookie{}
return structConstructor(vm, call, instance)
@@ -462,30 +573,10 @@ func mailsBinds(vm *goja.Runtime) {
obj := vm.NewObject()
vm.Set("$mails", obj)
// admin
obj.Set("sendAdminPasswordReset", mails.SendAdminPasswordReset)
// record
obj.Set("sendRecordPasswordReset", mails.SendRecordPasswordReset)
obj.Set("sendRecordVerification", mails.SendRecordVerification)
obj.Set("sendRecordChangeEmail", mails.SendRecordChangeEmail)
}
func tokensBinds(vm *goja.Runtime) {
obj := vm.NewObject()
vm.Set("$tokens", obj)
// admin
obj.Set("adminAuthToken", tokens.NewAdminAuthToken)
obj.Set("adminResetPasswordToken", tokens.NewAdminResetPasswordToken)
obj.Set("adminFileToken", tokens.NewAdminFileToken)
// record
obj.Set("recordAuthToken", tokens.NewRecordAuthToken)
obj.Set("recordVerifyToken", tokens.NewRecordVerifyToken)
obj.Set("recordResetPasswordToken", tokens.NewRecordResetPasswordToken)
obj.Set("recordChangeEmailToken", tokens.NewRecordChangeEmailToken)
obj.Set("recordFileToken", tokens.NewRecordFileToken)
obj.Set("sendRecordOTP", mails.SendRecordOTP)
}
func securityBinds(vm *goja.Runtime) {
@@ -502,6 +593,7 @@ func securityBinds(vm *goja.Runtime) {
// random
obj.Set("randomString", security.RandomString)
obj.Set("randomStringByRegex", security.RandomStringByRegex)
obj.Set("randomStringWithAlphabet", security.RandomStringWithAlphabet)
obj.Set("pseudorandomString", security.PseudorandomString)
obj.Set("pseudorandomStringWithAlphabet", security.PseudorandomStringWithAlphabet)
@@ -513,7 +605,9 @@ func securityBinds(vm *goja.Runtime) {
obj.Set("parseJWT", func(token string, verificationKey string) (map[string]any, error) {
return security.ParseJWT(token, verificationKey)
})
obj.Set("createJWT", security.NewJWT)
obj.Set("createJWT", func(payload jwt.MapClaims, signingKey string, secDuration int) (string, error) {
return security.NewJWT(payload, signingKey, time.Duration(secDuration)*time.Second)
})
// encryption
obj.Set("encrypt", security.Encrypt)
@@ -535,7 +629,7 @@ func filesystemBinds(vm *goja.Runtime) {
obj.Set("fileFromPath", filesystem.NewFileFromPath)
obj.Set("fileFromBytes", filesystem.NewFileFromBytes)
obj.Set("fileFromMultipart", filesystem.NewFileFromMultipart)
obj.Set("fileFromUrl", func(url string, secTimeout int) (*filesystem.File, error) {
obj.Set("fileFromURL", func(url string, secTimeout int) (*filesystem.File, error) {
if secTimeout == 0 {
secTimeout = 120
}
@@ -543,7 +637,7 @@ func filesystemBinds(vm *goja.Runtime) {
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(secTimeout)*time.Second)
defer cancel()
return filesystem.NewFileFromUrl(ctx, url)
return filesystem.NewFileFromURL(ctx, url)
})
}
@@ -592,24 +686,8 @@ func osBinds(vm *goja.Runtime) {
}
func formsBinds(vm *goja.Runtime) {
registerFactoryAsConstructor(vm, "AdminLoginForm", forms.NewAdminLogin)
registerFactoryAsConstructor(vm, "AdminPasswordResetConfirmForm", forms.NewAdminPasswordResetConfirm)
registerFactoryAsConstructor(vm, "AdminPasswordResetRequestForm", forms.NewAdminPasswordResetRequest)
registerFactoryAsConstructor(vm, "AdminUpsertForm", forms.NewAdminUpsert)
registerFactoryAsConstructor(vm, "AppleClientSecretCreateForm", forms.NewAppleClientSecretCreate)
registerFactoryAsConstructor(vm, "CollectionUpsertForm", forms.NewCollectionUpsert)
registerFactoryAsConstructor(vm, "CollectionsImportForm", forms.NewCollectionsImport)
registerFactoryAsConstructor(vm, "RealtimeSubscribeForm", forms.NewRealtimeSubscribe)
registerFactoryAsConstructor(vm, "RecordEmailChangeConfirmForm", forms.NewRecordEmailChangeConfirm)
registerFactoryAsConstructor(vm, "RecordEmailChangeRequestForm", forms.NewRecordEmailChangeRequest)
registerFactoryAsConstructor(vm, "RecordOAuth2LoginForm", forms.NewRecordOAuth2Login)
registerFactoryAsConstructor(vm, "RecordPasswordLoginForm", forms.NewRecordPasswordLogin)
registerFactoryAsConstructor(vm, "RecordPasswordResetConfirmForm", forms.NewRecordPasswordResetConfirm)
registerFactoryAsConstructor(vm, "RecordPasswordResetRequestForm", forms.NewRecordPasswordResetRequest)
registerFactoryAsConstructor(vm, "RecordUpsertForm", forms.NewRecordUpsert)
registerFactoryAsConstructor(vm, "RecordVerificationConfirmForm", forms.NewRecordVerificationConfirm)
registerFactoryAsConstructor(vm, "RecordVerificationRequestForm", forms.NewRecordVerificationRequest)
registerFactoryAsConstructor(vm, "SettingsUpsertForm", forms.NewSettingsUpsert)
registerFactoryAsConstructor(vm, "TestEmailSendForm", forms.NewTestEmailSend)
registerFactoryAsConstructor(vm, "TestS3FilesystemForm", forms.NewTestS3Filesystem)
}
@@ -618,33 +696,33 @@ func apisBinds(vm *goja.Runtime) {
obj := vm.NewObject()
vm.Set("$apis", obj)
obj.Set("staticDirectoryHandler", func(dir string, indexFallback bool) echo.HandlerFunc {
return apis.StaticDirectoryHandler(os.DirFS(dir), indexFallback)
obj.Set("static", func(dir string, indexFallback bool) hook.HandlerFunc[*core.RequestEvent] {
return apis.Static(os.DirFS(dir), indexFallback)
})
// middlewares
obj.Set("requireGuestOnly", apis.RequireGuestOnly)
obj.Set("requireRecordAuth", apis.RequireRecordAuth)
obj.Set("requireAdminAuth", apis.RequireAdminAuth)
obj.Set("requireAdminAuthOnlyIfAny", apis.RequireAdminAuthOnlyIfAny)
obj.Set("requireAdminOrRecordAuth", apis.RequireAdminOrRecordAuth)
obj.Set("requireAdminOrOwnerAuth", apis.RequireAdminOrOwnerAuth)
obj.Set("activityLogger", apis.ActivityLogger)
obj.Set("gzip", middleware.Gzip)
obj.Set("bodyLimit", middleware.BodyLimit)
obj.Set("requireAuth", apis.RequireAuth)
obj.Set("requireSuperuserAuth", apis.RequireSuperuserAuth)
obj.Set("requireSuperuserAuthOnlyIfAny", apis.RequireSuperuserAuthOnlyIfAny)
obj.Set("requireSuperuserOrOwnerAuth", apis.RequireSuperuserOrOwnerAuth)
obj.Set("skipSuccessActivityLog", apis.SkipSuccessActivityLog)
obj.Set("gzip", apis.Gzip)
obj.Set("bodyLimit", apis.BodyLimit)
// record helpers
obj.Set("requestInfo", apis.RequestInfo)
obj.Set("recordAuthResponse", apis.RecordAuthResponse)
obj.Set("enrichRecord", apis.EnrichRecord)
obj.Set("enrichRecords", apis.EnrichRecords)
// api errors
registerFactoryAsConstructor(vm, "ApiError", apis.NewApiError)
registerFactoryAsConstructor(vm, "NotFoundError", apis.NewNotFoundError)
registerFactoryAsConstructor(vm, "BadRequestError", apis.NewBadRequestError)
registerFactoryAsConstructor(vm, "ForbiddenError", apis.NewForbiddenError)
registerFactoryAsConstructor(vm, "UnauthorizedError", apis.NewUnauthorizedError)
registerFactoryAsConstructor(vm, "ApiError", router.NewApiError)
registerFactoryAsConstructor(vm, "NotFoundError", router.NewNotFoundError)
registerFactoryAsConstructor(vm, "BadRequestError", router.NewBadRequestError)
registerFactoryAsConstructor(vm, "ForbiddenError", router.NewForbiddenError)
registerFactoryAsConstructor(vm, "UnauthorizedError", router.NewUnauthorizedError)
registerFactoryAsConstructor(vm, "TooManyRequestsError", router.NewTooManyRequestsError)
registerFactoryAsConstructor(vm, "InternalServerError", router.NewInternalServerError)
}
func httpClientBinds(vm *goja.Runtime) {
@@ -661,7 +739,7 @@ func httpClientBinds(vm *goja.Runtime) {
})
type sendResult struct {
Json any `json:"json"`
JSON any `json:"json"`
Headers map[string][]string `json:"headers"`
Cookies map[string]*http.Cookie `json:"cookies"`
Raw string `json:"raw"`
@@ -727,6 +805,8 @@ func httpClientBinds(vm *goja.Runtime) {
reqBody = bytes.NewReader(encoded)
} else {
switch v := config.Body.(type) {
case io.Reader:
reqBody = v
case FormData:
body, mp, err := v.toMultipart()
if err != nil {
@@ -755,13 +835,6 @@ func httpClientBinds(vm *goja.Runtime) {
req.Header.Set("content-type", contentType)
}
// @todo consider removing during the refactoring
//
// fallback to json content-type
if req.Header.Get("content-type") == "" {
req.Header.Set("content-type", "application/json")
}
res, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
@@ -787,12 +860,12 @@ func httpClientBinds(vm *goja.Runtime) {
if len(result.Raw) != 0 {
// try as map
result.Json = map[string]any{}
if err := json.Unmarshal(bodyRaw, &result.Json); err != nil {
result.JSON = map[string]any{}
if err := json.Unmarshal(bodyRaw, &result.JSON); err != nil {
// try as slice
result.Json = []any{}
if err := json.Unmarshal(bodyRaw, &result.Json); err != nil {
result.Json = nil
result.JSON = []any{}
if err := json.Unmarshal(bodyRaw, &result.JSON); err != nil {
result.JSON = nil
}
}
}
@@ -864,7 +937,7 @@ func structConstructor(vm *goja.Runtime, call goja.ConstructorCall, instance any
func structConstructorUnmarshal(vm *goja.Runtime, call goja.ConstructorCall, instance any) *goja.Object {
if data := call.Argument(0).Export(); data != nil {
if raw, err := json.Marshal(data); err == nil {
json.Unmarshal(raw, instance)
_ = json.Unmarshal(raw, instance)
}
}
@@ -893,13 +966,13 @@ func newDynamicModel(shape map[string]any) any {
switch kind := vt.Kind(); kind {
case reflect.Map:
raw, _ := json.Marshal(v)
newV := types.JsonMap{}
newV := types.JSONMap[any]{}
newV.Scan(raw)
v = newV
vt = reflect.TypeOf(v)
case reflect.Slice, reflect.Array:
raw, _ := json.Marshal(v)
newV := types.JsonArray[any]{}
newV := types.JSONArray[any]{}
newV.Scan(raw)
v = newV
vt = reflect.TypeOf(newV)