You've already forked pocketbase
							
							
				mirror of
				https://github.com/pocketbase/pocketbase.git
				synced 2025-10-31 16:47:43 +02:00 
			
		
		
		
	call transaction Dao events only after commit, added totalPages to the search response and updated the tests
This commit is contained in:
		| @@ -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(`{ | ||||
|   | ||||
| @@ -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 | ||||
| 	}) | ||||
|  | ||||
|   | ||||
| @@ -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()) | ||||
|   | ||||
							
								
								
									
										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 | ||||
| 		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)") | ||||
|   | ||||
| @@ -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 { | ||||
|   | ||||
| @@ -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 | ||||
| 		} | ||||
|   | ||||
| @@ -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 { | ||||
|   | ||||
| @@ -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 | ||||
| } | ||||
|   | ||||
| @@ -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":""}]}`, | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user