diff --git a/CHANGELOG.md b/CHANGELOG.md index ea201653..a122cf78 100644 --- a/CHANGELOG.md +++ b/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] diff --git a/core/base_test.go b/core/base_test.go index a64a94b4..96d4b448 100644 --- a/core/base_test.go +++ b/core/base_test.go @@ -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()) diff --git a/core/collection_model.go b/core/collection_model.go index fe04e77c..f9a004ec 100644 --- a/core/collection_model.go +++ b/core/collection_model.go @@ -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 } diff --git a/core/log_printer_test.go b/core/log_printer_test.go index 26d26c2c..35a660d4 100644 --- a/core/log_printer_test.go +++ b/core/log_printer_test.go @@ -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) diff --git a/migrations/1640988000_init.go b/migrations/1640988000_init.go index ce8a0cae..c0d01361 100644 --- a/migrations/1640988000_init.go +++ b/migrations/1640988000_init.go @@ -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", diff --git a/migrations/1717233558_v0.23_migrate3.go b/migrations/1717233558_v0.23_migrate3.go new file mode 100644 index 00000000..b8ed1795 --- /dev/null +++ b/migrations/1717233558_v0.23_migrate3.go @@ -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) +} diff --git a/plugins/migratecmd/templates.go b/plugins/migratecmd/templates.go index 0c01e6eb..8297cc45 100644 --- a/plugins/migratecmd/templates.go +++ b/plugins/migratecmd/templates.go @@ -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 }) diff --git a/tests/data/data.db b/tests/data/data.db index e7ad361c..50892db4 100644 Binary files a/tests/data/data.db and b/tests/data/data.db differ diff --git a/ui/.env b/ui/.env index 57f34659..c494e0f2 100644 --- a/ui/.env +++ b/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"