added plugins subpackage and added basic support for js migrations
This commit is contained in:
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
package migratecmd
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pocketbase/dbx"
|
||||
"github.com/pocketbase/pocketbase/core"
|
||||
m "github.com/pocketbase/pocketbase/migrations"
|
||||
"github.com/pocketbase/pocketbase/models"
|
||||
"github.com/pocketbase/pocketbase/tools/list"
|
||||
)
|
||||
|
||||
const migrationsTable = "_migrations"
|
||||
|
||||
// tidyMigrationsTable cleanups the migrations table by removing all
|
||||
// entries with deleted migration files.
|
||||
func (p *plugin) tidyMigrationsTable() error {
|
||||
names, filesErr := p.getAllMigrationNames()
|
||||
if filesErr != nil {
|
||||
return fmt.Errorf("failed to fetch migration files list: %v", filesErr)
|
||||
}
|
||||
|
||||
_, tidyErr := p.app.Dao().DB().Delete(migrationsTable, dbx.NotIn("file", list.ToInterfaceSlice(names)...)).Execute()
|
||||
if tidyErr != nil {
|
||||
return fmt.Errorf("failed to delete last automigrates from the db: %v", tidyErr)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *plugin) onCollectionChange() func(*core.ModelEvent) error {
|
||||
return func(e *core.ModelEvent) error {
|
||||
if e.Model.TableName() != "_collections" {
|
||||
return nil // not a collection
|
||||
}
|
||||
|
||||
collections := []*models.Collection{}
|
||||
if err := p.app.Dao().CollectionQuery().OrderBy("created ASC").All(&collections); err != nil {
|
||||
return fmt.Errorf("failed to fetch collections list: %v", err)
|
||||
}
|
||||
if len(collections) == 0 {
|
||||
return errors.New("missing collections to automigrate")
|
||||
}
|
||||
|
||||
names, err := p.getAllMigrationNames()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to fetch migration files list: %v", err)
|
||||
}
|
||||
|
||||
// delete last consequitive automigrates
|
||||
lastAutomigrates := []string{}
|
||||
for i := len(names) - 1; i >= 0; i-- {
|
||||
migrationFile := names[i]
|
||||
if !strings.Contains(migrationFile, "_automigrate.") {
|
||||
break
|
||||
}
|
||||
lastAutomigrates = append(lastAutomigrates, migrationFile)
|
||||
}
|
||||
if len(lastAutomigrates) > 0 {
|
||||
// delete last automigrates from the db
|
||||
_, err := p.app.Dao().DB().Delete(migrationsTable, dbx.In("file", list.ToInterfaceSlice(lastAutomigrates)...)).Execute()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete last automigrates from the db: %v", err)
|
||||
}
|
||||
|
||||
// delete last automigrates from the filesystem
|
||||
for _, f := range lastAutomigrates {
|
||||
if err := os.Remove(filepath.Join(p.options.Dir, f)); err != nil && !os.IsNotExist(err) {
|
||||
return fmt.Errorf("failed to delete last automigrates from the filesystem: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var template string
|
||||
var templateErr error
|
||||
if p.options.TemplateLang == TemplateLangJS {
|
||||
template, templateErr = p.jsSnapshotTemplate(collections)
|
||||
} else {
|
||||
template, templateErr = p.goSnapshotTemplate(collections)
|
||||
}
|
||||
if templateErr != nil {
|
||||
return fmt.Errorf("failed to resolve template: %v", templateErr)
|
||||
}
|
||||
|
||||
// add a comment to not edit the template
|
||||
template = ("// Do not edit by hand since this file is autogenerated and may get overwritten.\n" +
|
||||
"// If you want to do further changes, create a new non '_automigrate' file instead.\n" + template)
|
||||
|
||||
appliedTime := time.Now().Unix()
|
||||
fileDest := filepath.Join(p.options.Dir, fmt.Sprintf("%d_automigrate.%s", appliedTime, p.options.TemplateLang))
|
||||
|
||||
// ensure that the local migrations dir exist
|
||||
if err := os.MkdirAll(p.options.Dir, os.ModePerm); err != nil {
|
||||
return fmt.Errorf("failed to create migration dir: %v", err)
|
||||
}
|
||||
|
||||
return os.WriteFile(fileDest, []byte(template), 0644)
|
||||
}
|
||||
}
|
||||
|
||||
// getAllMigrationNames return both applied and new local migration file names.
|
||||
func (p *plugin) getAllMigrationNames() ([]string, error) {
|
||||
names := []string{}
|
||||
|
||||
for _, migration := range m.AppMigrations.Items() {
|
||||
names = append(names, migration.File)
|
||||
}
|
||||
|
||||
localFiles, err := p.getLocalMigrationNames()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, name := range localFiles {
|
||||
if !list.ExistInSlice(name, names) {
|
||||
names = append(names, name)
|
||||
}
|
||||
}
|
||||
|
||||
sort.Slice(names, func(i int, j int) bool {
|
||||
return names[i] < names[j]
|
||||
})
|
||||
|
||||
return names, nil
|
||||
}
|
||||
|
||||
// getLocalMigrationNames returns a list with all local migration files
|
||||
//
|
||||
// Returns an empty slice if the migrations directory doesn't exist.
|
||||
func (p *plugin) getLocalMigrationNames() ([]string, error) {
|
||||
files, err := os.ReadDir(p.options.Dir)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return []string{}, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := make([]string, 0, len(files))
|
||||
|
||||
for _, f := range files {
|
||||
if f.IsDir() {
|
||||
continue
|
||||
}
|
||||
result = append(result, f.Name())
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
package migratecmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/pocketbase/pocketbase/core"
|
||||
"github.com/pocketbase/pocketbase/migrations"
|
||||
"github.com/pocketbase/pocketbase/models"
|
||||
"github.com/pocketbase/pocketbase/tools/inflector"
|
||||
"github.com/pocketbase/pocketbase/tools/migrate"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type Options struct {
|
||||
Dir string // the directory with user defined migrations
|
||||
AutoMigrate bool
|
||||
TemplateLang string
|
||||
}
|
||||
|
||||
type plugin struct {
|
||||
app core.App
|
||||
options *Options
|
||||
}
|
||||
|
||||
func MustRegister(app core.App, rootCmd *cobra.Command, options *Options) {
|
||||
if err := Register(app, rootCmd, options); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func Register(app core.App, rootCmd *cobra.Command, options *Options) error {
|
||||
p := &plugin{app: app}
|
||||
|
||||
if options != nil {
|
||||
p.options = options
|
||||
} else {
|
||||
p.options = &Options{}
|
||||
}
|
||||
|
||||
if p.options.TemplateLang == "" {
|
||||
p.options.TemplateLang = TemplateLangGo
|
||||
}
|
||||
|
||||
if p.options.Dir == "" {
|
||||
if p.options.TemplateLang == TemplateLangJS {
|
||||
p.options.Dir = filepath.Join(p.app.DataDir(), "../pb_migrations")
|
||||
} else {
|
||||
p.options.Dir = filepath.Join(p.app.DataDir(), "../migrations")
|
||||
}
|
||||
}
|
||||
|
||||
// attach the migrate command
|
||||
if rootCmd != nil {
|
||||
rootCmd.AddCommand(p.createCommand())
|
||||
}
|
||||
|
||||
// watch for collection changes
|
||||
if p.options.AutoMigrate {
|
||||
// @todo replace with AfterBootstrap
|
||||
p.app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
|
||||
if err := p.tidyMigrationsTable(); err != nil && p.app.IsDebug() {
|
||||
log.Println("Failed to tidy the migrations table.")
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
p.app.OnModelAfterCreate().Add(p.onCollectionChange())
|
||||
p.app.OnModelAfterUpdate().Add(p.onCollectionChange())
|
||||
p.app.OnModelAfterDelete().Add(p.onCollectionChange())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *plugin) createCommand() *cobra.Command {
|
||||
const cmdDesc = `Supported arguments are:
|
||||
- up - runs all available migrations.
|
||||
- down [number] - reverts the last [number] applied migrations.
|
||||
- create name [folder] - creates new blank migration template file.
|
||||
- collections [folder] - creates new migration file with the latest local collections snapshot (similar to the automigrate but allows editing).
|
||||
`
|
||||
|
||||
command := &cobra.Command{
|
||||
Use: "migrate",
|
||||
Short: "Executes app DB migration scripts",
|
||||
ValidArgs: []string{"up", "down", "create", "collections"},
|
||||
Long: cmdDesc,
|
||||
Run: func(command *cobra.Command, args []string) {
|
||||
cmd := ""
|
||||
if len(args) > 0 {
|
||||
cmd = args[0]
|
||||
}
|
||||
|
||||
// additional commands
|
||||
// ---
|
||||
if cmd == "create" {
|
||||
if err := p.migrateCreateHandler("", args[1:]); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if cmd == "collections" {
|
||||
if err := p.migrateCollectionsHandler(args[1:]); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
// ---
|
||||
|
||||
runner, err := migrate.NewRunner(p.app.DB(), migrations.AppMigrations)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if err := runner.Run(args...); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
return command
|
||||
}
|
||||
|
||||
func (p *plugin) migrateCreateHandler(template string, args []string) error {
|
||||
if len(args) < 1 {
|
||||
return fmt.Errorf("Missing migration file name")
|
||||
}
|
||||
|
||||
name := args[0]
|
||||
|
||||
var dir string
|
||||
if len(args) == 2 {
|
||||
dir = args[1]
|
||||
}
|
||||
if dir == "" {
|
||||
dir = p.options.Dir
|
||||
}
|
||||
|
||||
resultFilePath := path.Join(
|
||||
dir,
|
||||
fmt.Sprintf("%d_%s.%s", time.Now().Unix(), inflector.Snakecase(name), p.options.TemplateLang),
|
||||
)
|
||||
|
||||
confirm := false
|
||||
prompt := &survey.Confirm{
|
||||
Message: fmt.Sprintf("Do you really want to create migration %q?", resultFilePath),
|
||||
}
|
||||
survey.AskOne(prompt, &confirm)
|
||||
if !confirm {
|
||||
fmt.Println("The command has been cancelled")
|
||||
return nil
|
||||
}
|
||||
|
||||
// get default create template
|
||||
if template == "" {
|
||||
var templateErr error
|
||||
if p.options.TemplateLang == TemplateLangJS {
|
||||
template, templateErr = p.jsCreateTemplate()
|
||||
} else {
|
||||
template, templateErr = p.goCreateTemplate()
|
||||
}
|
||||
if templateErr != nil {
|
||||
return fmt.Errorf("Failed to resolve create template: %v\n", templateErr)
|
||||
}
|
||||
}
|
||||
|
||||
// ensure that the migrations dir exist
|
||||
if err := os.MkdirAll(dir, os.ModePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// save the migration file
|
||||
if err := os.WriteFile(resultFilePath, []byte(template), 0644); err != nil {
|
||||
return fmt.Errorf("Failed to save migration file %q: %v\n", resultFilePath, err)
|
||||
}
|
||||
|
||||
fmt.Printf("Successfully created file %q\n", resultFilePath)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *plugin) migrateCollectionsHandler(args []string) error {
|
||||
createArgs := []string{"collections_snapshot"}
|
||||
createArgs = append(createArgs, args...)
|
||||
|
||||
collections := []*models.Collection{}
|
||||
if err := p.app.Dao().CollectionQuery().OrderBy("created ASC").All(&collections); err != nil {
|
||||
return fmt.Errorf("Failed to fetch migrations list: %v", err)
|
||||
}
|
||||
|
||||
var template string
|
||||
var templateErr error
|
||||
if p.options.TemplateLang == TemplateLangJS {
|
||||
template, templateErr = p.jsSnapshotTemplate(collections)
|
||||
} else {
|
||||
template, templateErr = p.goSnapshotTemplate(collections)
|
||||
}
|
||||
if templateErr != nil {
|
||||
return fmt.Errorf("Failed to resolve template: %v", templateErr)
|
||||
}
|
||||
|
||||
return p.migrateCreateHandler(template, createArgs)
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
package migratecmd
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/pocketbase/pocketbase/models"
|
||||
)
|
||||
|
||||
const (
|
||||
TemplateLangJS = "js"
|
||||
TemplateLangGo = "go"
|
||||
)
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// JavaScript templates
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
func (p *plugin) jsCreateTemplate() (string, error) {
|
||||
const template = `migrate((db) => {
|
||||
// add up queries...
|
||||
}, (db) => {
|
||||
// add down queries...
|
||||
})
|
||||
`
|
||||
|
||||
return template, nil
|
||||
}
|
||||
|
||||
func (p *plugin) jsSnapshotTemplate(collections []*models.Collection) (string, error) {
|
||||
jsonData, err := json.MarshalIndent(collections, " ", " ")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to serialize collections list: %v", err)
|
||||
}
|
||||
|
||||
const template = `migrate((db) => {
|
||||
const snapshot = %s;
|
||||
|
||||
const collections = snapshot.map((item) => unmarshal(item, new Collection()));
|
||||
|
||||
return Dao(db).importCollections(collections, true, null);
|
||||
}, (db) => {
|
||||
return null;
|
||||
})
|
||||
`
|
||||
|
||||
return fmt.Sprintf(template, string(jsonData)), nil
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// Go templates
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
func (p *plugin) goCreateTemplate() (string, error) {
|
||||
const template = `package %s
|
||||
|
||||
import (
|
||||
"github.com/pocketbase/dbx"
|
||||
m "github.com/pocketbase/pocketbase/migrations"
|
||||
)
|
||||
|
||||
func init() {
|
||||
m.Register(func(db dbx.Builder) error {
|
||||
// add up queries...
|
||||
|
||||
return nil
|
||||
}, func(db dbx.Builder) error {
|
||||
// add down queries...
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
`
|
||||
|
||||
return fmt.Sprintf(template, filepath.Base(p.options.Dir)), nil
|
||||
}
|
||||
|
||||
func (p *plugin) goSnapshotTemplate(collections []*models.Collection) (string, error) {
|
||||
jsonData, err := json.MarshalIndent(collections, "\t", "\t\t")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to serialize collections list: %v", err)
|
||||
}
|
||||
|
||||
const template = `package %s
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/pocketbase/dbx"
|
||||
"github.com/pocketbase/pocketbase/daos"
|
||||
m "github.com/pocketbase/pocketbase/migrations"
|
||||
"github.com/pocketbase/pocketbase/models"
|
||||
)
|
||||
|
||||
func init() {
|
||||
m.Register(func(db dbx.Builder) error {
|
||||
jsonData := ` + "`%s`" + `
|
||||
|
||||
collections := []*models.Collection{}
|
||||
if err := json.Unmarshal([]byte(jsonData), &collections); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return daos.New(db).ImportCollections(collections, true, nil)
|
||||
}, func(db dbx.Builder) error {
|
||||
return nil
|
||||
})
|
||||
}
|
||||
`
|
||||
return fmt.Sprintf(template, filepath.Base(p.options.Dir), string(jsonData)), nil
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
// Example
|
||||
//
|
||||
// publicdir.MustRegister(app, &publicdir.Options{
|
||||
// FlagsCmd: app.RootCmd,
|
||||
// IndexFallback: false,
|
||||
// })
|
||||
package publicdir
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/pocketbase/pocketbase/apis"
|
||||
"github.com/pocketbase/pocketbase/core"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type Options struct {
|
||||
Dir string
|
||||
IndexFallback bool
|
||||
FlagsCmd *cobra.Command
|
||||
}
|
||||
|
||||
type plugin struct {
|
||||
app core.App
|
||||
options *Options
|
||||
}
|
||||
|
||||
func MustRegister(app core.App, options *Options) {
|
||||
if err := Register(app, options); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func Register(app core.App, options *Options) error {
|
||||
p := &plugin{app: app}
|
||||
|
||||
if options != nil {
|
||||
p.options = options
|
||||
} else {
|
||||
p.options = &Options{}
|
||||
}
|
||||
|
||||
if options.Dir == "" {
|
||||
options.Dir = defaultPublicDir()
|
||||
}
|
||||
|
||||
if options.FlagsCmd != nil {
|
||||
// add "--publicDir" option flag
|
||||
options.FlagsCmd.PersistentFlags().StringVar(
|
||||
&options.Dir,
|
||||
"publicDir",
|
||||
options.Dir,
|
||||
"the directory to serve static files",
|
||||
)
|
||||
|
||||
// add "--indexFallback" option flag
|
||||
options.FlagsCmd.PersistentFlags().BoolVar(
|
||||
&options.IndexFallback,
|
||||
"indexFallback",
|
||||
options.IndexFallback,
|
||||
"fallback the request to index.html on missing static path (eg. when pretty urls are used with SPA)",
|
||||
)
|
||||
}
|
||||
|
||||
p.app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
|
||||
// serves static files from the provided public dir (if exists)
|
||||
e.Router.GET("/*", apis.StaticDirectoryHandler(os.DirFS(options.Dir), options.IndexFallback))
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func defaultPublicDir() string {
|
||||
if strings.HasPrefix(os.Args[0], os.TempDir()) {
|
||||
// most likely ran with go run
|
||||
return "./pb_public"
|
||||
}
|
||||
|
||||
return filepath.Join(os.Args[0], "../pb_public")
|
||||
}
|
||||
Reference in New Issue
Block a user