forked from pocketbase/pocketbase
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
added plugins subpackage and added basic support for js migrations
- Loading branch information
1 parent
3e1a196
commit d8963c6
Showing
19 changed files
with
889 additions
and
120 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
package jsvm | ||
|
||
import ( | ||
"os" | ||
"path/filepath" | ||
|
||
"github.com/dop251/goja_nodejs/console" | ||
"github.com/dop251/goja_nodejs/require" | ||
"github.com/pocketbase/dbx" | ||
"github.com/pocketbase/pocketbase/core" | ||
m "github.com/pocketbase/pocketbase/migrations" | ||
) | ||
|
||
// MigrationsLoaderOptions defines optional struct to customize the default plugin behavior. | ||
type MigrationsLoaderOptions struct { | ||
// Dir is the app migrations directory from where the js files will be loaded | ||
// (default to pb_data/migrations) | ||
Dir string | ||
} | ||
|
||
// migrationsLoader is the plugin definition. | ||
// Usually it is instantiated via RegisterMigrationsLoader or MustRegisterMigrationsLoader. | ||
type migrationsLoader struct { | ||
app core.App | ||
options *MigrationsLoaderOptions | ||
} | ||
|
||
// | ||
// MustRegisterMigrationsLoader registers the plugin to the provided | ||
// app instance and panics if it fails. | ||
// | ||
// It it calls RegisterMigrationsLoader(app, options) | ||
// | ||
// If options is nil, by default the js files from pb_data/migrations are loaded. | ||
// Set custom options.Dir if you want to change it to some other directory. | ||
func MustRegisterMigrationsLoader(app core.App, options *MigrationsLoaderOptions) { | ||
if err := RegisterMigrationsLoader(app, options); err != nil { | ||
panic(err) | ||
} | ||
} | ||
|
||
// RegisterMigrationsLoader registers the plugin to the provided app instance. | ||
// | ||
// If options is nil, by default the js files from pb_data/migrations are loaded. | ||
// Set custom options.Dir if you want to change it to some other directory. | ||
func RegisterMigrationsLoader(app core.App, options *MigrationsLoaderOptions) error { | ||
l := &migrationsLoader{app: app} | ||
|
||
if options != nil { | ||
l.options = options | ||
} else { | ||
l.options = &MigrationsLoaderOptions{} | ||
} | ||
|
||
if l.options.Dir == "" { | ||
l.options.Dir = filepath.Join(app.DataDir(), "../pb_migrations") | ||
} | ||
|
||
files, err := readDirFiles(l.options.Dir) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
registry := new(require.Registry) // this can be shared by multiple runtimes | ||
|
||
for file, content := range files { | ||
vm := NewBaseVM(l.app) | ||
registry.Enable(vm) | ||
console.Enable(vm) | ||
|
||
vm.Set("migrate", func(up, down func(db dbx.Builder) error) { | ||
m.AppMigrations.Register(up, down, file) | ||
}) | ||
|
||
_, err := vm.RunString(string(content)) | ||
if err != nil { | ||
return err | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// readDirFiles returns a map with all directory files and their content. | ||
// | ||
// If directory with dirPath is missing, it returns an empty map and no error. | ||
func readDirFiles(dirPath string) (map[string][]byte, error) { | ||
files, err := os.ReadDir(dirPath) | ||
if err != nil { | ||
if os.IsNotExist(err) { | ||
return map[string][]byte{}, nil | ||
} | ||
return nil, err | ||
} | ||
|
||
result := map[string][]byte{} | ||
|
||
for _, f := range files { | ||
if f.IsDir() { | ||
continue | ||
} | ||
raw, err := os.ReadFile(filepath.Join(dirPath, f.Name())) | ||
if err != nil { | ||
return nil, err | ||
} | ||
result[f.Name()] = raw | ||
} | ||
|
||
return result, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
package jsvm | ||
|
||
import ( | ||
"encoding/json" | ||
|
||
"github.com/dop251/goja" | ||
"github.com/pocketbase/dbx" | ||
"github.com/pocketbase/pocketbase/apis" | ||
"github.com/pocketbase/pocketbase/core" | ||
"github.com/pocketbase/pocketbase/daos" | ||
"github.com/pocketbase/pocketbase/models" | ||
) | ||
|
||
func NewBaseVM(app core.App) *goja.Runtime { | ||
vm := goja.New() | ||
vm.SetFieldNameMapper(goja.UncapFieldNameMapper()) | ||
|
||
vm.Set("$app", app) | ||
|
||
vm.Set("unmarshal", func(src map[string]any, dest any) (any, error) { | ||
raw, err := json.Marshal(src) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
if err := json.Unmarshal(raw, &dest); err != nil { | ||
return nil, err | ||
} | ||
|
||
return dest, nil | ||
}) | ||
|
||
collectionConstructor(vm) | ||
recordConstructor(vm) | ||
adminConstructor(vm) | ||
daoConstructor(vm) | ||
dbxBinds(vm) | ||
|
||
return vm | ||
} | ||
|
||
func collectionConstructor(vm *goja.Runtime) { | ||
vm.Set("Collection", func(call goja.ConstructorCall) *goja.Object { | ||
instance := &models.Collection{} | ||
instanceValue := vm.ToValue(instance).(*goja.Object) | ||
instanceValue.SetPrototype(call.This.Prototype()) | ||
return instanceValue | ||
}) | ||
} | ||
|
||
func recordConstructor(vm *goja.Runtime) { | ||
vm.Set("Record", func(call goja.ConstructorCall) *goja.Object { | ||
instance := &models.Record{} | ||
instanceValue := vm.ToValue(instance).(*goja.Object) | ||
instanceValue.SetPrototype(call.This.Prototype()) | ||
return instanceValue | ||
}) | ||
} | ||
|
||
func adminConstructor(vm *goja.Runtime) { | ||
vm.Set("Admin", func(call goja.ConstructorCall) *goja.Object { | ||
instance := &models.Admin{} | ||
instanceValue := vm.ToValue(instance).(*goja.Object) | ||
instanceValue.SetPrototype(call.This.Prototype()) | ||
return instanceValue | ||
}) | ||
} | ||
|
||
func daoConstructor(vm *goja.Runtime) { | ||
vm.Set("Dao", func(call goja.ConstructorCall) *goja.Object { | ||
db, ok := call.Argument(0).Export().(dbx.Builder) | ||
if !ok || db == nil { | ||
panic("missing required Dao(db) argument") | ||
} | ||
|
||
instance := daos.New(db) | ||
instanceValue := vm.ToValue(instance).(*goja.Object) | ||
instanceValue.SetPrototype(call.This.Prototype()) | ||
return instanceValue | ||
}) | ||
} | ||
|
||
func dbxBinds(vm *goja.Runtime) { | ||
obj := vm.NewObject() | ||
vm.Set("$dbx", obj) | ||
|
||
obj.Set("exp", dbx.NewExp) | ||
obj.Set("hashExp", func(data map[string]any) dbx.HashExp { | ||
exp := dbx.HashExp{} | ||
for k, v := range data { | ||
exp[k] = v | ||
} | ||
return exp | ||
}) | ||
obj.Set("not", dbx.Not) | ||
obj.Set("and", dbx.And) | ||
obj.Set("or", dbx.Or) | ||
obj.Set("in", dbx.In) | ||
obj.Set("notIn", dbx.NotIn) | ||
obj.Set("like", dbx.Like) | ||
obj.Set("orLike", dbx.OrLike) | ||
obj.Set("notLike", dbx.NotLike) | ||
obj.Set("orNotLike", dbx.OrNotLike) | ||
obj.Set("exists", dbx.Exists) | ||
obj.Set("notExists", dbx.NotExists) | ||
obj.Set("between", dbx.Between) | ||
obj.Set("notBetween", dbx.NotBetween) | ||
} | ||
|
||
func apisBind(vm *goja.Runtime) { | ||
obj := vm.NewObject() | ||
vm.Set("$apis", obj) | ||
|
||
// middlewares | ||
obj.Set("requireRecordAuth", apis.RequireRecordAuth) | ||
obj.Set("requireRecordAuth", apis.RequireRecordAuth) | ||
obj.Set("requireSameContextRecordAuth", apis.RequireSameContextRecordAuth) | ||
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) | ||
|
||
// api errors | ||
obj.Set("notFoundError", apis.NewNotFoundError) | ||
obj.Set("badRequestError", apis.NewBadRequestError) | ||
obj.Set("forbiddenError", apis.NewForbiddenError) | ||
obj.Set("unauthorizedError", apis.NewUnauthorizedError) | ||
|
||
// record helpers | ||
obj.Set("getRequestData", apis.GetRequestData) | ||
obj.Set("requestData", apis.RequestData) | ||
obj.Set("enrichRecord", apis.EnrichRecord) | ||
obj.Set("enrichRecords", apis.EnrichRecords) | ||
} |
Oops, something went wrong.