| 
									
										
										
										
											2022-12-05 22:57:39 +02:00
										 |  |  | // Package migratecmd adds a new "migrate" command support to a PocketBase instance. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // It also comes with automigrations support and templates generation | 
					
						
							|  |  |  | // (both for JS and GO migration files). | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // Example usage: | 
					
						
							|  |  |  | // | 
					
						
							| 
									
										
										
										
											2023-06-08 17:59:08 +03:00
										 |  |  | //	migratecmd.MustRegister(app, app.RootCmd, migratecmd.Config{ | 
					
						
							| 
									
										
										
										
											2023-02-23 21:51:42 +02:00
										 |  |  | //		TemplateLang: migratecmd.TemplateLangJS, // default to migratecmd.TemplateLangGo | 
					
						
							|  |  |  | //		Automigrate:  true, | 
					
						
							| 
									
										
										
										
											2023-06-08 17:59:08 +03:00
										 |  |  | //		Dir:          "/custom/migrations/dir", // optional template migrations path; default to "pb_migrations" (for JS) and "migrations" (for Go) | 
					
						
							| 
									
										
										
										
											2023-02-23 21:51:42 +02:00
										 |  |  | //	}) | 
					
						
							| 
									
										
										
										
											2022-12-05 22:57:39 +02:00
										 |  |  | // | 
					
						
							| 
									
										
										
										
											2023-02-23 21:51:42 +02:00
										 |  |  | //	Note: To allow running JS migrations you'll need to enable first | 
					
						
							| 
									
										
										
										
											2023-06-28 22:54:13 +03:00
										 |  |  | //	[jsvm.MustRegister()]. | 
					
						
							| 
									
										
										
										
											2022-11-26 09:05:52 +02:00
										 |  |  | package migratecmd | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"fmt" | 
					
						
							|  |  |  | 	"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" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-08 17:59:08 +03:00
										 |  |  | // Config defines the config options of the migratecmd plugin. | 
					
						
							|  |  |  | type Config struct { | 
					
						
							| 
									
										
										
										
											2022-11-26 22:33:27 +02:00
										 |  |  | 	// Dir specifies the directory with the user defined migrations. | 
					
						
							|  |  |  | 	// | 
					
						
							|  |  |  | 	// If not set it fallbacks to a relative "pb_data/../pb_migrations" (for js) | 
					
						
							|  |  |  | 	// or "pb_data/../migrations" (for go) directory. | 
					
						
							|  |  |  | 	Dir string | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Automigrate specifies whether to enable automigrations. | 
					
						
							|  |  |  | 	Automigrate bool | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// TemplateLang specifies the template language to use when | 
					
						
							|  |  |  | 	// generating migrations - js or go (default). | 
					
						
							| 
									
										
										
										
											2022-11-26 09:05:52 +02:00
										 |  |  | 	TemplateLang string | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-08 17:59:08 +03:00
										 |  |  | // MustRegister registers the migratecmd plugin to the provided app instance | 
					
						
							|  |  |  | // and panic if it fails. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // Example usage: | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | //	migratecmd.MustRegister(app, app.RootCmd, migratecmd.Config{}) | 
					
						
							|  |  |  | func MustRegister(app core.App, rootCmd *cobra.Command, config Config) { | 
					
						
							|  |  |  | 	if err := Register(app, rootCmd, config); err != nil { | 
					
						
							| 
									
										
										
										
											2022-11-26 09:05:52 +02:00
										 |  |  | 		panic(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-08 17:59:08 +03:00
										 |  |  | // Register registers the migratecmd plugin to the provided app instance. | 
					
						
							|  |  |  | func Register(app core.App, rootCmd *cobra.Command, config Config) error { | 
					
						
							|  |  |  | 	p := &plugin{app: app, config: config} | 
					
						
							| 
									
										
										
										
											2022-11-26 09:05:52 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-08 17:59:08 +03:00
										 |  |  | 	if p.config.TemplateLang == "" { | 
					
						
							|  |  |  | 		p.config.TemplateLang = TemplateLangGo | 
					
						
							| 
									
										
										
										
											2022-11-26 09:05:52 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-08 17:59:08 +03:00
										 |  |  | 	if p.config.Dir == "" { | 
					
						
							|  |  |  | 		if p.config.TemplateLang == TemplateLangJS { | 
					
						
							|  |  |  | 			p.config.Dir = filepath.Join(p.app.DataDir(), "../pb_migrations") | 
					
						
							| 
									
										
										
										
											2022-11-26 09:05:52 +02:00
										 |  |  | 		} else { | 
					
						
							| 
									
										
										
										
											2023-06-08 17:59:08 +03:00
										 |  |  | 			p.config.Dir = filepath.Join(p.app.DataDir(), "../migrations") | 
					
						
							| 
									
										
										
										
											2022-11-26 09:05:52 +02:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// attach the migrate command | 
					
						
							|  |  |  | 	if rootCmd != nil { | 
					
						
							|  |  |  | 		rootCmd.AddCommand(p.createCommand()) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// watch for collection changes | 
					
						
							| 
									
										
										
										
											2023-06-08 17:59:08 +03:00
										 |  |  | 	if p.config.Automigrate { | 
					
						
							| 
									
										
										
										
											2022-11-28 19:59:17 +02:00
										 |  |  | 		// refresh the cache right after app bootstap | 
					
						
							| 
									
										
										
										
											2022-11-27 23:00:58 +02:00
										 |  |  | 		p.app.OnAfterBootstrap().Add(func(e *core.BootstrapEvent) error { | 
					
						
							|  |  |  | 			p.refreshCachedCollections() | 
					
						
							|  |  |  | 			return nil | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-11-28 19:59:17 +02:00
										 |  |  | 		// refresh the cache to ensure that it constains the latest changes | 
					
						
							|  |  |  | 		// when migrations are applied on server start | 
					
						
							|  |  |  | 		p.app.OnBeforeServe().Add(func(e *core.ServeEvent) error { | 
					
						
							|  |  |  | 			p.refreshCachedCollections() | 
					
						
							|  |  |  | 			return nil | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-11-27 23:21:42 +02:00
										 |  |  | 		p.app.OnModelAfterCreate().Add(p.afterCollectionChange()) | 
					
						
							|  |  |  | 		p.app.OnModelAfterUpdate().Add(p.afterCollectionChange()) | 
					
						
							|  |  |  | 		p.app.OnModelAfterDelete().Add(p.afterCollectionChange()) | 
					
						
							| 
									
										
										
										
											2022-11-26 09:05:52 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-08 17:59:08 +03:00
										 |  |  | type plugin struct { | 
					
						
							|  |  |  | 	app    core.App | 
					
						
							|  |  |  | 	config Config | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-11-26 09:05:52 +02:00
										 |  |  | func (p *plugin) createCommand() *cobra.Command { | 
					
						
							|  |  |  | 	const cmdDesc = `Supported arguments are: | 
					
						
							| 
									
										
										
										
											2022-11-28 21:56:30 +02:00
										 |  |  | - up            - runs all available migrations | 
					
						
							|  |  |  | - down [number] - reverts the last [number] applied migrations | 
					
						
							|  |  |  | - create name   - creates new blank migration template file | 
					
						
							|  |  |  | - collections   - creates new migration file with snapshot of the local collections configuration | 
					
						
							| 
									
										
										
										
											2023-03-25 21:48:19 +02:00
										 |  |  | - history-sync  - ensures that the _migrations history table doesn't have references to deleted migration files | 
					
						
							| 
									
										
										
										
											2022-11-26 09:05:52 +02:00
										 |  |  | ` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	command := &cobra.Command{ | 
					
						
							| 
									
										
										
										
											2023-12-03 13:44:30 +02:00
										 |  |  | 		Use:          "migrate", | 
					
						
							|  |  |  | 		Short:        "Executes app DB migration scripts", | 
					
						
							|  |  |  | 		Long:         cmdDesc, | 
					
						
							|  |  |  | 		ValidArgs:    []string{"up", "down", "create", "collections"}, | 
					
						
							|  |  |  | 		SilenceUsage: true, | 
					
						
							| 
									
										
										
										
											2023-04-20 23:39:48 +03:00
										 |  |  | 		RunE: func(command *cobra.Command, args []string) error { | 
					
						
							| 
									
										
										
										
											2022-11-26 09:05:52 +02:00
										 |  |  | 			cmd := "" | 
					
						
							|  |  |  | 			if len(args) > 0 { | 
					
						
							|  |  |  | 				cmd = args[0] | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-11-26 22:33:27 +02:00
										 |  |  | 			switch cmd { | 
					
						
							|  |  |  | 			case "create": | 
					
						
							| 
									
										
										
										
											2023-05-24 09:17:17 +03:00
										 |  |  | 				if _, err := p.migrateCreateHandler("", args[1:], true); err != nil { | 
					
						
							| 
									
										
										
										
											2023-04-20 23:39:48 +03:00
										 |  |  | 					return err | 
					
						
							| 
									
										
										
										
											2022-11-26 09:05:52 +02:00
										 |  |  | 				} | 
					
						
							| 
									
										
										
										
											2022-11-26 22:33:27 +02:00
										 |  |  | 			case "collections": | 
					
						
							| 
									
										
										
										
											2023-05-24 09:17:17 +03:00
										 |  |  | 				if _, err := p.migrateCollectionsHandler(args[1:], true); err != nil { | 
					
						
							| 
									
										
										
										
											2023-04-20 23:39:48 +03:00
										 |  |  | 					return err | 
					
						
							| 
									
										
										
										
											2022-11-26 09:05:52 +02:00
										 |  |  | 				} | 
					
						
							| 
									
										
										
										
											2022-11-26 22:33:27 +02:00
										 |  |  | 			default: | 
					
						
							|  |  |  | 				runner, err := migrate.NewRunner(p.app.DB(), migrations.AppMigrations) | 
					
						
							|  |  |  | 				if err != nil { | 
					
						
							| 
									
										
										
										
											2023-04-20 23:39:48 +03:00
										 |  |  | 					return err | 
					
						
							| 
									
										
										
										
											2022-11-26 22:33:27 +02:00
										 |  |  | 				} | 
					
						
							| 
									
										
										
										
											2022-11-26 09:05:52 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-11-26 22:33:27 +02:00
										 |  |  | 				if err := runner.Run(args...); err != nil { | 
					
						
							| 
									
										
										
										
											2023-04-20 23:39:48 +03:00
										 |  |  | 					return err | 
					
						
							| 
									
										
										
										
											2022-11-26 22:33:27 +02:00
										 |  |  | 				} | 
					
						
							| 
									
										
										
										
											2022-11-26 09:05:52 +02:00
										 |  |  | 			} | 
					
						
							| 
									
										
										
										
											2023-04-20 23:39:48 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			return nil | 
					
						
							| 
									
										
										
										
											2022-11-26 09:05:52 +02:00
										 |  |  | 		}, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return command | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-24 09:17:17 +03:00
										 |  |  | func (p *plugin) migrateCreateHandler(template string, args []string, interactive bool) (string, error) { | 
					
						
							| 
									
										
										
										
											2022-11-26 09:05:52 +02:00
										 |  |  | 	if len(args) < 1 { | 
					
						
							| 
									
										
										
										
											2023-05-24 09:17:17 +03:00
										 |  |  | 		return "", fmt.Errorf("Missing migration file name") | 
					
						
							| 
									
										
										
										
											2022-11-26 09:05:52 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	name := args[0] | 
					
						
							| 
									
										
										
										
											2023-06-08 17:59:08 +03:00
										 |  |  | 	dir := p.config.Dir | 
					
						
							| 
									
										
										
										
											2022-11-26 09:05:52 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-08 17:59:08 +03:00
										 |  |  | 	filename := fmt.Sprintf("%d_%s.%s", time.Now().Unix(), inflector.Snakecase(name), p.config.TemplateLang) | 
					
						
							| 
									
										
										
										
											2023-05-24 09:17:17 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	resultFilePath := path.Join(dir, filename) | 
					
						
							| 
									
										
										
										
											2022-11-26 09:05:52 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-12-05 13:57:09 +02:00
										 |  |  | 	if interactive { | 
					
						
							|  |  |  | 		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") | 
					
						
							| 
									
										
										
										
											2023-05-24 09:17:17 +03:00
										 |  |  | 			return "", nil | 
					
						
							| 
									
										
										
										
											2022-12-05 13:57:09 +02:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2022-11-26 09:05:52 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// get default create template | 
					
						
							|  |  |  | 	if template == "" { | 
					
						
							|  |  |  | 		var templateErr error | 
					
						
							| 
									
										
										
										
											2023-06-08 17:59:08 +03:00
										 |  |  | 		if p.config.TemplateLang == TemplateLangJS { | 
					
						
							| 
									
										
										
										
											2022-11-27 23:00:58 +02:00
										 |  |  | 			template, templateErr = p.jsBlankTemplate() | 
					
						
							| 
									
										
										
										
											2022-11-26 09:05:52 +02:00
										 |  |  | 		} else { | 
					
						
							| 
									
										
										
										
											2022-11-27 23:00:58 +02:00
										 |  |  | 			template, templateErr = p.goBlankTemplate() | 
					
						
							| 
									
										
										
										
											2022-11-26 09:05:52 +02:00
										 |  |  | 		} | 
					
						
							|  |  |  | 		if templateErr != nil { | 
					
						
							| 
									
										
										
										
											2023-05-24 09:17:17 +03:00
										 |  |  | 			return "", fmt.Errorf("Failed to resolve create template: %v\n", templateErr) | 
					
						
							| 
									
										
										
										
											2022-11-26 09:05:52 +02:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// ensure that the migrations dir exist | 
					
						
							|  |  |  | 	if err := os.MkdirAll(dir, os.ModePerm); err != nil { | 
					
						
							| 
									
										
										
										
											2023-05-24 09:17:17 +03:00
										 |  |  | 		return "", err | 
					
						
							| 
									
										
										
										
											2022-11-26 09:05:52 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// save the migration file | 
					
						
							|  |  |  | 	if err := os.WriteFile(resultFilePath, []byte(template), 0644); err != nil { | 
					
						
							| 
									
										
										
										
											2023-05-24 09:17:17 +03:00
										 |  |  | 		return "", fmt.Errorf("Failed to save migration file %q: %v\n", resultFilePath, err) | 
					
						
							| 
									
										
										
										
											2022-11-26 09:05:52 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-12-05 13:57:09 +02:00
										 |  |  | 	if interactive { | 
					
						
							|  |  |  | 		fmt.Printf("Successfully created file %q\n", resultFilePath) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-24 09:17:17 +03:00
										 |  |  | 	return filename, nil | 
					
						
							| 
									
										
										
										
											2022-11-26 09:05:52 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-24 09:17:17 +03:00
										 |  |  | func (p *plugin) migrateCollectionsHandler(args []string, interactive bool) (string, error) { | 
					
						
							| 
									
										
										
										
											2022-11-26 09:05:52 +02:00
										 |  |  | 	createArgs := []string{"collections_snapshot"} | 
					
						
							|  |  |  | 	createArgs = append(createArgs, args...) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	collections := []*models.Collection{} | 
					
						
							|  |  |  | 	if err := p.app.Dao().CollectionQuery().OrderBy("created ASC").All(&collections); err != nil { | 
					
						
							| 
									
										
										
										
											2023-05-24 09:17:17 +03:00
										 |  |  | 		return "", fmt.Errorf("Failed to fetch migrations list: %v", err) | 
					
						
							| 
									
										
										
										
											2022-11-26 09:05:52 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	var template string | 
					
						
							|  |  |  | 	var templateErr error | 
					
						
							| 
									
										
										
										
											2023-06-08 17:59:08 +03:00
										 |  |  | 	if p.config.TemplateLang == TemplateLangJS { | 
					
						
							| 
									
										
										
										
											2022-11-26 09:05:52 +02:00
										 |  |  | 		template, templateErr = p.jsSnapshotTemplate(collections) | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		template, templateErr = p.goSnapshotTemplate(collections) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if templateErr != nil { | 
					
						
							| 
									
										
										
										
											2023-05-24 09:17:17 +03:00
										 |  |  | 		return "", fmt.Errorf("Failed to resolve template: %v", templateErr) | 
					
						
							| 
									
										
										
										
											2022-11-26 09:05:52 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-12-05 13:57:09 +02:00
										 |  |  | 	return p.migrateCreateHandler(template, createArgs, interactive) | 
					
						
							| 
									
										
										
										
											2022-11-26 09:05:52 +02:00
										 |  |  | } |