call transaction Dao events only after commit, added totalPages to the search response and updated the tests
This commit is contained in:
parent
8288da8372
commit
f8f785d6e3
|
@ -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(`{
|
||||||
|
|
|
@ -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
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -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())
|
||||||
|
|
24
daos/base.go
24
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
|
// so execute the function within the current transaction
|
||||||
return fn(dao)
|
return fn(dao)
|
||||||
case *dbx.DB:
|
case *dbx.DB:
|
||||||
|
afterCalls := []afterCallGroup{}
|
||||||
|
|
||||||
return txOrDB.Transactional(func(tx *dbx.Tx) error {
|
txError := txOrDB.Transactional(func(tx *dbx.Tx) error {
|
||||||
txDao := New(tx)
|
txDao := New(tx)
|
||||||
|
|
||||||
afterCalls := []afterCallGroup{}
|
|
||||||
|
|
||||||
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)")
|
||||||
|
|
|
@ -365,32 +365,34 @@ 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 {
|
||||||
// test create
|
return txDao1.RunInTransaction(func(txDao2 *daos.Dao) error {
|
||||||
// ---
|
// test create
|
||||||
newModel := &models.Admin{}
|
// ---
|
||||||
newModel.Email = "test_new1@example.com"
|
newModel := &models.Admin{}
|
||||||
newModel.SetPassword("123456")
|
newModel.Email = "test_new1@example.com"
|
||||||
if err := txDao.Save(newModel); err != nil {
|
newModel.SetPassword("123456")
|
||||||
t.Fatal(err)
|
if err := txDao2.Save(newModel); err != nil {
|
||||||
}
|
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 {
|
||||||
|
@ -451,32 +453,34 @@ 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 {
|
||||||
// test create
|
return txDao1.RunInTransaction(func(txDao2 *daos.Dao) error {
|
||||||
// ---
|
// test create
|
||||||
newModel := &models.Admin{}
|
// ---
|
||||||
newModel.Email = "test_new1@example.com"
|
newModel := &models.Admin{}
|
||||||
newModel.SetPassword("123456")
|
newModel.Email = "test_new1@example.com"
|
||||||
if err := txDao.Save(newModel); err != nil {
|
newModel.SetPassword("123456")
|
||||||
t.Fatal(err)
|
if err := txDao2.Save(newModel); err != nil {
|
||||||
}
|
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 {
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,9 +37,8 @@ func NewRunner(db *dbx.DB, migrationsList MigrationsList) (*Runner, error) {
|
||||||
// Run interactively executes the current runner with the provided args.
|
// Run interactively executes the current runner with the provided args.
|
||||||
//
|
//
|
||||||
// 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 {
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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":""}]}`,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue