diff --git a/apis/collection_test.go b/apis/collection_test.go index a3d1353a..8809da72 100644 --- a/apis/collection_test.go +++ b/apis/collection_test.go @@ -553,7 +553,7 @@ func TestCollectionImport(t *testing.T) { }, }, { - Name: "authorized as admin + successfull collections save", + Name: "authorized as admin + successful collections save", Method: http.MethodPut, Url: "/api/collections/import", Body: strings.NewReader(`{ @@ -601,7 +601,7 @@ func TestCollectionImport(t *testing.T) { }, }, { - Name: "authorized as admin + successfull collections save and old non-system collections deletion", + Name: "authorized as admin + successful collections save and old non-system collections deletion", Method: http.MethodPut, Url: "/api/collections/import", Body: strings.NewReader(`{ diff --git a/apis/realtime.go b/apis/realtime.go index 6023ccde..41461433 100644 --- a/apis/realtime.go +++ b/apis/realtime.go @@ -210,13 +210,17 @@ func (api *realtimeApi) bindEvents() { return nil }) - api.app.OnRecordAfterCreateRequest().Add(func(e *core.RecordCreateEvent) error { - api.broadcastRecord("create", e.Record) + api.app.OnModelAfterCreate().Add(func(e *core.ModelEvent) error { + if record, ok := e.Model.(*models.Record); ok { + api.broadcastRecord("create", record) + } return nil }) - api.app.OnRecordAfterUpdateRequest().Add(func(e *core.RecordUpdateEvent) error { - api.broadcastRecord("update", e.Record) + api.app.OnModelAfterUpdate().Add(func(e *core.ModelEvent) error { + if record, ok := e.Model.(*models.Record); ok { + api.broadcastRecord("update", record) + } return nil }) diff --git a/cmd/migrate.go b/cmd/migrate.go index a2d5c748..1d095266 100644 --- a/cmd/migrate.go +++ b/cmd/migrate.go @@ -217,7 +217,7 @@ func init() { ` func migrateCollectionsHandler(app core.App, args []string) error { - createArgs := []string{"collections_import"} + createArgs := []string{"collections_snapshot"} createArgs = append(createArgs, args...) dao := daos.New(app.DB()) diff --git a/daos/base.go b/daos/base.go index b27aae35..0c6df55a 100644 --- a/daos/base.go +++ b/daos/base.go @@ -65,12 +65,11 @@ func (dao *Dao) RunInTransaction(fn func(txDao *Dao) error) error { // so execute the function within the current transaction return fn(dao) case *dbx.DB: + afterCalls := []afterCallGroup{} - return txOrDB.Transactional(func(tx *dbx.Tx) error { + txError := txOrDB.Transactional(func(tx *dbx.Tx) error { txDao := New(tx) - afterCalls := []afterCallGroup{} - if dao.BeforeCreateFunc != nil { txDao.BeforeCreateFunc = func(eventDao *Dao, m models.Model) error { return dao.BeforeCreateFunc(eventDao, m) @@ -103,23 +102,24 @@ func (dao *Dao) RunInTransaction(fn func(txDao *Dao) error) error { } } - if err := fn(txDao); err != nil { - return err - } + return fn(txDao) + }) - // execute after event calls on successfull transaction + if txError == nil { + // execute after event calls on successful transaction for _, call := range afterCalls { - if call.Action == "create" { + switch call.Action { + case "create": dao.AfterCreateFunc(call.EventDao, call.Model) - } else if call.Action == "update" { + case "update": dao.AfterUpdateFunc(call.EventDao, call.Model) - } else if call.Action == "delete" { + case "delete": dao.AfterDeleteFunc(call.EventDao, call.Model) } } + } - return nil - }) + return txError } return errors.New("Failed to start transaction (unknown dao.db)") diff --git a/daos/base_test.go b/daos/base_test.go index ee3d1265..3b5ed45f 100644 --- a/daos/base_test.go +++ b/daos/base_test.go @@ -365,32 +365,34 @@ func TestDaoTransactionHooksCallsOnFailure(t *testing.T) { existingModel, _ := testApp.Dao().FindAdminByEmail("test@example.com") - baseDao.RunInTransaction(func(txDao *daos.Dao) error { - // test create - // --- - newModel := &models.Admin{} - newModel.Email = "test_new1@example.com" - newModel.SetPassword("123456") - if err := txDao.Save(newModel); err != nil { - t.Fatal(err) - } + baseDao.RunInTransaction(func(txDao1 *daos.Dao) error { + return txDao1.RunInTransaction(func(txDao2 *daos.Dao) error { + // test create + // --- + newModel := &models.Admin{} + newModel.Email = "test_new1@example.com" + newModel.SetPassword("123456") + if err := txDao2.Save(newModel); err != nil { + t.Fatal(err) + } - // test update (twice) - // --- - if err := txDao.Save(existingModel); err != nil { - t.Fatal(err) - } - if err := txDao.Save(existingModel); err != nil { - t.Fatal(err) - } + // test update (twice) + // --- + if err := txDao2.Save(existingModel); err != nil { + t.Fatal(err) + } + if err := txDao2.Save(existingModel); err != nil { + t.Fatal(err) + } - // test delete - // --- - if err := txDao.Delete(existingModel); err != nil { - t.Fatal(err) - } + // test delete + // --- + if err := txDao2.Delete(existingModel); err != nil { + t.Fatal(err) + } - return errors.New("test_tx_error") + return errors.New("test_tx_error") + }) }) if beforeCreateFuncCalls != 1 { @@ -451,32 +453,34 @@ func TestDaoTransactionHooksCallsOnSuccess(t *testing.T) { existingModel, _ := testApp.Dao().FindAdminByEmail("test@example.com") - baseDao.RunInTransaction(func(txDao *daos.Dao) error { - // test create - // --- - newModel := &models.Admin{} - newModel.Email = "test_new1@example.com" - newModel.SetPassword("123456") - if err := txDao.Save(newModel); err != nil { - t.Fatal(err) - } + baseDao.RunInTransaction(func(txDao1 *daos.Dao) error { + return txDao1.RunInTransaction(func(txDao2 *daos.Dao) error { + // test create + // --- + newModel := &models.Admin{} + newModel.Email = "test_new1@example.com" + newModel.SetPassword("123456") + if err := txDao2.Save(newModel); err != nil { + t.Fatal(err) + } - // test update (twice) - // --- - if err := txDao.Save(existingModel); err != nil { - t.Fatal(err) - } - if err := txDao.Save(existingModel); err != nil { - t.Fatal(err) - } + // test update (twice) + // --- + if err := txDao2.Save(existingModel); err != nil { + t.Fatal(err) + } + if err := txDao2.Save(existingModel); err != nil { + t.Fatal(err) + } - // test delete - // --- - if err := txDao.Delete(existingModel); err != nil { - t.Fatal(err) - } + // test delete + // --- + if err := txDao2.Delete(existingModel); err != nil { + t.Fatal(err) + } - return nil + return nil + }) }) if beforeCreateFuncCalls != 1 { diff --git a/daos/record_test.go b/daos/record_test.go index c23602fc..024be661 100644 --- a/daos/record_test.go +++ b/daos/record_test.go @@ -298,7 +298,6 @@ func TestFindUserRelatedRecords(t *testing.T) { } if len(records) != len(scenario.expectedIds) { - fmt.Println(records[0]) t.Errorf("(%d) Expected %d records, got %d (%v)", i, len(scenario.expectedIds), len(records), records) continue } diff --git a/tools/migrate/runner.go b/tools/migrate/runner.go index f9c6d87e..57f30c0e 100644 --- a/tools/migrate/runner.go +++ b/tools/migrate/runner.go @@ -37,9 +37,8 @@ func NewRunner(db *dbx.DB, migrationsList MigrationsList) (*Runner, error) { // Run interactively executes the current runner with the provided args. // // The following commands are supported: -// - up - applies all migrations -// - down [n] - reverts the last n applied migrations -// - create NEW_MIGRATION_NAME - create NEW_MIGRATION_NAME.go file from a migration template +// - up - applies all migrations +// - down [n] - reverts the last n applied migrations func (r *Runner) Run(args ...string) error { cmd := "up" if len(args) > 0 { diff --git a/tools/search/provider.go b/tools/search/provider.go index c2c07cb8..13fc31da 100644 --- a/tools/search/provider.go +++ b/tools/search/provider.go @@ -28,6 +28,7 @@ type Result struct { Page int `json:"page"` PerPage int `json:"perPage"` TotalItems int `json:"totalItems"` + TotalPages int `json:"totalPages"` Items any `json:"items"` } @@ -213,10 +214,12 @@ func (s *Provider) Exec(items any) (*Result, error) { s.perPage = MaxPerPage } + totalPages := int(math.Ceil(float64(totalCount) / float64(s.perPage))) + // normalize page according to the total count if s.page <= 0 || totalCount == 0 { s.page = 1 - } else if totalPages := int(math.Ceil(float64(totalCount) / float64(s.perPage))); s.page > totalPages { + } else if s.page > totalPages { s.page = totalPages } @@ -233,6 +236,7 @@ func (s *Provider) Exec(items any) (*Result, error) { Page: s.page, PerPage: s.perPage, TotalItems: int(totalCount), + TotalPages: totalPages, Items: items, }, nil } diff --git a/tools/search/provider_test.go b/tools/search/provider_test.go index be77daf8..dfdd40e5 100644 --- a/tools/search/provider_test.go +++ b/tools/search/provider_test.go @@ -236,7 +236,7 @@ func TestProviderExecNonEmptyQuery(t *testing.T) { []FilterData{}, "", false, - `{"page":1,"perPage":10,"totalItems":2,"items":[{"test1":1,"test2":"test2.1","test3":""},{"test1":2,"test2":"test2.2","test3":""}]}`, + `{"page":1,"perPage":10,"totalItems":2,"totalPages":1,"items":[{"test1":1,"test2":"test2.1","test3":""},{"test1":2,"test2":"test2.2","test3":""}]}`, []string{ "SELECT COUNT(*) FROM `test` WHERE NOT (`test1` IS NULL) ORDER BY `test1` ASC", "SELECT * FROM `test` WHERE NOT (`test1` IS NULL) ORDER BY `test1` ASC LIMIT 10", @@ -250,7 +250,7 @@ func TestProviderExecNonEmptyQuery(t *testing.T) { []FilterData{}, "", false, - `{"page":1,"perPage":30,"totalItems":2,"items":[{"test1":1,"test2":"test2.1","test3":""},{"test1":2,"test2":"test2.2","test3":""}]}`, + `{"page":1,"perPage":30,"totalItems":2,"totalPages":1,"items":[{"test1":1,"test2":"test2.1","test3":""},{"test1":2,"test2":"test2.2","test3":""}]}`, []string{ "SELECT COUNT(*) FROM `test` WHERE NOT (`test1` IS NULL) ORDER BY `test1` ASC", "SELECT * FROM `test` WHERE NOT (`test1` IS NULL) ORDER BY `test1` ASC LIMIT 30", @@ -286,7 +286,7 @@ func TestProviderExecNonEmptyQuery(t *testing.T) { []FilterData{"test2 != null", "test1 >= 2"}, "", false, - `{"page":1,"perPage":` + fmt.Sprint(MaxPerPage) + `,"totalItems":1,"items":[{"test1":2,"test2":"test2.2","test3":""}]}`, + `{"page":1,"perPage":` + fmt.Sprint(MaxPerPage) + `,"totalItems":1,"totalPages":1,"items":[{"test1":2,"test2":"test2.2","test3":""}]}`, []string{ "SELECT COUNT(*) FROM `test` WHERE ((NOT (`test1` IS NULL)) AND (COALESCE(test2, '') != COALESCE(null, ''))) AND (test1 >= 2) ORDER BY `test1` ASC, `test2` DESC", "SELECT * FROM `test` WHERE ((NOT (`test1` IS NULL)) AND (COALESCE(test2, '') != COALESCE(null, ''))) AND (test1 >= 2) ORDER BY `test1` ASC, `test2` DESC LIMIT 200", @@ -300,7 +300,7 @@ func TestProviderExecNonEmptyQuery(t *testing.T) { []FilterData{"test3 != ''"}, "", false, - `{"page":1,"perPage":10,"totalItems":0,"items":[]}`, + `{"page":1,"perPage":10,"totalItems":0,"totalPages":0,"items":[]}`, []string{ "SELECT COUNT(*) FROM `test` WHERE (NOT (`test1` IS NULL)) AND (COALESCE(test3, '') != COALESCE('', '')) ORDER BY `test1` ASC, `test3` ASC", "SELECT * FROM `test` WHERE (NOT (`test1` IS NULL)) AND (COALESCE(test3, '') != COALESCE('', '')) ORDER BY `test1` ASC, `test3` ASC LIMIT 10", @@ -314,7 +314,7 @@ func TestProviderExecNonEmptyQuery(t *testing.T) { []FilterData{}, "", false, - `{"page":2,"perPage":1,"totalItems":2,"items":[{"test1":2,"test2":"test2.2","test3":""}]}`, + `{"page":2,"perPage":1,"totalItems":2,"totalPages":2,"items":[{"test1":2,"test2":"test2.2","test3":""}]}`, []string{ "SELECT COUNT(*) FROM `test` WHERE NOT (`test1` IS NULL) ORDER BY `test1` ASC", "SELECT * FROM `test` WHERE NOT (`test1` IS NULL) ORDER BY `test1` ASC LIMIT 1 OFFSET 1", @@ -328,7 +328,7 @@ func TestProviderExecNonEmptyQuery(t *testing.T) { []FilterData{}, "test.test1", false, - `{"page":2,"perPage":1,"totalItems":2,"items":[{"test1":2,"test2":"test2.2","test3":""}]}`, + `{"page":2,"perPage":1,"totalItems":2,"totalPages":2,"items":[{"test1":2,"test2":"test2.2","test3":""}]}`, []string{ "SELECT COUNT(DISTINCT(test.test1)) FROM `test` WHERE NOT (`test1` IS NULL) ORDER BY `test1` ASC", "SELECT * FROM `test` WHERE NOT (`test1` IS NULL) ORDER BY `test1` ASC LIMIT 1 OFFSET 1", @@ -403,7 +403,7 @@ func TestProviderParseAndExec(t *testing.T) { { "", false, - `{"page":1,"perPage":123,"totalItems":2,"items":[{"test1":1,"test2":"test2.1","test3":""},{"test1":2,"test2":"test2.2","test3":""}]}`, + `{"page":1,"perPage":123,"totalItems":2,"totalPages":1,"items":[{"test1":1,"test2":"test2.1","test3":""},{"test1":2,"test2":"test2.2","test3":""}]}`, }, // invalid query { @@ -439,7 +439,7 @@ func TestProviderParseAndExec(t *testing.T) { { "page=3&perPage=555&filter=test1>1&sort=-test2,test3", false, - `{"page":1,"perPage":200,"totalItems":1,"items":[{"test1":2,"test2":"test2.2","test3":""}]}`, + `{"page":1,"perPage":200,"totalItems":1,"totalPages":1,"items":[{"test1":2,"test2":"test2.2","test3":""}]}`, }, }