call transaction Dao events only after commit, added totalPages to the search response and updated the tests

This commit is contained in:
Gani Georgiev 2022-08-09 16:20:39 +03:00
parent 8288da8372
commit f8f785d6e3
9 changed files with 88 additions and 78 deletions

View File

@ -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, Method: http.MethodPut,
Url: "/api/collections/import", Url: "/api/collections/import",
Body: strings.NewReader(`{ 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, Method: http.MethodPut,
Url: "/api/collections/import", Url: "/api/collections/import",
Body: strings.NewReader(`{ Body: strings.NewReader(`{

View File

@ -210,13 +210,17 @@ func (api *realtimeApi) bindEvents() {
return nil return nil
}) })
api.app.OnRecordAfterCreateRequest().Add(func(e *core.RecordCreateEvent) error { api.app.OnModelAfterCreate().Add(func(e *core.ModelEvent) error {
api.broadcastRecord("create", e.Record) if record, ok := e.Model.(*models.Record); ok {
api.broadcastRecord("create", record)
}
return nil return nil
}) })
api.app.OnRecordAfterUpdateRequest().Add(func(e *core.RecordUpdateEvent) error { api.app.OnModelAfterUpdate().Add(func(e *core.ModelEvent) error {
api.broadcastRecord("update", e.Record) if record, ok := e.Model.(*models.Record); ok {
api.broadcastRecord("update", record)
}
return nil return nil
}) })

View File

@ -217,7 +217,7 @@ func init() {
` `
func migrateCollectionsHandler(app core.App, args []string) error { func migrateCollectionsHandler(app core.App, args []string) error {
createArgs := []string{"collections_import"} createArgs := []string{"collections_snapshot"}
createArgs = append(createArgs, args...) createArgs = append(createArgs, args...)
dao := daos.New(app.DB()) dao := daos.New(app.DB())

View File

@ -65,12 +65,11 @@ func (dao *Dao) RunInTransaction(fn func(txDao *Dao) error) error {
// so execute the function within the current transaction // so execute the function within the current transaction
return fn(dao) return fn(dao)
case *dbx.DB: case *dbx.DB:
return txOrDB.Transactional(func(tx *dbx.Tx) error {
txDao := New(tx)
afterCalls := []afterCallGroup{} afterCalls := []afterCallGroup{}
txError := txOrDB.Transactional(func(tx *dbx.Tx) error {
txDao := New(tx)
if dao.BeforeCreateFunc != nil { if dao.BeforeCreateFunc != nil {
txDao.BeforeCreateFunc = func(eventDao *Dao, m models.Model) error { txDao.BeforeCreateFunc = func(eventDao *Dao, m models.Model) error {
return dao.BeforeCreateFunc(eventDao, m) 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 fn(txDao)
return err })
}
// execute after event calls on successfull transaction if txError == nil {
// execute after event calls on successful transaction
for _, call := range afterCalls { for _, call := range afterCalls {
if call.Action == "create" { switch call.Action {
case "create":
dao.AfterCreateFunc(call.EventDao, call.Model) dao.AfterCreateFunc(call.EventDao, call.Model)
} else if call.Action == "update" { case "update":
dao.AfterUpdateFunc(call.EventDao, call.Model) dao.AfterUpdateFunc(call.EventDao, call.Model)
} else if call.Action == "delete" { case "delete":
dao.AfterDeleteFunc(call.EventDao, call.Model) dao.AfterDeleteFunc(call.EventDao, call.Model)
} }
} }
}
return nil return txError
})
} }
return errors.New("Failed to start transaction (unknown dao.db)") return errors.New("Failed to start transaction (unknown dao.db)")

View File

@ -365,33 +365,35 @@ func TestDaoTransactionHooksCallsOnFailure(t *testing.T) {
existingModel, _ := testApp.Dao().FindAdminByEmail("test@example.com") existingModel, _ := testApp.Dao().FindAdminByEmail("test@example.com")
baseDao.RunInTransaction(func(txDao *daos.Dao) error { baseDao.RunInTransaction(func(txDao1 *daos.Dao) error {
return txDao1.RunInTransaction(func(txDao2 *daos.Dao) error {
// test create // test create
// --- // ---
newModel := &models.Admin{} newModel := &models.Admin{}
newModel.Email = "test_new1@example.com" newModel.Email = "test_new1@example.com"
newModel.SetPassword("123456") newModel.SetPassword("123456")
if err := txDao.Save(newModel); err != nil { if err := txDao2.Save(newModel); err != nil {
t.Fatal(err) t.Fatal(err)
} }
// test update (twice) // test update (twice)
// --- // ---
if err := txDao.Save(existingModel); err != nil { if err := txDao2.Save(existingModel); err != nil {
t.Fatal(err) t.Fatal(err)
} }
if err := txDao.Save(existingModel); err != nil { if err := txDao2.Save(existingModel); err != nil {
t.Fatal(err) t.Fatal(err)
} }
// test delete // test delete
// --- // ---
if err := txDao.Delete(existingModel); err != nil { if err := txDao2.Delete(existingModel); err != nil {
t.Fatal(err) t.Fatal(err)
} }
return errors.New("test_tx_error") return errors.New("test_tx_error")
}) })
})
if beforeCreateFuncCalls != 1 { if beforeCreateFuncCalls != 1 {
t.Fatalf("Expected beforeCreateFuncCalls to be called 1 times, got %d", beforeCreateFuncCalls) t.Fatalf("Expected beforeCreateFuncCalls to be called 1 times, got %d", beforeCreateFuncCalls)
@ -451,33 +453,35 @@ func TestDaoTransactionHooksCallsOnSuccess(t *testing.T) {
existingModel, _ := testApp.Dao().FindAdminByEmail("test@example.com") existingModel, _ := testApp.Dao().FindAdminByEmail("test@example.com")
baseDao.RunInTransaction(func(txDao *daos.Dao) error { baseDao.RunInTransaction(func(txDao1 *daos.Dao) error {
return txDao1.RunInTransaction(func(txDao2 *daos.Dao) error {
// test create // test create
// --- // ---
newModel := &models.Admin{} newModel := &models.Admin{}
newModel.Email = "test_new1@example.com" newModel.Email = "test_new1@example.com"
newModel.SetPassword("123456") newModel.SetPassword("123456")
if err := txDao.Save(newModel); err != nil { if err := txDao2.Save(newModel); err != nil {
t.Fatal(err) t.Fatal(err)
} }
// test update (twice) // test update (twice)
// --- // ---
if err := txDao.Save(existingModel); err != nil { if err := txDao2.Save(existingModel); err != nil {
t.Fatal(err) t.Fatal(err)
} }
if err := txDao.Save(existingModel); err != nil { if err := txDao2.Save(existingModel); err != nil {
t.Fatal(err) t.Fatal(err)
} }
// test delete // test delete
// --- // ---
if err := txDao.Delete(existingModel); err != nil { if err := txDao2.Delete(existingModel); err != nil {
t.Fatal(err) t.Fatal(err)
} }
return nil return nil
}) })
})
if beforeCreateFuncCalls != 1 { if beforeCreateFuncCalls != 1 {
t.Fatalf("Expected beforeCreateFuncCalls to be called 1 times, got %d", beforeCreateFuncCalls) t.Fatalf("Expected beforeCreateFuncCalls to be called 1 times, got %d", beforeCreateFuncCalls)

View File

@ -298,7 +298,6 @@ func TestFindUserRelatedRecords(t *testing.T) {
} }
if len(records) != len(scenario.expectedIds) { 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) t.Errorf("(%d) Expected %d records, got %d (%v)", i, len(scenario.expectedIds), len(records), records)
continue continue
} }

View File

@ -39,7 +39,6 @@ func NewRunner(db *dbx.DB, migrationsList MigrationsList) (*Runner, error) {
// The following commands are supported: // The following commands are supported:
// - up - applies all migrations // - up - applies all migrations
// - down [n] - reverts the last n applied migrations // - down [n] - reverts the last n applied migrations
// - create NEW_MIGRATION_NAME - create NEW_MIGRATION_NAME.go file from a migration template
func (r *Runner) Run(args ...string) error { func (r *Runner) Run(args ...string) error {
cmd := "up" cmd := "up"
if len(args) > 0 { if len(args) > 0 {

View File

@ -28,6 +28,7 @@ type Result struct {
Page int `json:"page"` Page int `json:"page"`
PerPage int `json:"perPage"` PerPage int `json:"perPage"`
TotalItems int `json:"totalItems"` TotalItems int `json:"totalItems"`
TotalPages int `json:"totalPages"`
Items any `json:"items"` Items any `json:"items"`
} }
@ -213,10 +214,12 @@ func (s *Provider) Exec(items any) (*Result, error) {
s.perPage = MaxPerPage s.perPage = MaxPerPage
} }
totalPages := int(math.Ceil(float64(totalCount) / float64(s.perPage)))
// normalize page according to the total count // normalize page according to the total count
if s.page <= 0 || totalCount == 0 { if s.page <= 0 || totalCount == 0 {
s.page = 1 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 s.page = totalPages
} }
@ -233,6 +236,7 @@ func (s *Provider) Exec(items any) (*Result, error) {
Page: s.page, Page: s.page,
PerPage: s.perPage, PerPage: s.perPage,
TotalItems: int(totalCount), TotalItems: int(totalCount),
TotalPages: totalPages,
Items: items, Items: items,
}, nil }, nil
} }

View File

@ -236,7 +236,7 @@ func TestProviderExecNonEmptyQuery(t *testing.T) {
[]FilterData{}, []FilterData{},
"", "",
false, 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{ []string{
"SELECT COUNT(*) FROM `test` WHERE NOT (`test1` IS NULL) ORDER BY `test1` ASC", "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", "SELECT * FROM `test` WHERE NOT (`test1` IS NULL) ORDER BY `test1` ASC LIMIT 10",
@ -250,7 +250,7 @@ func TestProviderExecNonEmptyQuery(t *testing.T) {
[]FilterData{}, []FilterData{},
"", "",
false, 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{ []string{
"SELECT COUNT(*) FROM `test` WHERE NOT (`test1` IS NULL) ORDER BY `test1` ASC", "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", "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"}, []FilterData{"test2 != null", "test1 >= 2"},
"", "",
false, 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{ []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 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", "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 != ''"}, []FilterData{"test3 != ''"},
"", "",
false, false,
`{"page":1,"perPage":10,"totalItems":0,"items":[]}`, `{"page":1,"perPage":10,"totalItems":0,"totalPages":0,"items":[]}`,
[]string{ []string{
"SELECT COUNT(*) FROM `test` WHERE (NOT (`test1` IS NULL)) AND (COALESCE(test3, '') != COALESCE('', '')) ORDER BY `test1` ASC, `test3` ASC", "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", "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{}, []FilterData{},
"", "",
false, 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{ []string{
"SELECT COUNT(*) FROM `test` WHERE NOT (`test1` IS NULL) ORDER BY `test1` ASC", "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", "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{}, []FilterData{},
"test.test1", "test.test1",
false, 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{ []string{
"SELECT COUNT(DISTINCT(test.test1)) FROM `test` WHERE NOT (`test1` IS NULL) ORDER BY `test1` ASC", "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", "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, 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 // invalid query
{ {
@ -439,7 +439,7 @@ func TestProviderParseAndExec(t *testing.T) {
{ {
"page=3&perPage=555&filter=test1>1&sort=-test2,test3", "page=3&perPage=555&filter=test1>1&sort=-test2,test3",
false, 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":""}]}`,
}, },
} }