155 lines
4.3 KiB
Go
155 lines
4.3 KiB
Go
|
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
|
||
|
}
|