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 }