You've already forked pocketbase
							
							
				mirror of
				https://github.com/pocketbase/pocketbase.git
				synced 2025-10-31 08:37:38 +02:00 
			
		
		
		
	added migration to normalize the system collection and field ids
This commit is contained in:
		
							
								
								
									
										12
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								CHANGELOG.md
									
									
									
									
									
								
							| @@ -1,3 +1,15 @@ | ||||
| ## v0.23.0-rc10 | ||||
|  | ||||
| > [!CAUTION] | ||||
| > **This is a prerelease intended for test and experimental purposes only!** | ||||
|  | ||||
| - Restore the CRC32 checksum autogeneration for the collection/field ids in order to maintain deterministic default identifier value and minimize conflicts between custom migrations and full collections snapshots. | ||||
|   _There is a system migration that will attempt to normalize existing system collections ids, but if you already migrated to v0.23.0-rc and have generated a full collections snapshot migration, you have to delete it and regenerate a new one._ | ||||
|  | ||||
| - Change the behavior of the default generated collections snapshot migration to act as "extend" instead of "replace" to prevent accidental data deletion. | ||||
|   _I think this would be rare but if you want the old behaviour you can edit the generated snapshot file and replace the second argument (`deleteMissing`) of `App.ImportCollection/App.ImportCollectionsByMarshaledJSON` from `false` to `true`._ | ||||
|  | ||||
|  | ||||
| ## v0.23.0-rc9 | ||||
|  | ||||
| > [!CAUTION] | ||||
|   | ||||
| @@ -2,6 +2,7 @@ package core_test | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"database/sql" | ||||
| 	"log/slog" | ||||
| 	"os" | ||||
| 	"testing" | ||||
| @@ -9,6 +10,7 @@ import ( | ||||
|  | ||||
| 	_ "unsafe" | ||||
|  | ||||
| 	"github.com/pocketbase/dbx" | ||||
| 	"github.com/pocketbase/pocketbase/core" | ||||
| 	"github.com/pocketbase/pocketbase/tests" | ||||
| 	"github.com/pocketbase/pocketbase/tools/logger" | ||||
| @@ -351,6 +353,12 @@ func TestBaseAppRefreshSettingsLoggerMinLevelEnabled(t *testing.T) { | ||||
| 				t.Fatal(err) | ||||
| 			} | ||||
|  | ||||
| 			// silence query logs | ||||
| 			app.DB().(*dbx.DB).ExecLogFunc = func(ctx context.Context, t time.Duration, sql string, result sql.Result, err error) {} | ||||
| 			app.DB().(*dbx.DB).QueryLogFunc = func(ctx context.Context, t time.Duration, sql string, rows *sql.Rows, err error) {} | ||||
| 			app.NonconcurrentDB().(*dbx.DB).ExecLogFunc = func(ctx context.Context, t time.Duration, sql string, result sql.Result, err error) {} | ||||
| 			app.NonconcurrentDB().(*dbx.DB).QueryLogFunc = func(ctx context.Context, t time.Duration, sql string, rows *sql.Rows, err error) {} | ||||
|  | ||||
| 			handler, ok := app.Logger().Handler().(*logger.BatchHandler) | ||||
| 			if !ok { | ||||
| 				t.Fatalf("Expected BatchHandler, got %v", app.Logger().Handler()) | ||||
|   | ||||
| @@ -488,12 +488,13 @@ func (m *Collection) UnmarshalJSON(b []byte) error { | ||||
| 		minimal := &struct { | ||||
| 			Type string `json:"type"` | ||||
| 			Name string `json:"name"` | ||||
| 			Id   string `json:"id"` | ||||
| 		}{} | ||||
| 		if err := json.Unmarshal(b, minimal); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
|  | ||||
| 		blank := NewCollection(minimal.Type, minimal.Name) | ||||
| 		blank := NewCollection(minimal.Type, minimal.Name, minimal.Id) | ||||
| 		*m = *blank | ||||
| 	} | ||||
|  | ||||
|   | ||||
| @@ -2,10 +2,13 @@ package core | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"database/sql" | ||||
| 	"log/slog" | ||||
| 	"os" | ||||
| 	"testing" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/pocketbase/dbx" | ||||
| 	"github.com/pocketbase/pocketbase/tools/list" | ||||
| 	"github.com/pocketbase/pocketbase/tools/logger" | ||||
| ) | ||||
| @@ -51,6 +54,12 @@ func TestBaseAppLoggerLevelDevPrint(t *testing.T) { | ||||
| 				t.Fatal(err) | ||||
| 			} | ||||
|  | ||||
| 			// silence query logs | ||||
| 			app.DB().(*dbx.DB).ExecLogFunc = func(ctx context.Context, t time.Duration, sql string, result sql.Result, err error) {} | ||||
| 			app.DB().(*dbx.DB).QueryLogFunc = func(ctx context.Context, t time.Duration, sql string, rows *sql.Rows, err error) {} | ||||
| 			app.NonconcurrentDB().(*dbx.DB).ExecLogFunc = func(ctx context.Context, t time.Duration, sql string, result sql.Result, err error) {} | ||||
| 			app.NonconcurrentDB().(*dbx.DB).QueryLogFunc = func(ctx context.Context, t time.Duration, sql string, rows *sql.Rows, err error) {} | ||||
|  | ||||
| 			app.Settings().Logs.MinLevel = testLogLevel | ||||
| 			if err := app.Save(app.Settings()); err != nil { | ||||
| 				t.Fatal(err) | ||||
|   | ||||
| @@ -118,7 +118,7 @@ func createParamsTable(txApp core.App) error { | ||||
| } | ||||
|  | ||||
| func createMFAsCollection(txApp core.App) error { | ||||
| 	col := core.NewBaseCollection(core.CollectionNameMFAs, "_pbc"+core.CollectionNameMFAs) | ||||
| 	col := core.NewBaseCollection(core.CollectionNameMFAs) | ||||
| 	col.System = true | ||||
|  | ||||
| 	ownerRule := "@request.auth.id != '' && recordRef = @request.auth.id && collectionRef = @request.auth.collectionId" | ||||
| @@ -157,7 +157,7 @@ func createMFAsCollection(txApp core.App) error { | ||||
| } | ||||
|  | ||||
| func createOTPsCollection(txApp core.App) error { | ||||
| 	col := core.NewBaseCollection(core.CollectionNameOTPs, "_pbc"+core.CollectionNameOTPs) | ||||
| 	col := core.NewBaseCollection(core.CollectionNameOTPs) | ||||
| 	col.System = true | ||||
|  | ||||
| 	ownerRule := "@request.auth.id != '' && recordRef = @request.auth.id && collectionRef = @request.auth.collectionId" | ||||
| @@ -198,7 +198,7 @@ func createOTPsCollection(txApp core.App) error { | ||||
| } | ||||
|  | ||||
| func createAuthOriginsCollection(txApp core.App) error { | ||||
| 	col := core.NewBaseCollection(core.CollectionNameAuthOrigins, "_pbc"+core.CollectionNameAuthOrigins) | ||||
| 	col := core.NewBaseCollection(core.CollectionNameAuthOrigins) | ||||
| 	col.System = true | ||||
|  | ||||
| 	ownerRule := "@request.auth.id != '' && recordRef = @request.auth.id && collectionRef = @request.auth.collectionId" | ||||
| @@ -238,7 +238,7 @@ func createAuthOriginsCollection(txApp core.App) error { | ||||
| } | ||||
|  | ||||
| func createExternalAuthsCollection(txApp core.App) error { | ||||
| 	col := core.NewBaseCollection(core.CollectionNameExternalAuths, "_pbc"+core.CollectionNameExternalAuths) | ||||
| 	col := core.NewBaseCollection(core.CollectionNameExternalAuths) | ||||
| 	col.System = true | ||||
|  | ||||
| 	ownerRule := "@request.auth.id != '' && recordRef = @request.auth.id && collectionRef = @request.auth.collectionId" | ||||
| @@ -284,7 +284,7 @@ func createExternalAuthsCollection(txApp core.App) error { | ||||
| } | ||||
|  | ||||
| func createSuperusersCollection(txApp core.App) error { | ||||
| 	superusers := core.NewAuthCollection(core.CollectionNameSuperusers, "_pbc"+core.CollectionNameSuperusers) | ||||
| 	superusers := core.NewAuthCollection(core.CollectionNameSuperusers) | ||||
| 	superusers.System = true | ||||
| 	superusers.Fields.Add(&core.EmailField{ | ||||
| 		Name:     "email", | ||||
|   | ||||
							
								
								
									
										138
									
								
								migrations/1717233558_v0.23_migrate3.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										138
									
								
								migrations/1717233558_v0.23_migrate3.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,138 @@ | ||||
| package migrations | ||||
|  | ||||
| import ( | ||||
| 	"hash/crc32" | ||||
| 	"strconv" | ||||
|  | ||||
| 	"github.com/pocketbase/dbx" | ||||
| 	"github.com/pocketbase/pocketbase/core" | ||||
| ) | ||||
|  | ||||
| // note: this migration will be deleted in future version | ||||
|  | ||||
| func collectionIdChecksum(c *core.Collection) string { | ||||
| 	return "pbc_" + strconv.Itoa(int(crc32.ChecksumIEEE([]byte(c.Type+c.Name)))) | ||||
| } | ||||
|  | ||||
| func fieldIdChecksum(f core.Field) string { | ||||
| 	return f.Type() + strconv.Itoa(int(crc32.ChecksumIEEE([]byte(f.GetName())))) | ||||
| } | ||||
|  | ||||
| // normalize system collection and field ids | ||||
| func init() { | ||||
| 	core.SystemMigrations.Register(func(txApp core.App) error { | ||||
| 		collections := []*core.Collection{} | ||||
| 		err := txApp.CollectionQuery(). | ||||
| 			AndWhere(dbx.In( | ||||
| 				"name", | ||||
| 				core.CollectionNameMFAs, | ||||
| 				core.CollectionNameOTPs, | ||||
| 				core.CollectionNameExternalAuths, | ||||
| 				core.CollectionNameAuthOrigins, | ||||
| 				core.CollectionNameSuperusers, | ||||
| 			)). | ||||
| 			All(&collections) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
|  | ||||
| 		for _, c := range collections { | ||||
| 			var needUpdate bool | ||||
|  | ||||
| 			references, err := txApp.FindCollectionReferences(c, c.Id) | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
|  | ||||
| 			authOrigins, err := txApp.FindAllAuthOriginsByCollection(c) | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
|  | ||||
| 			mfas, err := txApp.FindAllMFAsByCollection(c) | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
|  | ||||
| 			otps, err := txApp.FindAllOTPsByCollection(c) | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
|  | ||||
| 			originalId := c.Id | ||||
|  | ||||
| 			// normalize collection id | ||||
| 			if checksum := collectionIdChecksum(c); c.Id != checksum { | ||||
| 				c.Id = checksum | ||||
| 				needUpdate = true | ||||
| 			} | ||||
|  | ||||
| 			// normalize system fields | ||||
| 			for _, f := range c.Fields { | ||||
| 				if !f.GetSystem() { | ||||
| 					continue | ||||
| 				} | ||||
|  | ||||
| 				if checksum := fieldIdChecksum(f); f.GetId() != checksum { | ||||
| 					f.SetId(checksum) | ||||
| 					needUpdate = true | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			if !needUpdate { | ||||
| 				continue | ||||
| 			} | ||||
|  | ||||
| 			rawExport, err := c.DBExport(txApp) | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
|  | ||||
| 			_, err = txApp.DB().Update("_collections", rawExport, dbx.HashExp{"id": originalId}).Execute() | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
|  | ||||
| 			// update collection references | ||||
| 			for refCollection, fields := range references { | ||||
| 				for _, f := range fields { | ||||
| 					relationField, ok := f.(*core.RelationField) | ||||
| 					if !ok || relationField.CollectionId == originalId { | ||||
| 						continue | ||||
| 					} | ||||
|  | ||||
| 					relationField.CollectionId = c.Id | ||||
| 				} | ||||
| 				if err = txApp.Save(refCollection); err != nil { | ||||
| 					return err | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			// update mfas references | ||||
| 			for _, item := range mfas { | ||||
| 				item.SetCollectionRef(c.Id) | ||||
| 				if err = txApp.Save(item); err != nil { | ||||
| 					return err | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			// update otps references | ||||
| 			for _, item := range otps { | ||||
| 				item.SetCollectionRef(c.Id) | ||||
| 				if err = txApp.Save(item); err != nil { | ||||
| 					return err | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			// update authOrigins references | ||||
| 			for _, item := range authOrigins { | ||||
| 				item.SetCollectionRef(c.Id) | ||||
| 				if err = txApp.Save(item); err != nil { | ||||
| 					return err | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		return nil | ||||
| 	}, nil) | ||||
| } | ||||
| @@ -62,7 +62,7 @@ func (p *plugin) jsSnapshotTemplate(collections []*core.Collection) (string, err | ||||
| 	const template = jsTypesDirective + `migrate((app) => { | ||||
|   const snapshot = %s; | ||||
|  | ||||
|   return app.importCollections(snapshot, true); | ||||
|   return app.importCollections(snapshot, false); | ||||
| }, (app) => { | ||||
|   return null; | ||||
| }) | ||||
| @@ -348,7 +348,7 @@ func init() { | ||||
| 	m.Register(func(app core.App) error { | ||||
| 		jsonData := ` + "`%s`" + ` | ||||
|  | ||||
| 		return app.ImportCollectionsByMarshaledJSON([]byte(jsonData), true) | ||||
| 		return app.ImportCollectionsByMarshaledJSON([]byte(jsonData), false) | ||||
| 	}, func(app core.App) error { | ||||
| 		return nil | ||||
| 	}) | ||||
|   | ||||
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										2
									
								
								ui/.env
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								ui/.env
									
									
									
									
									
								
							| @@ -10,4 +10,4 @@ PB_DOCS_URL            = "https://pocketbase.io/docs/" | ||||
| PB_JS_SDK_URL          = "https://github.com/pocketbase/js-sdk" | ||||
| PB_DART_SDK_URL        = "https://github.com/pocketbase/dart-sdk" | ||||
| PB_RELEASES            = "https://github.com/pocketbase/pocketbase/releases" | ||||
| PB_VERSION             = "v0.23.0-rc9" | ||||
| PB_VERSION             = "v0.23.0-rc10" | ||||
|   | ||||
		Reference in New Issue
	
	Block a user