removed the legacy temp upgrade command
This commit is contained in:
		
							parent
							
								
									472671fee1
								
							
						
					
					
						commit
						a42ab6a205
					
				|  | @ -1,444 +0,0 @@ | |||
| package cmd | ||||
| 
 | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"regexp" | ||||
| 	"strings" | ||||
| 
 | ||||
| 	"github.com/fatih/color" | ||||
| 	"github.com/pocketbase/dbx" | ||||
| 	"github.com/pocketbase/pocketbase/core" | ||||
| 	"github.com/pocketbase/pocketbase/daos" | ||||
| 	"github.com/pocketbase/pocketbase/models" | ||||
| 	"github.com/pocketbase/pocketbase/models/schema" | ||||
| 	"github.com/pocketbase/pocketbase/tools/types" | ||||
| 	"github.com/spf13/cobra" | ||||
| ) | ||||
| 
 | ||||
| // Temporary console command to update the pb_data structure to be compatible with the v0.8.0 changes.
 | ||||
| //
 | ||||
| // NB! It will be removed in v0.9+
 | ||||
| func NewTempUpgradeCommand(app core.App) *cobra.Command { | ||||
| 	command := &cobra.Command{ | ||||
| 		Use:   "upgrade", | ||||
| 		Short: "Upgrades your existing pb_data to be compatible with the v0.8.x changes", | ||||
| 		Long: ` | ||||
| Upgrades your existing pb_data to be compatible with the v0.8.x changes | ||||
| Prerequisites and caveats: | ||||
| - already upgraded to v0.7.* | ||||
| - no existing users collection | ||||
| - existing profiles collection fields like email, username, verified, etc. will be renamed to username2, email2, etc. | ||||
| `, | ||||
| 		Run: func(command *cobra.Command, args []string) { | ||||
| 			if err := upgrade(app); err != nil { | ||||
| 				color.Red("Error: %v", err) | ||||
| 			} | ||||
| 		}, | ||||
| 	} | ||||
| 
 | ||||
| 	return command | ||||
| } | ||||
| 
 | ||||
| func upgrade(app core.App) error { | ||||
| 	if _, err := app.Dao().FindCollectionByNameOrId("users"); err == nil { | ||||
| 		return errors.New("It seems that you've already upgraded or have an existing 'users' collection.") | ||||
| 	} | ||||
| 
 | ||||
| 	return app.Dao().RunInTransaction(func(txDao *daos.Dao) error { | ||||
| 		if err := migrateCollections(txDao); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 
 | ||||
| 		if err := migrateUsers(app, txDao); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 
 | ||||
| 		if err := resetMigrationsTable(txDao); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 
 | ||||
| 		bold := color.New(color.Bold).Add(color.FgGreen) | ||||
| 		bold.Println("The pb_data upgrade completed successfully!") | ||||
| 		bold.Println("You can now start the application as usual with the 'serve' command.") | ||||
| 		bold.Println("Please review the migrated collection API rules and fields in the Admin UI and apply the necessary changes in your client-side code.") | ||||
| 		fmt.Println() | ||||
| 
 | ||||
| 		return nil | ||||
| 	}) | ||||
| } | ||||
| 
 | ||||
| // -------------------------------------------------------------------
 | ||||
| 
 | ||||
| func migrateCollections(txDao *daos.Dao) error { | ||||
| 	// add new collection columns
 | ||||
| 	if _, err := txDao.DB().AddColumn("_collections", "type", "TEXT DEFAULT 'base' NOT NULL").Execute(); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if _, err := txDao.DB().AddColumn("_collections", "options", "JSON DEFAULT '{}' NOT NULL").Execute(); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	ruleReplacements := []struct { | ||||
| 		old string | ||||
| 		new string | ||||
| 	}{ | ||||
| 		{"expand", "expand2"}, | ||||
| 		{"collecitonId", "collectionId2"}, | ||||
| 		{"collecitonName", "collectionName2"}, | ||||
| 		{"profile.userId", "profile.id"}, | ||||
| 
 | ||||
| 		// @collection.*
 | ||||
| 		{"@collection.profiles.userId", "@collection.users.id"}, | ||||
| 		{"@collection.profiles.username", "@collection.users.username2"}, | ||||
| 		{"@collection.profiles.email", "@collection.users.email2"}, | ||||
| 		{"@collection.profiles.emailVisibility", "@collection.users.emailVisibility2"}, | ||||
| 		{"@collection.profiles.verified", "@collection.users.verified2"}, | ||||
| 		{"@collection.profiles.tokenKey", "@collection.users.tokenKey2"}, | ||||
| 		{"@collection.profiles.passwordHash", "@collection.users.passwordHash2"}, | ||||
| 		{"@collection.profiles.lastResetSentAt", "@collection.users.lastResetSentAt2"}, | ||||
| 		{"@collection.profiles.lastVerificationSentAt", "@collection.users.lastVerificationSentAt2"}, | ||||
| 		{"@collection.profiles.", "@collection.users."}, | ||||
| 
 | ||||
| 		// @request.*
 | ||||
| 		{"@request.user.profile.userId", "@request.auth.id"}, | ||||
| 		{"@request.user.profile.username", "@request.auth.username2"}, | ||||
| 		{"@request.user.profile.email", "@request.auth.email2"}, | ||||
| 		{"@request.user.profile.emailVisibility", "@request.auth.emailVisibility2"}, | ||||
| 		{"@request.user.profile.verified", "@request.auth.verified2"}, | ||||
| 		{"@request.user.profile.tokenKey", "@request.auth.tokenKey2"}, | ||||
| 		{"@request.user.profile.passwordHash", "@request.auth.passwordHash2"}, | ||||
| 		{"@request.user.profile.lastResetSentAt", "@request.auth.lastResetSentAt2"}, | ||||
| 		{"@request.user.profile.lastVerificationSentAt", "@request.auth.lastVerificationSentAt2"}, | ||||
| 		{"@request.user.profile.", "@request.auth."}, | ||||
| 		{"@request.user", "@request.auth"}, | ||||
| 	} | ||||
| 
 | ||||
| 	collections := []*models.Collection{} | ||||
| 	if err := txDao.CollectionQuery().All(&collections); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	for _, collection := range collections { | ||||
| 		collection.Type = models.CollectionTypeBase | ||||
| 		collection.NormalizeOptions() | ||||
| 
 | ||||
| 		// rename profile fields
 | ||||
| 		// ---
 | ||||
| 		fieldsToRename := []string{ | ||||
| 			"collectionId", | ||||
| 			"collectionName", | ||||
| 			"expand", | ||||
| 		} | ||||
| 		if collection.Name == "profiles" { | ||||
| 			fieldsToRename = append(fieldsToRename, | ||||
| 				"username", | ||||
| 				"email", | ||||
| 				"emailVisibility", | ||||
| 				"verified", | ||||
| 				"tokenKey", | ||||
| 				"passwordHash", | ||||
| 				"lastResetSentAt", | ||||
| 				"lastVerificationSentAt", | ||||
| 			) | ||||
| 		} | ||||
| 		for _, name := range fieldsToRename { | ||||
| 			f := collection.Schema.GetFieldByName(name) | ||||
| 			if f != nil { | ||||
| 				color.Blue("[%s - renamed field]", collection.Name) | ||||
| 				color.Yellow(" - old: %s", f.Name) | ||||
| 				color.Green(" - new: %s2", f.Name) | ||||
| 				fmt.Println() | ||||
| 				f.Name += "2" | ||||
| 			} | ||||
| 		} | ||||
| 		// ---
 | ||||
| 
 | ||||
| 		// replace rule fields
 | ||||
| 		// ---
 | ||||
| 		rules := map[string]*string{ | ||||
| 			"ListRule":   collection.ListRule, | ||||
| 			"ViewRule":   collection.ViewRule, | ||||
| 			"CreateRule": collection.CreateRule, | ||||
| 			"UpdateRule": collection.UpdateRule, | ||||
| 			"DeleteRule": collection.DeleteRule, | ||||
| 		} | ||||
| 
 | ||||
| 		for ruleKey, rule := range rules { | ||||
| 			if rule == nil || *rule == "" { | ||||
| 				continue | ||||
| 			} | ||||
| 
 | ||||
| 			originalRule := *rule | ||||
| 
 | ||||
| 			for _, replacement := range ruleReplacements { | ||||
| 				re := regexp.MustCompile(regexp.QuoteMeta(replacement.old) + `\b`) | ||||
| 				*rule = re.ReplaceAllString(*rule, replacement.new) | ||||
| 			} | ||||
| 
 | ||||
| 			*rule = replaceReversedLikes(*rule) | ||||
| 
 | ||||
| 			if originalRule != *rule { | ||||
| 				color.Blue("[%s - replaced %s]:", collection.Name, ruleKey) | ||||
| 				color.Yellow(" - old: %s", strings.TrimSpace(originalRule)) | ||||
| 				color.Green(" - new: %s", strings.TrimSpace(*rule)) | ||||
| 				fmt.Println() | ||||
| 			} | ||||
| 		} | ||||
| 		// ---
 | ||||
| 
 | ||||
| 		if err := txDao.SaveCollection(collection); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func migrateUsers(app core.App, txDao *daos.Dao) error { | ||||
| 	color.Blue(`[merging "_users" and "profiles"]:`) | ||||
| 
 | ||||
| 	profilesCollection, err := txDao.FindCollectionByNameOrId("profiles") | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	originalProfilesCollectionId := profilesCollection.Id | ||||
| 
 | ||||
| 	// change the profiles collection id to something else since we will be using
 | ||||
| 	// it for the new users collection in order to avoid renaming the storage dir
 | ||||
| 	_, idRenameErr := txDao.DB().NewQuery(fmt.Sprintf( | ||||
| 		`UPDATE {{_collections}} | ||||
| 			SET id = '%s' | ||||
| 			WHERE id = '%s'; | ||||
| 		`, | ||||
| 		(originalProfilesCollectionId + "__old__"), | ||||
| 		originalProfilesCollectionId, | ||||
| 	)).Execute() | ||||
| 	if idRenameErr != nil { | ||||
| 		return idRenameErr | ||||
| 	} | ||||
| 
 | ||||
| 	// refresh profiles collection
 | ||||
| 	profilesCollection, err = txDao.FindCollectionByNameOrId("profiles") | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	usersSchema, _ := profilesCollection.Schema.Clone() | ||||
| 	userIdField := usersSchema.GetFieldByName("userId") | ||||
| 	if userIdField != nil { | ||||
| 		usersSchema.RemoveField(userIdField.Id) | ||||
| 	} | ||||
| 
 | ||||
| 	usersCollection := &models.Collection{} | ||||
| 	usersCollection.MarkAsNew() | ||||
| 	usersCollection.Id = originalProfilesCollectionId | ||||
| 	usersCollection.Name = "users" | ||||
| 	usersCollection.Type = models.CollectionTypeAuth | ||||
| 	usersCollection.Schema = *usersSchema | ||||
| 	usersCollection.CreateRule = types.Pointer("") | ||||
| 	if profilesCollection.ListRule != nil && *profilesCollection.ListRule != "" { | ||||
| 		*profilesCollection.ListRule = strings.ReplaceAll(*profilesCollection.ListRule, "userId", "id") | ||||
| 		usersCollection.ListRule = profilesCollection.ListRule | ||||
| 	} | ||||
| 	if profilesCollection.ViewRule != nil && *profilesCollection.ViewRule != "" { | ||||
| 		*profilesCollection.ViewRule = strings.ReplaceAll(*profilesCollection.ViewRule, "userId", "id") | ||||
| 		usersCollection.ViewRule = profilesCollection.ViewRule | ||||
| 	} | ||||
| 	if profilesCollection.UpdateRule != nil && *profilesCollection.UpdateRule != "" { | ||||
| 		*profilesCollection.UpdateRule = strings.ReplaceAll(*profilesCollection.UpdateRule, "userId", "id") | ||||
| 		usersCollection.UpdateRule = profilesCollection.UpdateRule | ||||
| 	} | ||||
| 	if profilesCollection.DeleteRule != nil && *profilesCollection.DeleteRule != "" { | ||||
| 		*profilesCollection.DeleteRule = strings.ReplaceAll(*profilesCollection.DeleteRule, "userId", "id") | ||||
| 		usersCollection.DeleteRule = profilesCollection.DeleteRule | ||||
| 	} | ||||
| 
 | ||||
| 	// set auth options
 | ||||
| 	settings := app.Settings() | ||||
| 	authOptions := usersCollection.AuthOptions() | ||||
| 	authOptions.ManageRule = nil | ||||
| 	authOptions.AllowOAuth2Auth = true | ||||
| 	authOptions.AllowUsernameAuth = false | ||||
| 	authOptions.AllowEmailAuth = settings.EmailAuth.Enabled | ||||
| 	authOptions.MinPasswordLength = settings.EmailAuth.MinPasswordLength | ||||
| 	authOptions.OnlyEmailDomains = settings.EmailAuth.OnlyDomains | ||||
| 	authOptions.ExceptEmailDomains = settings.EmailAuth.ExceptDomains | ||||
| 	// twitter currently is the only provider that doesn't return an email
 | ||||
| 	authOptions.RequireEmail = !settings.TwitterAuth.Enabled | ||||
| 
 | ||||
| 	usersCollection.SetOptions(authOptions) | ||||
| 
 | ||||
| 	if err := txDao.SaveCollection(usersCollection); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	// copy the original users
 | ||||
| 	_, usersErr := txDao.DB().NewQuery(` | ||||
| 		INSERT INTO {{users}} (id, created, updated, username, email, emailVisibility, verified, tokenKey, passwordHash, lastResetSentAt, lastVerificationSentAt) | ||||
| 		SELECT id, created, updated, ("u_" || id), email, false, verified, tokenKey, passwordHash, lastResetSentAt, lastVerificationSentAt | ||||
| 		FROM {{_users}}; | ||||
| 	`).Execute() | ||||
| 	if usersErr != nil { | ||||
| 		return usersErr | ||||
| 	} | ||||
| 
 | ||||
| 	// generate the profile fields copy statements
 | ||||
| 	sets := []string{"id = p.id"} | ||||
| 	for _, f := range usersSchema.Fields() { | ||||
| 		sets = append(sets, fmt.Sprintf("%s = p.%s", f.Name, f.Name)) | ||||
| 	} | ||||
| 
 | ||||
| 	// copy profile fields
 | ||||
| 	_, copyProfileErr := txDao.DB().NewQuery(fmt.Sprintf(` | ||||
| 		UPDATE {{users}} as u | ||||
| 		SET %s | ||||
| 		FROM {{profiles}} as p | ||||
| 		WHERE u.id = p.userId; | ||||
| 	`, strings.Join(sets, ", "))).Execute() | ||||
| 	if copyProfileErr != nil { | ||||
| 		return copyProfileErr | ||||
| 	} | ||||
| 
 | ||||
| 	profileRecords, err := txDao.FindRecordsByExpr("profiles") | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	// update all profiles and users fields to point to the new users collection
 | ||||
| 	collections := []*models.Collection{} | ||||
| 	if err := txDao.CollectionQuery().All(&collections); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	for _, collection := range collections { | ||||
| 		var hasChanges bool | ||||
| 
 | ||||
| 		for _, f := range collection.Schema.Fields() { | ||||
| 			f.InitOptions() | ||||
| 
 | ||||
| 			if f.Type == schema.FieldTypeUser { | ||||
| 				if collection.Name == "profiles" && f.Name == "userId" { | ||||
| 					continue | ||||
| 				} | ||||
| 
 | ||||
| 				hasChanges = true | ||||
| 
 | ||||
| 				// change the user field to a relation field
 | ||||
| 				options, _ := f.Options.(*schema.UserOptions) | ||||
| 				f.Type = schema.FieldTypeRelation | ||||
| 				f.Options = &schema.RelationOptions{ | ||||
| 					CollectionId:  usersCollection.Id, | ||||
| 					MaxSelect:     &options.MaxSelect, | ||||
| 					CascadeDelete: options.CascadeDelete, | ||||
| 				} | ||||
| 
 | ||||
| 				for _, p := range profileRecords { | ||||
| 					pId := p.Id | ||||
| 					pUserId := p.GetString("userId") | ||||
| 					// replace all user record id references with the profile id
 | ||||
| 					_, replaceErr := txDao.DB().NewQuery(fmt.Sprintf(` | ||||
| 						UPDATE %s | ||||
| 						SET [[%s]] = REPLACE([[%s]], '%s', '%s') | ||||
| 						WHERE [[%s]] LIKE ('%%%s%%'); | ||||
| 					`, collection.Name, f.Name, f.Name, pUserId, pId, f.Name, pUserId)).Execute() | ||||
| 					if replaceErr != nil { | ||||
| 						return replaceErr | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		if hasChanges { | ||||
| 			if err := txDao.Save(collection); err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	if err := migrateExternalAuths(txDao, originalProfilesCollectionId); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	// drop _users table
 | ||||
| 	if _, err := txDao.DB().DropTable("_users").Execute(); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	// drop profiles table
 | ||||
| 	if _, err := txDao.DB().DropTable("profiles").Execute(); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	// delete profiles collection
 | ||||
| 	if err := txDao.Delete(profilesCollection); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	color.Green(` - Successfully merged "_users" and "profiles" into a new collection "users".`) | ||||
| 	fmt.Println() | ||||
| 
 | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func migrateExternalAuths(txDao *daos.Dao, userCollectionId string) error { | ||||
| 	_, alterErr := txDao.DB().NewQuery(` | ||||
| 		-- crate new externalAuths table | ||||
| 		CREATE TABLE {{_newExternalAuths}} ( | ||||
| 			[[id]]           TEXT PRIMARY KEY, | ||||
| 			[[collectionId]] TEXT NOT NULL, | ||||
| 			[[recordId]]     TEXT NOT NULL, | ||||
| 			[[provider]]     TEXT NOT NULL, | ||||
| 			[[providerId]]   TEXT NOT NULL, | ||||
| 			[[created]]      TEXT DEFAULT "" NOT NULL, | ||||
| 			[[updated]]      TEXT DEFAULT "" NOT NULL, | ||||
| 			--- | ||||
| 			FOREIGN KEY ([[collectionId]]) REFERENCES {{_collections}} ([[id]]) ON UPDATE CASCADE ON DELETE CASCADE | ||||
| 		); | ||||
| 
 | ||||
| 		-- copy all data from the old table to the new one | ||||
| 		INSERT INTO {{_newExternalAuths}} | ||||
| 		SELECT auth.id, "` + userCollectionId + `" as collectionId, [[profiles.id]] as recordId, auth.provider, auth.providerId, auth.created, auth.updated | ||||
| 		FROM {{_externalAuths}} auth | ||||
| 		INNER JOIN {{profiles}} on [[profiles.userId]] = [[auth.userId]]; | ||||
| 
 | ||||
| 		-- drop old table | ||||
| 		DROP TABLE {{_externalAuths}}; | ||||
| 
 | ||||
| 		-- rename new table | ||||
| 		ALTER TABLE {{_newExternalAuths}} RENAME TO {{_externalAuths}}; | ||||
| 
 | ||||
| 		-- create named indexes | ||||
| 		CREATE UNIQUE INDEX _externalAuths_record_provider_idx on {{_externalAuths}} ([[collectionId]], [[recordId]], [[provider]]); | ||||
| 		CREATE UNIQUE INDEX _externalAuths_provider_providerId_idx on {{_externalAuths}} ([[provider]], [[providerId]]); | ||||
| 	`).Execute() | ||||
| 
 | ||||
| 	return alterErr | ||||
| } | ||||
| 
 | ||||
| func resetMigrationsTable(txDao *daos.Dao) error { | ||||
| 	// reset the migration state to the new init
 | ||||
| 	_, err := txDao.DB().Delete("_migrations", dbx.HashExp{ | ||||
| 		"file": "1661586591_add_externalAuths_table.go", | ||||
| 	}).Execute() | ||||
| 
 | ||||
| 	return err | ||||
| } | ||||
| 
 | ||||
| var reverseLikeRegex = regexp.MustCompile(`(['"]\w*['"])\s*(\~|!~)\s*([\w\@\.]*)`) | ||||
| 
 | ||||
| func replaceReversedLikes(rule string) string { | ||||
| 	parts := reverseLikeRegex.FindAllStringSubmatch(rule, -1) | ||||
| 
 | ||||
| 	for _, p := range parts { | ||||
| 		if len(p) != 4 { | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		newPart := fmt.Sprintf("%s %s %s", p[3], p[2], p[1]) | ||||
| 
 | ||||
| 		rule = strings.ReplaceAll(rule, p[0], newPart) | ||||
| 	} | ||||
| 
 | ||||
| 	return rule | ||||
| } | ||||
|  | @ -136,7 +136,6 @@ func NewWithConfig(config *Config) *PocketBase { | |||
| func (pb *PocketBase) Start() error { | ||||
| 	// register system commands
 | ||||
| 	pb.RootCmd.AddCommand(cmd.NewAdminCommand(pb)) | ||||
| 	pb.RootCmd.AddCommand(cmd.NewTempUpgradeCommand(pb)) | ||||
| 	pb.RootCmd.AddCommand(cmd.NewServeCommand(pb, !pb.hideStartBanner)) | ||||
| 
 | ||||
| 	return pb.Execute() | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue