1
0
mirror of https://github.com/pocketbase/pocketbase.git synced 2025-01-10 00:43:36 +02:00
pocketbase/plugins/migratecmd/automigrate.go

141 lines
3.9 KiB
Go

package migratecmd
import (
"errors"
"fmt"
"os"
"os/exec"
"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"
const automigrateSuffix = "_automigrate"
// onCollectionChange handles the automigration snapshot generation on
// collection change event (create/update/delete).
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")
}
oldFiles, err := p.getAllMigrationNames()
if err != nil {
return fmt.Errorf("failed to fetch migration files 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)
}
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)
}
if err := os.WriteFile(fileDest, []byte(template), 0644); err != nil {
return fmt.Errorf("failed to save automigrate file: %v", err)
}
// remove the old untracked automigrate file
// (only if the last one was automigrate!)
if len(oldFiles) > 0 && strings.HasSuffix(oldFiles[len(oldFiles)-1], automigrateSuffix+"."+p.options.TemplateLang) {
olfName := oldFiles[len(oldFiles)-1]
oldPath := filepath.Join(p.options.Dir, olfName)
isUntracked := exec.Command(p.options.GitPath, "ls-files", "--error-unmatch", oldPath).Run() != nil
if isUntracked {
// delete the old automigrate from the db if it was already applied
_, err := p.app.Dao().DB().Delete(migrationsTable, dbx.HashExp{"file": olfName}).Execute()
if err != nil {
return fmt.Errorf("failed to delete last applied automigrate from the migration db: %v", err)
}
// delete the old automigrate file from the filesystem
if err := os.Remove(oldPath); err != nil && !os.IsNotExist(err) {
return fmt.Errorf("failed to delete last automigrates from the filesystem: %v", err)
}
}
}
return nil
}
}
// getAllMigrationNames return sorted slice with 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
}